Introduction - Imaginary Realities

Transcription

Introduction - Imaginary Realities
The Epitaph Survival
Guide
Michael James Heron
([email protected])
Blight Draft
Epitaph Online
http://drakkos.co.uk
The Dead Speak
Copyright Notice
All rights, including copyright, in the content of these documents are owned or
controlled by the indicated author.
You are permited to use this material for your own personal, non-commercial use.
This material may be used, adapted, modified, and distributed by the
administration of Epitaph Online (http://drakkos.co.uk– try the veal) as necessary.
You are not otherwise permitted to copy, distribute, download, transmit, show in
public, adapt or change in any way the content of this document for any purpose
whatsoever without the prior written permission of the indicated author(s).
If you wish to use this material for non-personal use, please contact the authors of
the texts for permission.
If you find these texts useful and want to give less niche programming languages
a try, come check out http://www.monkeys-at-keyboards.com for more free
instructional material.
My apologies for the unfriendly legal boilerplate, but I have had people attempt to
steal ownership of my material before.
Please direct any comments about this material to [email protected].
LPC for Dummies
Those of you who have encountered the Discworld texts known as LPC for
Dummies 1 and 2, Working with Others, and Being a Better Creator may find much
that is similar in this document. The reason for that is simple – it’s the same
material, just updated, revised, expanded, and converted over to the Epitaph
Mudlib which is where the original author now resides upon a dark, infernal
throne constructed from the skulls of children. If you are using the Epitaph
mudlib, this material will be of limited used to you. If you are using the Discworld
mudlib, then the Epitaph material will likewise fail to satisfy.
As a result of this conversion being done by fallible human hands, there may be
rogue Discworld references littering this document like corpses in a river. If you
see one of these that isn’t justified by context, please send me an email so I can
fish it out and give it a decent burial. Thank you!
2
Epitaph Online
http://drakkos.co.uk
Table of Contents
WELCOME TO THE CREATORBASE ..............................................................................................................................12
YOU’VE MADE IT TO THE SAFEHOUSE..................................................................................................................................... 13
To Whom It May Concern: .....................................................................................................................................................13
Being a Creator ......................................................................................................................................................................13
Introduction to Epitaph Creator Culture ................................................................................................................................15
Domain Hierarchy ..................................................................................................................................................................18
Invisibility and You .................................................................................................................................................................19
Your Creating Environment ....................................................................................................................................................20
Creator Commands ................................................................................................................................................................23
Alts and You ...........................................................................................................................................................................28
Epitaph Development Cycle ...................................................................................................................................................29
Conclusion .............................................................................................................................................................................30
INTRODUCTORY LPC ..................................................................................................................................................32
INTRODUCTORY LPC WITH THE EPITAPH LIB ............................................................................................................................ 33
Introduction ...........................................................................................................................................................................33
The Learning Scenario............................................................................................................................................................33
The Structure of the Game ....................................................................................................................................................34
Programming as a Concept ....................................................................................................................................................35
It's All Down To You................................................................................................................................................................38
Conclusion .............................................................................................................................................................................38
MY FIRST ROOM............................................................................................................................................................... 39
Introduction ...........................................................................................................................................................................39
Your Own Deadville ................................................................................................................................................................39
The Basic Template ................................................................................................................................................................40
My Room Sucks ......................................................................................................................................................................42
It's a little too quiet... .............................................................................................................................................................43
A Second Room ......................................................................................................................................................................43
Our Overall Plan .....................................................................................................................................................................44
Property Ladder .....................................................................................................................................................................45
Conclusion .............................................................................................................................................................................46
MY FIRST AREA ................................................................................................................................................................ 47
Introduction ...........................................................................................................................................................................47
The Structure .........................................................................................................................................................................47
Exits and Maintainability........................................................................................................................................................49
Our Exitses .............................................................................................................................................................................51
Chain, chain, chain... ..............................................................................................................................................................52
What is all that crazy STD_OUTSIDE stuff, anyway? ...............................................................................................................54
Conclusion .............................................................................................................................................................................55
BUILDING THE PERFECT BEAST ............................................................................................................................................. 56
Introduction ...........................................................................................................................................................................56
A First NPC .............................................................................................................................................................................56
Breathing Life into the Lifeless ...............................................................................................................................................58
Cover Yourself Up, You'll Catch a Cold ...................................................................................................................................60
Request Item ..........................................................................................................................................................................61
Challenges ..............................................................................................................................................................................61
Chatting Away ........................................................................................................................................................................62
Conclusion .............................................................................................................................................................................65
HOOKING UP ................................................................................................................................................................... 66
Introduction ...........................................................................................................................................................................66
The Path.h Problem ...............................................................................................................................................................66
Sorted! ...................................................................................................................................................................................68
Reset Your Phasers for Stun ...................................................................................................................................................68
If at First You Don't Succeed ..................................................................................................................................................70
Compound Interest ................................................................................................................................................................73
Conclusion .............................................................................................................................................................................75
BACK TO THE BEGINNING .................................................................................................................................................... 76
Introduction ...........................................................................................................................................................................76
3
Epitaph Online
http://drakkos.co.uk
Captain Beefy's Return...........................................................................................................................................................76
The Road Less Travelled .........................................................................................................................................................77
Bigger, Better, Faster ..............................................................................................................................................................78
Search Profiles .......................................................................................................................................................................79
The Taskmapper .....................................................................................................................................................................80
Task Mapping .........................................................................................................................................................................81
Switching Things Around .......................................................................................................................................................84
Scoping Things Out ................................................................................................................................................................85
Conclusion .............................................................................................................................................................................87
NOW THAT WE'RE AN ITEM ................................................................................................................................................ 88
Introduction ...........................................................................................................................................................................88
Virtually Self-Explanatory.......................................................................................................................................................88
Beefy's Boots .........................................................................................................................................................................90
Bling .......................................................................................................................................................................................91
Item Normalisation ................................................................................................................................................................92
Long Dollars ...........................................................................................................................................................................93
More Item Additions ..............................................................................................................................................................93
Conclusion .............................................................................................................................................................................95
AN INSIDE JOB.................................................................................................................................................................. 96
Introduction ...........................................................................................................................................................................96
The Mysterious Room ............................................................................................................................................................96
Modifying Exits ......................................................................................................................................................................97
Shop 'Till You Drop .................................................................................................................................................................99
Stabby Joe ............................................................................................................................................................................100
Conclusion ...........................................................................................................................................................................101
DYSFUNCTIONAL BEHAVIOUR ............................................................................................................................................ 102
Introduction .........................................................................................................................................................................102
Our Task ...............................................................................................................................................................................102
The Science Bit... Concentrate! ............................................................................................................................................102
The Structure of a Function .................................................................................................................................................103
A Little Bit More... ................................................................................................................................................................105
Function Scope ....................................................................................................................................................................106
Onwards and Upwards! .......................................................................................................................................................106
Violence Begets Violence .....................................................................................................................................................109
Conclusion ...........................................................................................................................................................................112
GOING LOOPY ................................................................................................................................................................ 113
Introduction .........................................................................................................................................................................113
Stabby's Shop .......................................................................................................................................................................113
Long Hand ............................................................................................................................................................................114
Loops ...................................................................................................................................................................................115
Doing While... ......................................................................................................................................................................116
For Loops .............................................................................................................................................................................117
Conclusion ...........................................................................................................................................................................118
ARRAYS, YOU SAY? .......................................................................................................................................................... 119
Introduction .........................................................................................................................................................................119
Slicey Pete ............................................................................................................................................................................119
The Array .............................................................................................................................................................................121
Array Indexing ......................................................................................................................................................................123
Array management ..............................................................................................................................................................123
Fine Dining ...........................................................................................................................................................................124
The Foreach Structure .........................................................................................................................................................126
Conclusion ...........................................................................................................................................................................127
HOORAY FOR ARRAYS ....................................................................................................................................................... 128
Introduction .........................................................................................................................................................................128
A Secret to be Discovered ....................................................................................................................................................128
A Grue Some Fate! ...............................................................................................................................................................129
Getting To Our Shop of Horror .............................................................................................................................................133
Tell Events ............................................................................................................................................................................134
Conclusion ...........................................................................................................................................................................135
MAPPING IT OUT ............................................................................................................................................................ 136
Introduction .........................................................................................................................................................................136
The Mapping ........................................................................................................................................................................136
The Magic Hate Ball .............................................................................................................................................................138
More Mapping Manipulation, Matey ..................................................................................................................................141
Conclusion ...........................................................................................................................................................................144
4
Epitaph Online
http://drakkos.co.uk
DEBUGGING ................................................................................................................................................................... 145
Introduction .........................................................................................................................................................................145
The Two Different Kinds of Errors ........................................................................................................................................145
Fixing your Syntax Errors ......................................................................................................................................................145
Logic Errors ..........................................................................................................................................................................148
I found an error! ..................................................................................................................................................................149
Conclusion ...........................................................................................................................................................................151
SO LONG ....................................................................................................................................................................... 153
Introduction .........................................................................................................................................................................153
The Past ...............................................................................................................................................................................153
The Present ..........................................................................................................................................................................155
The Future ...........................................................................................................................................................................155
Conclusion ...........................................................................................................................................................................156
READER EXERCISES .......................................................................................................................................................... 157
Introduction .........................................................................................................................................................................157
Exercises ..............................................................................................................................................................................157
Send Suggestions .................................................................................................................................................................159
BEING A BETTER CREATOR .......................................................................................................................................160
WELCOME TO THE LEARNING ZONE .................................................................................................................................... 161
Introduction .........................................................................................................................................................................161
The Plan for This Course ......................................................................................................................................................161
What's In A Game? ..............................................................................................................................................................162
Design Philosophy of Areas ..................................................................................................................................................164
Zombieville ..........................................................................................................................................................................165
Conclusion ...........................................................................................................................................................................166
PLAYER DEMOGRAPHICS ................................................................................................................................................... 167
Introduction .........................................................................................................................................................................167
Bartle's Definitions...............................................................................................................................................................167
Your Zone's Players ..............................................................................................................................................................168
Catering For Achievers .........................................................................................................................................................168
Catering for Explorers ..........................................................................................................................................................170
Catering for Socialisers.........................................................................................................................................................171
Catering for Imposers ..........................................................................................................................................................172
Conclusion ...........................................................................................................................................................................172
URBAN PLANNING........................................................................................................................................................... 173
Introduction .........................................................................................................................................................................173
An Interesting Area ..............................................................................................................................................................173
The Refined Feature Density Model ....................................................................................................................................174
Feature Planning ..................................................................................................................................................................177
Conclusion ...........................................................................................................................................................................179
THEMATIC CONSIDERATIONS ............................................................................................................................................. 180
Introduction .........................................................................................................................................................................180
Starting Principles ................................................................................................................................................................180
The Opportunities ................................................................................................................................................................181
Profanity and Racism ...........................................................................................................................................................182
Humour ................................................................................................................................................................................183
Player Input ..........................................................................................................................................................................184
Back to Zombieville ..............................................................................................................................................................185
Conclusion ...........................................................................................................................................................................187
THE BEST LAID SCHEMES .................................................................................................................................................. 188
Introduction .........................................................................................................................................................................188
Making the Map...................................................................................................................................................................188
Incorporating Features.........................................................................................................................................................189
The School ...........................................................................................................................................................................191
Instances ..............................................................................................................................................................................191
The Shop ..............................................................................................................................................................................192
It All Adds Up .......................................................................................................................................................................192
Conclusion ...........................................................................................................................................................................193
QUEST DESIGN ............................................................................................................................................................... 194
Introduction .........................................................................................................................................................................194
Why Quests? ........................................................................................................................................................................194
Hand-Crafted Craziness ........................................................................................................................................................195
The Anatomy of a Epitaph Quest .........................................................................................................................................196
5
Epitaph Online
http://drakkos.co.uk
Conclusion ...........................................................................................................................................................................201
LET'S TALK ABOUT QUESTS, BABY ....................................................................................................................................... 202
Introduction .........................................................................................................................................................................202
The Intention of a Quest ......................................................................................................................................................202
The Zombieville Quests ........................................................................................................................................................203
Conclusion ...........................................................................................................................................................................205
DYNAMIC QUEST DESIGN ................................................................................................................................................. 206
Introduction .........................................................................................................................................................................206
Dynamic and Linear Quests .................................................................................................................................................206
Which are Best? ...................................................................................................................................................................207
Some Guiding Principles For Dynamic Quest Design ...........................................................................................................208
Dynamic Quests – The Book Sorting Quest ..........................................................................................................................210
Dynamic Quests – The Partition Quest ................................................................................................................................212
Dynamic Quests – The Secret Room Quest ..........................................................................................................................213
Conclusion ...........................................................................................................................................................................213
THE TEN COMMANDMENTS OF ROOM DESCRIPTIONS ............................................................................................................ 215
Introduction .........................................................................................................................................................................215
The Ten Commandments .....................................................................................................................................................215
Conclusion ...........................................................................................................................................................................222
THE ART OF WRITING ROOM DESCRIPTIONS ........................................................................................................................ 223
Introduction .........................................................................................................................................................................223
Your Mental Picture .............................................................................................................................................................223
Hand-Crafting versus Procedural Descriptions ....................................................................................................................226
Room Chats ..........................................................................................................................................................................228
References ...........................................................................................................................................................................229
Quality Assurance ................................................................................................................................................................230
Conclusion ...........................................................................................................................................................................231
NON PLAYER CHARACTERS ................................................................................................................................................ 232
Introduction .........................................................................................................................................................................232
NPC Classifications ...............................................................................................................................................................232
NPC Descriptions .................................................................................................................................................................234
Equipment ...........................................................................................................................................................................235
Dialog ...................................................................................................................................................................................236
Conclusion ...........................................................................................................................................................................238
BEASTS OF ZOMBIEVILLE ................................................................................................................................................... 239
Introduction .........................................................................................................................................................................239
Our NPC Manifesto ..............................................................................................................................................................239
The Zombies.........................................................................................................................................................................240
The Survivor .........................................................................................................................................................................241
The Headmistress ................................................................................................................................................................243
Boss Battles ..........................................................................................................................................................................244
Conclusion ...........................................................................................................................................................................246
FEATURE DEVELOPMENT................................................................................................................................................... 247
Introduction .........................................................................................................................................................................247
A Local Shop For Local People ..............................................................................................................................................247
The Library ...........................................................................................................................................................................248
Feature Creep ......................................................................................................................................................................250
Conclusion ...........................................................................................................................................................................252
FINISHING UP................................................................................................................................................................. 253
Introduction .........................................................................................................................................................................253
Integration Mechanisms ......................................................................................................................................................253
Advertisement .....................................................................................................................................................................254
Bookkeeping ........................................................................................................................................................................255
Lessons Learned...................................................................................................................................................................257
Conclusion ...........................................................................................................................................................................257
LESSONS LEARNED........................................................................................................................................................... 259
Introduction .........................................................................................................................................................................259
Death March Projects ..........................................................................................................................................................259
Realism over Fun..................................................................................................................................................................260
Randomness Is Not Challenging ...........................................................................................................................................261
Complexity is not King .........................................................................................................................................................261
Complaints Are Not Representative .....................................................................................................................................262
Know When to Step Back .....................................................................................................................................................262
For God's Sake, Have Fun .....................................................................................................................................................263
6
Epitaph Online
http://drakkos.co.uk
Conclusion ...........................................................................................................................................................................264
FINAL THOUGHTS ............................................................................................................................................................ 265
Introduction .........................................................................................................................................................................265
What Happens Now? ...........................................................................................................................................................265
Keeping Our Players Happy ..................................................................................................................................................265
Conclusion ...........................................................................................................................................................................266
INTERMEDIATE LPC ..................................................................................................................................................267
THE MUDLIB STRIKES BACK............................................................................................................................................... 268
Introduction .........................................................................................................................................................................268
Zombieville ..........................................................................................................................................................................268
The Village Plan....................................................................................................................................................................269
A Word Of Caution ...............................................................................................................................................................269
Conclusion ...........................................................................................................................................................................270
INHERITANCE .................................................................................................................................................................. 271
Introduction .........................................................................................................................................................................271
Inheritance ...........................................................................................................................................................................271
Hooking It All Up ..................................................................................................................................................................272
Replace Program ..................................................................................................................................................................273
Success! ...............................................................................................................................................................................274
However... ............................................................................................................................................................................275
Multiple Inheritance and Scope Resolution .........................................................................................................................276
Our Zombieville Room Inherit ..............................................................................................................................................278
Visibility ...............................................................................................................................................................................279
The Impact of Change ..........................................................................................................................................................281
Conclusion ...........................................................................................................................................................................281
THE LIBRARY QUEST ........................................................................................................................................................ 283
Introduction .........................................................................................................................................................................283
An Epitaph Quest .................................................................................................................................................................283
Data Representation ............................................................................................................................................................284
In A Class of Your Own .........................................................................................................................................................287
The Library State ..................................................................................................................................................................290
Dynamic Quest Design .........................................................................................................................................................290
The Rest of the Quest ..........................................................................................................................................................291
Conclusion ...........................................................................................................................................................................292
ADDING COMMANDS....................................................................................................................................................... 293
Introduction .........................................................................................................................................................................293
Deciding On a User Interface ...............................................................................................................................................293
Adding Commands ...............................................................................................................................................................295
Failure Messages..................................................................................................................................................................297
More on Patterns .................................................................................................................................................................299
Optional Parameters and Set Choices ..................................................................................................................................301
Direct and Indirect Objects ..................................................................................................................................................301
Passed Parameters ...............................................................................................................................................................302
Tannah's Pattern Matcher ....................................................................................................................................................303
Conclusion ...........................................................................................................................................................................304
THE LIBRARY ROOM ........................................................................................................................................................ 305
Introduction .........................................................................................................................................................................305
The Room .............................................................................................................................................................................305
Guess the Syntax Quests ......................................................................................................................................................309
Viewing the Blurb ................................................................................................................................................................310
The Sort Command ..............................................................................................................................................................311
Except, not quite... ...............................................................................................................................................................314
Conclusion ...........................................................................................................................................................................316
QUEST HANDLERS ........................................................................................................................................................... 317
Introduction .........................................................................................................................................................................317
The Handlers ........................................................................................................................................................................317
Cast Your Mind Back... .........................................................................................................................................................318
Our Second Quest ................................................................................................................................................................319
Conclusion ...........................................................................................................................................................................322
OUR LAST QUEST ............................................................................................................................................................ 323
Introduction .........................................................................................................................................................................323
The Quest Design .................................................................................................................................................................323
Handlers ...............................................................................................................................................................................324
7
Epitaph Online
http://drakkos.co.uk
The Portrait Handler ............................................................................................................................................................325
Back to the Library ...............................................................................................................................................................330
Conclusion ...........................................................................................................................................................................331
FINISHING TOUCHES ........................................................................................................................................................ 333
Introduction .........................................................................................................................................................................333
The Second Level Room .......................................................................................................................................................333
I'm Looking Through... uh... behind you ..............................................................................................................................334
Ecretsay Odecay ...................................................................................................................................................................335
Random Guessing ................................................................................................................................................................336
What Kind Of Day Has It Been? ............................................................................................................................................337
Conclusion ...........................................................................................................................................................................338
ZOMBIES ....................................................................................................................................................................... 339
Introduction .........................................................................................................................................................................339
The Zombie Template ..........................................................................................................................................................339
Event Driven Programming ..................................................................................................................................................343
The Power of Inherits...........................................................................................................................................................345
Conclusion ...........................................................................................................................................................................346
AGNES MCKAY ............................................................................................................................................................... 347
Introduction .........................................................................................................................................................................347
Agnes ...................................................................................................................................................................................347
Librarian Notes and Callbacks ..............................................................................................................................................348
Back to the Portrait Handler ................................................................................................................................................349
Data Persistence ..................................................................................................................................................................352
Effective User Identifiers ......................................................................................................................................................352
Conclusion ...........................................................................................................................................................................355
FUNCTION POINTERS ....................................................................................................................................................... 356
Introduction .........................................................................................................................................................................356
The Structure of a Function Pointer .....................................................................................................................................356
The Four Holy Efuns .............................................................................................................................................................358
Back to the Library ...............................................................................................................................................................360
Function Pointer Support .....................................................................................................................................................362
A Few More Things about Function Pointers .......................................................................................................................363
Conclusion ...........................................................................................................................................................................364
THE HEADMISTRESS......................................................................................................................................................... 365
Introduction .........................................................................................................................................................................365
The Skeleton of a Boss Zombie ............................................................................................................................................365
NPC Combat Behaviours ......................................................................................................................................................368
Boss Powers .........................................................................................................................................................................370
Pus Attack ............................................................................................................................................................................371
Boss Drops ...........................................................................................................................................................................372
Conclusion ...........................................................................................................................................................................373
SHOPPING AROUND ........................................................................................................................................................ 374
Introduction .........................................................................................................................................................................374
Item Development ...............................................................................................................................................................374
Auto Loading ........................................................................................................................................................................375
Dressing Up ..........................................................................................................................................................................376
Back To The Auto Loading ....................................................................................................................................................377
Doing It Better .....................................................................................................................................................................379
Back to our Shop ..................................................................................................................................................................379
Conclusion ...........................................................................................................................................................................380
CAUSE AND EFFECT ......................................................................................................................................................... 381
Introduction .........................................................................................................................................................................381
What's An Effect? .................................................................................................................................................................381
The Power of Effects ............................................................................................................................................................384
Working with Effects ............................................................................................................................................................387
Bits and Pieces .....................................................................................................................................................................389
Conclusion ...........................................................................................................................................................................390
ACHIEVING YOUR POTENTIAL............................................................................................................................................. 391
Introduction .........................................................................................................................................................................391
Achievements and Quests ...................................................................................................................................................391
How Often Should I Add Achievements? .............................................................................................................................392
My First Achievement ..........................................................................................................................................................393
Criteria .................................................................................................................................................................................394
Criteria Matching .................................................................................................................................................................395
A Little More Complicated ...................................................................................................................................................397
8
Epitaph Online
http://drakkos.co.uk
Other Achievement Settings ................................................................................................................................................400
I've written an achievement! What now? ............................................................................................................................400
Ack, it sucks! How do I get rid of it? .....................................................................................................................................401
Achievement Levels .............................................................................................................................................................401
Conclusion ...........................................................................................................................................................................402
SO HERE WE ARE AGAIN.................................................................................................................................................. 403
Introduction .........................................................................................................................................................................403
What's On Your Mind? .........................................................................................................................................................403
Help Us to Help You .............................................................................................................................................................404
Where Do You Go From Here? .............................................................................................................................................404
Conclusion ...........................................................................................................................................................................405
WORKING WITH OTHERS .........................................................................................................................................406
PEOPLE SKILLS ................................................................................................................................................................ 407
Introduction .........................................................................................................................................................................407
Whole New Skills .................................................................................................................................................................407
Standard Standards ..............................................................................................................................................................408
Professionalism ....................................................................................................................................................................409
Conclusion ...........................................................................................................................................................................410
CODE LAYOUT ................................................................................................................................................................ 411
Introduction .........................................................................................................................................................................411
Code Formatting ..................................................................................................................................................................411
Conclusion ...........................................................................................................................................................................416
COLLABORATION ............................................................................................................................................................. 417
Introduction .........................................................................................................................................................................417
The Social Context of Collaboration .....................................................................................................................................417
Development in Volunteer Environments ............................................................................................................................418
What Are The Benefits of Collaboration? ............................................................................................................................419
Collaboration Tools on Epitaph ............................................................................................................................................420
A Suggested Collaboration Process ......................................................................................................................................421
Conclusion ...........................................................................................................................................................................422
SOCIAL CAPITAL .............................................................................................................................................................. 423
Introduction .........................................................................................................................................................................423
Creator Politics and MUDs ...................................................................................................................................................423
The Ten Commandments Of Egoless Programming .............................................................................................................424
Trust and Common Ground .................................................................................................................................................426
The Trust Triad .....................................................................................................................................................................428
Conclusion ...........................................................................................................................................................................429
THE DARK ART OF REFACTORING........................................................................................................................................ 431
Introduction .........................................................................................................................................................................431
Refactoring...........................................................................................................................................................................431
Good Code ...........................................................................................................................................................................431
Impact of Change .................................................................................................................................................................431
The Rules .............................................................................................................................................................................432
Breaking the Rules ...............................................................................................................................................................434
Refactoring...........................................................................................................................................................................435
Some Common Refactoring Tasks ........................................................................................................................................436
Conclusion ...........................................................................................................................................................................436
CODING ETIQUITTE.......................................................................................................................................................... 437
Introduction .........................................................................................................................................................................437
Before You Write Any Code ..................................................................................................................................................437
When You Are Writing Code ................................................................................................................................................438
When You Have Written Code .............................................................................................................................................441
Conclusion ...........................................................................................................................................................................442
SOURCE CONTROL ........................................................................................................................................................... 443
Introduction .........................................................................................................................................................................443
Source Control in the Abstract .............................................................................................................................................443
The Epitaph RCS System .......................................................................................................................................................444
Problems ..............................................................................................................................................................................448
Conclusion ...........................................................................................................................................................................449
DOCUMENTATION ........................................................................................................................................................... 450
Introduction .........................................................................................................................................................................450
Commenting ........................................................................................................................................................................450
Commenting Good Practice .................................................................................................................................................451
9
Epitaph Online
http://drakkos.co.uk
Autodoc ...............................................................................................................................................................................452
The Autodoc Process ...........................................................................................................................................................455
Other Help-Files ...................................................................................................................................................................456
Why Document? ..................................................................................................................................................................457
Conclusion ...........................................................................................................................................................................457
DOMAIN INTEGRATION ..................................................................................................................................................... 458
Introduction .........................................................................................................................................................................458
Multiple Developers – the Traditional Approach .................................................................................................................458
Examples of this on Epitaph .................................................................................................................................................459
Continuous Integration ........................................................................................................................................................460
A Framework for Area Integration .......................................................................................................................................461
Conclusion ...........................................................................................................................................................................462
GROUP DYNAMICS .......................................................................................................................................................... 464
Introduction .........................................................................................................................................................................464
What is a Domain? ...............................................................................................................................................................464
When Is A Group Not A Group? ...........................................................................................................................................466
Group Roles .........................................................................................................................................................................467
Group-think .........................................................................................................................................................................469
Conclusion ...........................................................................................................................................................................470
PROJECT MANAGEMENT .................................................................................................................................................. 471
Introduction .........................................................................................................................................................................471
Project Management 101 ....................................................................................................................................................471
Frameworks .........................................................................................................................................................................472
Communication and Team Roles ..........................................................................................................................................473
Subdivision of Effort and Ownership....................................................................................................................................474
The Epitaph Project Tracker .................................................................................................................................................475
Conclusion ...........................................................................................................................................................................479
MAINTENANCE ............................................................................................................................................................... 480
Introduction .........................................................................................................................................................................480
Maintenance in the Software Development Process ...........................................................................................................480
Domain Maintenance ..........................................................................................................................................................481
Where Do Bugs Come From? ...............................................................................................................................................482
Bug Triage ............................................................................................................................................................................483
The Error Handler ................................................................................................................................................................484
Conclusion ...........................................................................................................................................................................487
THE EXPERIENCE DIVIDE ................................................................................................................................................... 488
Introduction .........................................................................................................................................................................488
Professional and Amateur Programmers .............................................................................................................................488
Deep Smarts ........................................................................................................................................................................490
The Tension ..........................................................................................................................................................................491
Strategies for Success ..........................................................................................................................................................492
Conclusion ...........................................................................................................................................................................493
WRAPPING UP ............................................................................................................................................................... 495
Introduction .........................................................................................................................................................................495
Collegiality ...........................................................................................................................................................................495
Conclusion ...........................................................................................................................................................................495
EPITAPH QUICK REFERENCES ...................................................................................................................................497
QUICK REFERENCE - RUMOUR OBJECTS ............................................................................................................................... 498
Introduction .........................................................................................................................................................................498
Why Do We Use Them? .......................................................................................................................................................498
When to add a rumour object .............................................................................................................................................498
What do they look like? .......................................................................................................................................................498
QUICK REFERENCE - POWERED ITEMS ................................................................................................................................. 500
Introduction .........................................................................................................................................................................500
Why Do We Use Them? .......................................................................................................................................................500
When to add a powered item ..............................................................................................................................................500
What do they look like? .......................................................................................................................................................500
QUICK REFERENCE - CITY STREET ....................................................................................................................................... 504
Introduction .........................................................................................................................................................................504
What is a City Street ............................................................................................................................................................504
Why Do We Use Them? .......................................................................................................................................................504
What Do They Look Like? .....................................................................................................................................................505
QUICK REFERENCE - TEMPLATED ROOMS ............................................................................................................................. 508
10
Epitaph Online
http://drakkos.co.uk
Introduction .........................................................................................................................................................................508
What is a Template ..............................................................................................................................................................508
Why Do We Use Them? .......................................................................................................................................................508
What Do They Look Like.......................................................................................................................................................508
QUICK REFERENCE - CITY LANDMARK .................................................................................................................................. 511
Introduction .........................................................................................................................................................................511
What is a Landmark .............................................................................................................................................................511
Why Do We Use Them? .......................................................................................................................................................511
When to add a landmark .....................................................................................................................................................511
Getting your Landmark to Show Up .....................................................................................................................................511
Landmarks and the City .......................................................................................................................................................512
What does the data file look like?........................................................................................................................................512
Items and Shorts ..................................................................................................................................................................515
Aliases and Descriptions ......................................................................................................................................................515
Other Add Items ..................................................................................................................................................................516
The Rest ...............................................................................................................................................................................518
Plotted Landmarks ...............................................................................................................................................................518
QUICK REFERENCE - A PRACTISE OBJECT ............................................................................................................................. 520
Introduction .........................................................................................................................................................................520
What is a Practise Object .....................................................................................................................................................520
Why Do We Use Them? .......................................................................................................................................................520
When to add a practise object .............................................................................................................................................520
What do they look like .........................................................................................................................................................520
Textbooks .............................................................................................................................................................................522
Object Levels ........................................................................................................................................................................523
Powered Practise Objects ....................................................................................................................................................523
QUICK REFERENCE - A MAESTRO MODULE .......................................................................................................................... 526
Introduction .........................................................................................................................................................................526
What is a Maestro Module ..................................................................................................................................................526
Why Do We Use Them? .......................................................................................................................................................526
When to add a Maestro Module ..........................................................................................................................................526
What Does A Maestro Module Look Like .............................................................................................................................526
Generating the Event ...........................................................................................................................................................528
QUICK REFERENCE - FACTIONS ........................................................................................................................................... 529
Introduction .........................................................................................................................................................................529
What is a Faction .................................................................................................................................................................529
Why Do We Use Them? .......................................................................................................................................................529
When to add a Faction .........................................................................................................................................................529
An Example Faction File .......................................................................................................................................................529
Faction Infrastructure ..........................................................................................................................................................532
QUICK REFERENCE - CRAFTING .......................................................................................................................................... 534
Introduction .........................................................................................................................................................................534
What is crafting ....................................................................................................................................................................534
Why Do We Use Them? .......................................................................................................................................................534
When to add a Crafting Pattern ...........................................................................................................................................534
A Simple Crafting Pattern .....................................................................................................................................................534
A Slightly More Complex Example .......................................................................................................................................537
Finished products ................................................................................................................................................................539
APPENDIX ................................................................................................................................................................541
APPENDIX 1: CODE OWNERSHIP POLICY .............................................................................................................................. 542
Introduction ........................................................................................................................................................... 542
Code Ownership .................................................................................................................................................... 542
When Does It Become Epitaph Code? ................................................................................................................... 542
Epitaph and Discworld........................................................................................................................................... 543
Shared Ownership ................................................................................................................................................. 543
Profiting ................................................................................................................................................................. 544
Not a Straightjacket .............................................................................................................................................. 544
APPENDIX 2 - FEATURE DENSITY ........................................................................................................................................ 546
APPENDIX 3 - GLOSSARY................................................................................................................................................... 548
APPENDIX 4 - FURTHER READING LIST ................................................................................................................................. 550
Game Design ........................................................................................................................................................................550
Theme and Writing ..............................................................................................................................................................551
11
Epitaph Online
http://drakkos.co.uk
Social Contexts .....................................................................................................................................................................552
Miscellaneous ......................................................................................................................................................................552
INDEX ......................................................................................................................................................................553
Section Zero
Welcome to the
Creatorbase
12
Epitaph Online
http://drakkos.co.uk
You’ve made it to the Safehouse
To Whom It May Concern:
The manual you have in your hands is a tome of great power - it details the ways
in which you as an Epitaph Developer can shape the world in which you are
functioning. You have either been selected to be a creator by the administration of
an Epitaph based MUD, or you are the administrator of your own MUD using the
Epitaph lib. If you're not either of these things, you'll probably find this text rather
dry, unsettling, and ultimately of little use. For the rest of this document, we’ll
assume you were employed on Epitaph.
For everyone else though, some words of comfort: It’s okay to be nervous about
this – it’s a big step, and you’ve signed up for a lot of extra responsibility. All we
ask is that you do the best you can – there are people in the Support domain who
are there to help ease this transition and provide you with the guidance you need
to become the Top Notch Creator that your employer believes you can be!
Being a Creator
The fact that you’ve actually been promoted shows a number of things. The first
is that when given a particular task (as undoubtedly happened as part of your
creator application) you showed that you could knuckle down and meet the
request. That’s may not sound like anything, but you’d probably be surprised at
the number of people who give up at that first hurdle.
Being a creator, regardless of what you may have heard, is not easy. On older
MUDs, it was traditional for players who reached a certain level to be instantly
promoted to the rank of God. That’s not the case here – the game isn’t over for
you, you’ve just added an entirely new facet to it. However, that new facet comes
with responsibilities.
First and foremost of these responsibilities is your own willingness to learn.
Although there are people there to help provide you with guidance, we can’t
actively teach you – it’s just not possible when everyone is spread over many
different countries, and time zones. As such, you have to be proactive in learning
how to code – ultimately the person who is responsible for your eventual success
is you.
You wouldn’t be here though if someone didn’t believe that you had that in you –
much as you’d be surprised at the number of people who never complete their
application tasks, you’d probably also be surprised at the number of people who
are rejected as creators. There are certain criteria that an individual is expected to
fulfil, and while these vary from lord to lord, they tend to fall into a number of
broad categories:
13
Epitaph Online



http://drakkos.co.uk
You are expected to be a model player. While some ‘brushes with the law’
can be forgiven, especially if they happened in the distant past or were relatively minor, on the whole you should have a clean player record.
You are expected to be active. While real life will always get in the way of
your volunteer efforts here, and while real life should always be prioritized
over what you do as an Epitaph creator, in the end you are expected to produce some content for the MUD on a regular basis.
You are expected to have an appreciation of both the structure of the MUD,
and the thematic constraints of the setting. You need to have played for a
while, and you need to be familiar with our game canon.
As a creator, you have considerable power over very large swathes of the MUD. We
have to trust you to use that power responsibly. Some people can’t handle the
temptation, and try to turn their creator powers to advantage their own player
characters, or those of their friends. Do not break this rule, or you are going to
get caught. There is always someone who thinks they’re smarter than the people
running the game. Seriously, you’re not... there are always people watching.
As a creator, you are apart from the game – you cannot do anything that lends an
in-game advantage to any other player. This rule is strict, and it is inviolable. If
you break it, you’ll be dismissed, and you’ll never get another chance to be a
creator. While we do allow second chances, we don’t forgive this particular
infraction. You may find yourself under considerable pressure from your friends
to demonstrate your new cosmic powers through gifts of skills, items and cash.
Don’t give in to this temptation – you worked quite hard to get this gig, you don’t
want to throw it away.
Being a creator, or at least being a coding creator, is not about policing the MUD.
There are creators who have that specific responsibility, but it’s not yours. Your
job is to develop the game that people are playing and enjoying. The people who
are responsible for policing the game are certain members of the Support domain,
and those members of Admin. If you feel that something needs to be addressed,
you should either contact a member of the Admin domain directly, or make a post
about it on the bulletin boards. Only in the most extreme of circumstances
should you intervene directly. As you progress in your role as a Epitaph creator, it
will become obvious to you when such situations arise – if you’re not sure, err on
the side of caution.
As a creator, you gain the ability to switch between visibility (everyone can see
you) and invisibility (only fellow creators and those players to whom you have
granted permission can see you). As a new creator, you should instantly go
invisible and stay that way. The command to do this is simply invis. We’ll talk
more about being invisible later.
Players will often pose questions to visible creators – some of these questions will
be quite innocent, and some will be requests for assistance. You do not yet know
enough about how things are done to render useful assistance, so remove
yourself from that situation by going invisible. If you are asked for help by people
on your allow list, either direct them to an online liaison or director, or ask them
to wait if none are online.
14
Epitaph Online
http://drakkos.co.uk
Being a creator also carries with it a very heavy security obligation – you are never
to reveal creator information to players, or to allow yourself to profit directly from
your knowledge of creator information. This restriction is in place to protect you
from accusations of impropriety, and also to protect the integrity of the game –
the hidden values of commands, skills, and items are hidden for a reason – we
don’t want players to know them. You must not confirm or deny anything you are
asked, or provide any answers that a player cannot provide. Only if you have been
authorized to reveal information by your domain leader can you do so. Any
violation of this will carry with it strict penalties, up to and including dismissal.
Your obligation on Epitaph is to contribute – each domain leader will have their
own benchmarks for what is considered to be an acceptable level of activity, but
on the whole it can be summed up as ‘you are expected to make a measurable
contribution to your domain’. Your domain leader, and any domain deputies, will
check up on you on a regular basis to ensure you’re managing, but you should be
proactive in getting touch if you feel you are not.
It may sound like an awful lot of work to be a creator. That’s because it is! But it’s
also great fun and you’ll get to create whole worlds using nothing but your mind
and your computer. Along the way, you’ll learn genuinely useful life skills – there
are any number of creators who can trace real life employment opportunities to
what they learned as creators here.
Introduction to Epitaph Creator Culture
It’s a whole new world on this side of the fence, and that is going to show. It’s an
alien culture, and unless you’ve had a lot of exposure to the way creators think
and talk, you’re going to find it somewhat intimidating.
The general rule you should be following here is one of cautious integration.
That’s not because we’re an unfriendly bunch... I mean, we are, but that’s not why
I’m suggesting this. The creator-base is like any online community – it has its own
culture, its own language, its own in-jokes and its own etiquette. You, as a new
creator, don’t know any of these.
Your first exposure to creator culture is more than likely to be the creator
channel. That’s the main communication channel for Epitaph creators, and is a
mostly social environment. It’s for jokes, gossip, and general chat. You should
feel entirely comfortable with chatting on this channel, although bear in mind that
for many of the regulars on the channel, you’ll be an entirely new name. You
don’t want to be the guy at the party who nobody knows and yet persists on
monopolizing every conversation!
Most of what you do as a newbie creator is read – this document is only one of the
things that is on your ‘to read’ pile. There are also a great many more bulletin
boards than you may have remembered. The creator boards you should read
regularly – vital information is often posted there, and it’s an easy way to build
your knowledge of Epitaph creator culture. The player ones you can choose to
follow as you wish, but if you choose not to, you run the risk of losing touch with
what the players are saying.
15
Epitaph Online
http://drakkos.co.uk
The boards are accessible as usual through the website, but you can also carry a
bulletin board around with you at all times so you can have access in game. The
clone command is used to create copies of items – to get your very own copy of
an in-game bulletin board, you type:
clone /items/creator/board_mas
Once you’ve done that, type summary.
There are a lot of boards... a lot of boards. Some of these are more important
than others. The important boards for now are commonroom, mudlib, and the
board that belongs to your own domain. You should read the other creator boards
too, but you can put them to the bottom of your pile.
Those boards you definitely do not want to read can be removed from your list by
using the killfile command:
killfile flame
As far as your contribution to the player bulletin boards go – well, that should
stop now. Your words carry additional weight as a creator, and you run a huge
risk of being misinterpreted, or having your own personal opinion quoted as
‘Creator opinion’. It’s not so much of an issue with the ‘social’ boards such as five
minute hate, love-in, debates and so forth – but if you post as a creator on a
player board, you’re asking for trouble. The best advice I can give is to post as a
player alt. If you don’t have one, create one – that way, you avoid the impression
that you are speaking from ‘on high’. Remember too that you are now ‘one of us’,
and if you have a concern about the game your first duty is to report it to your
colleagues. While we’re not a mind-cult, and while nobody is demanding that you
‘toe the party line’, we don’t like to read your objections for the first time coming
from your player alt.
I personally recommend the following as guidelines for your board usage:



Avoid posting to a game-related board as a creator. If you have something
to add to an idea being bounced around, then log in a player alt to make
your contribution. Some players may see your postings as coming from a
position of authority, and unless you're in a position where that's accurate,
it should be avoided. If logging in an alt to post is too much effort, it may
be an idea to reconsider whether your posting is entirely necessary.
If you post as a creator, please don't post about developments with which
you are not directly involved. The domain leader and domain deputies of a
domain should make the final call as to whether information on a particular
development should be publicized. This also goes for posting about games
mechanics outside your jurisdiction. If you're not sure if you should be
posting something, don't do it.
If you post, speak for yourself. Don't speak for all creators, since we all have
our own opinions which may, or may not, coincide with what you post.
Referencing to the rest of the creator-base is rude, and inconsiderate.
16
Epitaph Online
http://drakkos.co.uk
Really, it all comes down to this: if you're newly promoted, please think twice
about posting in the first place. Give yourself time to come to grips with your new
duties and responsibilities. It's a natural urge to want to express your opinions,
especially from a new position of authority, but that should wait until you're more
established as a creator. Log in an alt to post... that way, you don't run the risk of
what you say reflecting badly on creators as a whole.
These guidelines also apply to your interactions with the normal talker channels.
As with the creator channel, it’s well worth keeping a low(ish) profile for a while.
If you have any questions about code, then your first port of call is your domain
leader – it is their responsibility to ensure that you are properly supported in the
work you are doing. No one person can know everything though, so if you find no
luck there, you can ask your question on the (code) channel.
There is also a special tool called the ‘Oracle’. If you have a query that gets
resolved, then the chances are someone else would be interested in that
knowledge at some point, so it is appreciated when you can record your question
and the answer on the Oracle knowledge base. You can also ask a question you
haven’t been able to get resolved previously, and perhaps someone who wasn’t
online when you had the problem will be able to help.
As has been mentioned, the Epitaph creatorbase has a culture all of its own, and
that culture is mainly implicit and changes over time. Some of the more important
implicit rules to follow will be covered here:




It is acceptable to read over the code of in game areas, obviously... this is
where you get a lot of inspiration as to how to solve your own coding
problems. However, if you wish to look over code in development or
playtesting, you should ask the creator in charge of the development if they
mind – most won’t, but it’s nice to be nice. This also applies to wandering
around in PT/development areas... if there are no open invitations to
explore the area then you should ask permission.
The same also applies to code within /w/ directories... it is considered rude
to indiscriminately read, copy or reference to code/files within a creator's
personal directory. It is also considered rude to clone objects from their
directory without permission. Again, if code/objects are within common
circulation within the creatorbase you don't need to worry too much about
this, but it is always polite to make sure.
Peripherally related to this, please beware when you're playing around with
in-game code. It's a natural desire to want to understand the workings of
some of the NPCs and rooms you've experienced as a player, but remember
in the majority of cases actually interacting with these objects is interfering
with something that is in-game. This also applies to cloning unique NPCs
into your workroom - rooms that clone unique NPCs assume no more than
one instance of the NPC is loaded at any one time. If you have a copy of the
NPC in your room, it's possible that you will interfere with the NPC actually
being cloned in the game.
Likewise, some NPCs have stats that save, or interact in various Arcane
Ways with handlers around the game-world. Multiple copies of these NPCs
being in existence at any one time is very likely to cause Interesting
17
Epitaph Online
http://drakkos.co.uk
Problems. Unless you know for certain that cloning another copy of the NPC
is not going to have negative effects on the smooth running of the MUD,
please don't clone them or move them to your workroom. Use the more
command to read the code of the loaded version if you want to see how it
works.
With regards to creator invisibility, it is important you do not pass information
regarding this to players. If you are asked by a player if a particular creator is on
but invisible, then ask the creator in question if they want to speak to that person.
If not, you can tell the player that you are unable to pass on information regarding
a creator's invisibility. This goes equally for Lord/High Lord invisibility and general
creatordom. If you are on a Lord's allow list and a creator or player asks you if
they are online, make sure you ask the Lord if they want to talk to the person.
All creators should set a valid e-mail in their finger info. If you don't do this
and at some time in the future you, for example, disappear without word on some
secret mission, then your domain leader won't be able to mail you and ask what's
going on, and will instead have to just demote you. Please make sure your domain
leader has a means of getting in touch with you outside of the MUD on those
occasions when it is vital they make contact
Domain Hierarchy
As a new creator, you are going to be taking a position in an existing
organizational hierarchy. First of all, you belong to a domain, usually either 'game'
or 'support'. The 'game' domain is responsible for... well, the game. They create
the areas, write the NPCs, and build the quests. More senior creators in the game
domain are also responsible for the architecture of the game - commands, and the
various code files that drive the rest of the MUD. The support domain has a more
ephemeral brief, and covers anything that supports the creators responsible for
the development of the game, such as public relations and the creation of
documentation such as this.
Each domain has a Domain Leader (usually a member of admin), and the Domain
Leader is responsible for the hiring, firing, and general direction of their domain.
Most domain leaders also appoint a number of Domain Deputies, who have some
delegated authority within the domain. The exact amount of authority varies from
domain to domain, but it is safe to say that their word carries significant weight
with your domain leader, or they wouldn’t be deputies at all.
The domains are mostly independent, although cross-domain communication at
the level of domain leaders helps to co-ordinate development. On the whole
though, you are likely to be assigned a project in an existing development as part
of an existing plan – there will be plenty of scope for creativity, but at least
initially your efforts will be directed as to where your domain administration
(leader and deputies) feel they are most useful. If you have particular interest in a
development, you should let your domain administration know – finding your
project should be, as far as is possible, a collaborative effort.
Domain leaders have broad autonomy, but the domain of Admin contains the last
18
Epitaph Online
http://drakkos.co.uk
word on Epitaph. In this domain may be found the ultimate authority, from which
all other authority flows. Within the virtual parameters of Epitaph, their word is
Law. While the admin domain has a leader like other domains, you can think of
the leader of the admin domain as more of a ‘first amongst equals’ than the boss.
Each member of admin will have specific responsibility in which they are the
ultimate arbitrator of the MUD’s direction, but most admin work generally across
the game also.
Running parallel to this domain hierarchy is a system of creator ranks. Your rank
will begin at creator – we do not distinguish between new creators and existing
creators, there is no ‘junior’ creator rank. Once you have proven yourself, which is
at the discretion of your domain leader, you might be promoted to senior creator.
Usually this will not happen before you have at least a year’s contribution, and a
substantial project under your belt. Senior creator, as a position, is largely
honourific and does not carry with it any particular extra responsibilities, although
you do gain a little extra access to the MUD's file system.
Next up the ladder is the rank of Lord. Lords have a history of contribution to the
game and have broad authority to develop game systems and steer the game
direction. With regards to ‘paperwork’, the position of 'lord' does not necessarily
carry with it a management burden. Only those who are listed as being part of the
domain administration will have to push paper around.
Finally, there is the top rank – the High Lords. There are only a few of these, and
they are all members of the Admin domain. High Lords are responsible for
promoting Lords and, on rare occasions, additional High Lords. They're
responsible for setting broad MUD policy, and ensuring that everything
progresses in a way that is harmonious with the overall MUD philosophy.
The last domain we have is the Cemetery domain, and it's kind of a holding area
for people who are creators but don't have a day to day development role on
Epitaph. They are usually ex-Epitaph creators, or creators from other MUDs who
are given honourary developer status here, much like diplomats. Some of these
members may have higher rank than creator, which within this domain is largely
honourific.
Invisibility and You
I have already made some points on the importance of being invisible. Invisibility
though has a few more issues that I’d like to address.
Firstly, there are several levels of invisibility. You as a creator have access to level
one, which makes you invisible to everyone except creators and above. Lords have
access to invisibility level two (also known as lord invisible), which makes them
invisible to everyone except fellow lords and above. High lords have access to
level three (high lord invisible), which makes them invisible to everyone except
fellow High lords.
While you are invisible, people cannot target you and cannot see you on the finger
19
Epitaph Online
http://drakkos.co.uk
output. Additionally, your finger info will not display you as being online. You
have the ability to add exceptions to your invisibility so that your friends can still
interact with you, and that’s done by using your allow command. For example:
allow drakkos
Using the command allow by itself will list your current allow list. To allow an
individual, you use allow <name>. If you want to remove them, then disallow
<name>:
disallow drakkos
There is a certain etiquette to being an invisible creator. It’s considered rude to
send tells or souls to people not on your allow list, for obvious reasons (they have
no way to respond). It is also considered somewhat rude to chat on a talker
channel when you are invisible to the majority of your audience.
Your may find that your domain leader is sometimes inaccessible to you by virtue
of being at a higher level of invisibility – please do not take this as an insult. There
are often additional calls on an individual’s time as a domain leader, and it is
sometimes necessary to make onesself invisible to deal with these issues without
interruption. If you wish to contact a domain leader who is on a higher level of
invisibility, you can always send them a mail.
Invisibility is a tool, and it is obviously not to be abused. Although you cannot be
seen in the MUD while invisible, that doesn’t give you the authority to snoop on
other players. If you are caught doing so, there will be ramifications. Your
invisibility is only an aid to effective coding, not the fulfillment of so many
teenage fantasies.
Your Creating Environment
We're going to get to actual creating soon (ish), but before we do let's spend a
little time talking about how you actually write code for Epitaph.
You have two routes to development available to you. The first is to make use of
the in-mud editors for code. I strongly recommend against this – so strongly, in
fact, I'm not going to mention the possibility again. You have the option available,
but you’re on your own if you want to follow through with it.
The second method is to use an off-line development system. This will consist of
two things:


A decent text editor (note, not a word processor)
A solid FTP client
For maximum ease of use, you want both of these combined into a single package
– if you're willing to splash out a little, I recommend UltraEdit very strongly.
20
Epitaph Online
http://drakkos.co.uk
Another popular choice is EditPlus. For Mac users, TextWrangler is a good choice.
If you prefer to use separate packages, then Notepad++ and Filezilla are free, and
excellent, packages.
Regardless of which solution you go for, the basic system is the same. You write
some code, you upload it to Epitaph, and then you update the in-game file.
Epitaph's file structure may take some getting used to, so it's worth spending a
little time navigating around.
Every creator on Epitaph starts off with a home directory that is accessible in the
/w/ directory. You have full access to write to this directory, and it's most likely
where your first few files are going to go.
The code that belongs to your area domain is found under /d/, in a subdirectory
that matches the name of the domain. If you were a creator in Game for example,
the code that belongs to the Game domain may be found in /d/game/.
You navigate between directories using the cd command – at all times, you have a
working directory, and that's the directory you'll see when you list the files in it
(you can do this with the ls command). The cd command stands for change
directory, and ls stands for list.
Your default directory is going to be /w/your name, and that's the directory you
will log into when you open up your FTP client for the first time.
For the purposes of the rest of this section, we'll assume you're using Ultraedit – if
you're using a different setup, you'll need to figure how how to incorporate all of
the following information.
From your Ultraedit application, go to File on the menu, and down to the
FTP/Telnet option. That'll open up a submenu, of which you want the FTP Account
Manager. Choose 'add account', and enter the details as follows:
Setting
Server
Port
Username
Password
Value
drakkos.co.uk
6001 (if your ISP blocks this port, try 6999)
Your Epitaph creator name here
Your Epitaph creator password here
Commit these changes, then go to File -> FTP/Telnet -> Open from FTP and
'Browse Site'.
If that works, beautiful – you're ready to do some coding. If not, you may be
having connection issues. Go back into the account manager, click on the
'Advanced' tab and check 'Passive Transfers' and 'Use IP Address returned by
server'.
Now, let's make sure you can edit files on the MUD. Double click on 'workroom.c'
and you should get a copy of your workroom file downloaded to your editor.
We're not going to do much to this right now – all we're going to do is put in an
21
Epitaph Online
http://drakkos.co.uk
extra item to look at to make sure our setup is working. In your file you'll see a
comment that says something like this:
/*
*
*
*
The items are the things in the room that you can look at.
Item longs should be proper sentences and shouldn't have \n at the end.
Make sure you have a room item for every noun described in the long
of the room or in the long of another room item. */
This is a comment, it's a bit of the code that is there for you to read – the MUD
doesn't pay any attention to it. Underneath this comment, we're going to add a
little test item – add this, *exactly* as it is written below:
add_item ("textbook", "It's full of knowledge!");
Now, save your file. That commits your changes back to the MUD. You should
always make sure you work from a fresh copy of the file each time you sit down –
you can't be sure that changes haven't been made to the file in your absence.
Your change won't automatically be reflected in the MUD – first you need to
update the file so it loads the newest version. You can do it by updating the path
of the file:
update /w/your_name_here/workroom
Or if you're standing in the room, you can simply:
update here
Assuming you've done this properly, you'll see something like this:
Updated workroom of your_name_here (/w/your_name_here/workroom).
If you haven't done it properly, you'll see lots of red and blue text. If this is what
you encounter, go back to the editor and make sure your addition is exactly as
shown above.
Once you get the updated successful message, then type:
look textbook
All being well, you'll see the description you set for it echoed back to you.
Congratulations, you're now ready to start writing code! We won't talk about that
just yet, there's still more we need to discuss... but when it comes time, you're
ready to roll.
22
Epitaph Online
http://drakkos.co.uk
Creator Commands
So, now we’ve spoken a bit about what’s expected of you as a new creator, let’s
talk about the fun and exciting new powers you have available to you. First of all,
let’s talk about how you move around the game. Your usual navigation commands
work as before, but creators are lazy and so we also have the ability to teleport
ourselves instantly to any other part of the game. We do this using the goto
command, followed by the path to that room.
To go to your workroom, for example, you use the following command:
goto /w/your_name_here/workroom
It’s worthwhile aliasing this under something like ‘home’, so you can always get to
privacy.
Hit return and you’re magically there. If you want to return From Whence You
Came, you use the goback command. You’ll see the path of rooms along the top
of the long description as you walk around – make a note of any location you find
interesting on your travels so that you can whiz around as you desire.
Goto also lets you teleport yourself instantly to another player or creator who is
online. Consider the etiquette of this before you do it – people won’t appreciate
being dropped in on in their own personal workspace without some warning. If
someone is in a public place, then by all means feel free to use them as a shortcut
to finding the path to a particular room, for example:
goto drakkos
That’s just an example of the syntax. Seriously, don’t use me as your Goto Guy
(teehee), or I will devise some suitably extravagant punishment and mete it out to
you over the course of your entire time on Epitaph.
Ahem.
You can also bring other people to you with the trans command, for example:
trans drakkos
You should exercise caution with this. I’m sure it goes without saying that you
shouldn’t abuse this command to pull people around the game, but a special case
might exist in terms of bringing players to your own private workroom. I cannot
give blanket advice on this as the policy varies from domain leader to domain
leader – check with them first before you do it.
It’s not necessarily a good idea in any case – it carries with it connotations of
privilege and favouritism, and gets in the way of the normal functioning of the
game. While in a creator workroom, players are free from PK retaliation or in-game
punishments, and this can breed resentment.
23
Epitaph Online
http://drakkos.co.uk
As a further note, when you are going to trans someone remember your manners
- let them know you want to trans them, wait until they tell you it's okay, and
respect their wishes if they do not want transported. The obvious exception to
this is in cases where Creator Justice is being applied, but that's not something
with which you're going to be involved.
While the policy on this will vary between domains, as a universal rule you do not
trans players into development or play-testing areas without the express
permission of the relevant domain lord. Your own areas in your /w/ directory are
the sole possible exception to when you may bring players into the creator
environment. When they are leaving, they should be returned to the exact same
room they left with the following command:
trans drakkos back
Remember, check first with your domain leader to see whether or not they permit
this.
You no longer have the need for Mortal Possessions, but you can create copies of
objects to which you know the path using the cl command (short for clone).
You’ve already done that with your bulletin board of course – if you want to know
the path to other items, you can use the pathof command if you have them
handy. The pathof command may give you a response with a hash sign and some
numbers at the end – ignore those numbers for now, they’re not relevant for the
time being.
If you want a copy of an item that exists in the game, you can make use of the
request command. Check out the syntax of the command for details, but you can
list items in certain categories, such as:
request list clothes
You can also search within a category by providing a search string. If you want to
list only those elements in the list of clothes that have the word ‘a-line’ in their
description, you’d use this:
request list clothes a-line
When you find something you want, you request it by its name (replacing
underscores with spaces)
request thin cloth nappy
At that point, it’ll appear in your inventory – or, if you can’t pick it up because it’s
too heavy, it’ll appear in your current room.
If you want to check the inventory of another player, you can use the inv
command. Be careful with this, though – it can be considered a breach of privacy:
inv drakkos
24
Epitaph Online
http://drakkos.co.uk
If you want to make a copy of something another creator or player has in their
inventory, you can use the dup (duplicate) command... for example:
dup hilarious scottish bonnet in drakkos
Assuming there is an item matching the name ‘hilarious scottish bonnet’, you’ll
get a copy of that item in your own inventory. The usual rules apply to this though
– don’t abuse it. Sometimes people have deeply personal items in their inventory
(such as items with personal inscriptions), and it is considered a Grave Infraction
to violate that privacy.
You can ‘dup’ things in your own inventory, and also ‘dup’ generic NPCs (they will
be cloned into your environment, rather than your inventory). You don’t even need
to be in the same room, they just need to loaded somewhere on the MUD. For
example:
dup zombie
Before you dup things, it's worthwhile to get the path of the item (using the
pathof command) to make sure you're getting the thing you actually want. When
you 'dup black shirt in drakkos', you may find that you didn't want a peep-hole
black leather gimp shirt - you actually wanted just the normal black shirt that
normal people wear. Pathof will let you see where the item resides, and will also
help you learn where you need to look for particular things.
As a general rule, you have access to read any of the files in your domain (except
for those in the secure directory), and usually in other domains too. You can do
this using the more command if you know the path, like so:
more /w/your_name_here/workroom.c
If you are standing in a room, you can get the code by using ‘here’ in place of a
path:
more here
If you want the code of an item in your possession, or an NPC that is loaded on
the MUD, you can refer to it by name:
more gun
If you want to get rid of something, you use the dest command (destruct):
dest sword
It’s possible to dest players and creators, but don’t do it unless you have a valid
reason (as a hint, if you don’t know if you have a valid reason, you don’t have
one).
25
Epitaph Online
http://drakkos.co.uk
Creator commands often make use of a powerful matching system called
'wiz_present' - this has a helpfile of its own if you want to check it out. It's a much
more powerful matching system than you will have been used to as a player where
you could usually only target things in the same room (with some notable
exceptions). Wiz_present allows you to match things according to far more
complex criteria, and is MUD-wide. You've already seen some of this in action with
the commands above, but there are some useful expansions.
Would you like to look at the code of the room in which I'm standing? You can
refer to the container of an object by enclosing its name in brackets:
more (drakkos)
That gets you the code of the room I'm in, rather than the code for me myself.
If I want to get access to something in a container or in another player, I can refer
to things as such:
pathof sword in drakkos
You can even chain these together:
pathof sword in (drakkos)
There are more complicated things you can do with wiz_present, and these will
come with time.
That should be enough in the short term to get you navigating around and setting
yourself up with some items – the next thing we’ll talk about are your two most
powerful commands.
The first of these is call and is used to make things happen in objects. You’ll
need to know a bit more about coding before you can use this effectively, but for
now let’s do a simple example and give ourselves a few levels of a skill:
call add_skill_level ("combat", 100) me
The first part is the name of the method we’re calling (add_skill_level), the bit
between the brackets are the parameters (the skill we want to increase, and how
many levels by which we want to increase it), and the last bit is the object on
which we want to call the method (in this case, ourselves). Again, a word of
warning – don’t use this on players unless you know you’re doing the right thing.
We Are Always Watching.
You don’t need to know much about call at the moment, just know it’s a powerful
tool we’ll talk about in good time.
The second command is exec – it’s a more powerful tool than call, and
correspondingly more complex. It lets you execute a bit of code ‘on the fly’
without having to build it into an object. Once again, don’t abuse this – people are
paying attention.
26
Epitaph Online
http://drakkos.co.uk
There are dozens of other commands you have available as a creator – most of
them have helpfiles to go with them. If you’re interested to see what’s available,
try this:
commands
This command will reveal a list of the commands you have available, and one of
the categories of command will be 'creator'. You'll see something like this:
Your Creator commands:
addalt, addevent, altonline, autodoc, autodocadd, autodocrm, ban, blackball,
bugreps, build, callouts, cheaters, check, cloner, clubs, comment, compare,
compare2, coords, cos, countshadows, creator, cstat, ctotd, dbxwhere, debug,
define, delalt, delevent, denied, destruct, deutf8, dirs, distribution, dump,
dupdate, effects, errors, family, fds, fetch, findcorpse, findredef, fixed,
flush, forwards, ftpwho, gag, gna, goto, goback, gopop, gopush, guildlist,
hcode, hcre, head, hliaison, home, host, hours, housing, hupdate, iemote,
ilocate, imc2, imc2mudlist, inheritance, inv, jumps, localcmd, maestro, mail,
malloc, materials, mediawiki, memstat, meminfo, mkdir, more, mobs, mudinfo,
multiplayer, mv, netstat, netdups, newest, nicetime, nslookup, object, ogrep,
oldest, online, osql, pathof, people, permit, playerinfo, pquests, prof,
project, pstat, ptesters, pwd, qpeople, qwho, randomname, reboot, rehash,
repcomment, request, review, rm, rmdir, sar, schematics, screen, set, setmin,
setmmin, setmmout, setmout, shadows, showhelp, shutdown, skillcheck, snetstat,
snoop, snoopers, sockets, soulinfo, spawn, spell, sql, sqlcmd, stat, stats,
status, suspend, taskmapper, tcheck, tell!, template, terms, terrains,
testchar, todo, trans, trace, ungag, unsuspend, unventriloquise, upgrade,
usage, wgoto, whereis and which
All of those are available to you (sometimes with restrictions), just be careful that
you know what they do before you use them. If you use MXP, clicking on the
underlined command will give you its helpfile.
As a final note, you’re going to die quite a lot – some of it is general horseplay
amongst creators, sometimes it’s for testing purposes, and sometimes it’s just by
accident. It probably goes without saying that death for a creator is Not a Big
Deal.
Oh, and as an extra perk – you have access to mail wherever you are in the game.
Just use the mail command and you’ll find yourself inside the Epitaph mailer.
Finally, it's easier to be a creator when you don't need to deal with the limitations
of normal players. There are four options under 'creator' in your options
command that are especially useful:
27
Epitaph Online
http://drakkos.co.uk
Option
suppress_aggressive
Effect
NPCs will no longer be
aggressive towards you.
suppress_maestro
Maestro will no longer pick
you out for his tender
ministrations.
suppress_temperature
No matter where you are,
your temperature will be
'comfortable'
You will no longer
experience gains or decays
in your wellbeing.
wellbeing_freeze
I would advise switching all of these on, but the choice is yours.
Alts and You
Many creators find the role of creating gives them a satisfaction that playing never
did, but it's not a good idea to let the development part of your life on Epitaph
dominate entirely. You are allowed to continue playing with an alternate character
– indeed, it should be encouraged lest you start to become out of touch with how
the game actually works. As usual though, there are some restrictions on this.
First, your player character should not participate in any player-run element of the
game. Your loyalty, first and foremost, is to being a creator. Even when online as a
player, that loyalty is paramount.
As far as possible, you should play the game as a player – that means no sneaking
looks at the code while playing. Obviously you may be more familiar with certain
bits of codebase than others, and you can't simply forget what you already know.
Try to make an effort not to turn to the code whenever you encounter a challenge
– instead, consider how the behaviour of the MUD could be improved and make a
suggestion. If you need to rely on the code, then consider how difficult it would
be for a player without your privileged access.
It's natural that your first thoughts as a creator will be flavoured by your
experience as a player – that will go away with time. Try to take a broader view
when playing, and see things from the perspectives of game balance rather than
from the narrower view of your player character.
On the other hand, your perspective as a player is important too – you'll have your
own views on what's important to the game, what you enjoyed, and what you
didn't. That perspective is valuable - it just has to be tempered with concerns for
game balance.
28
Epitaph Online
http://drakkos.co.uk
You are also permitted a test character to help you debug code. As a convention,
your test character should have an obvious name, such as <your_name>test, or
your name backwards. Once you've created your test character, you can use the
'testchar' command to add the necessary properties to him/her:
testchar draktest on
Your test character can be logged on at the same time as your creator character.
Indeed, that's the only time they should be logged on. You can do anything you
like to your test character – change their skills, give them vast amounts of money,
kill them on a whim. What you can't do is play the game with your test character –
any attempt to do so will be treated as an attempt to cheat, and you'll end up
being dismissed
Epitaph Development Cycle
Development on Epitaph tends to follow its own idiosyncratic development cycle.
Again, each domain does things differently, and the approach a domain takes will
vary from project to project. There’s a general structure to the process that can
serve as a useful guide to what things actually mean.
To begin with, there is a plan. That plan may be very detailed in terms of what
code is to be incorporated, or it may be very vague (let’s build a city!). Usually the
plan is part of a larger ‘master plan’ that incorporates the Domain Leader’s vision
for their domain. The plan may not even be written down anywhere, and may exist
in just the Domain Leader’s head. If you’re lucky, the plan will be documented
somewhere so you can see the big picture.
The Plan needs developers – that’s where you come in. Your domain
administration will likely assign you to a project (perhaps with your input as to
your interests, perhaps not) and give you a broad remit for what it is you are to
do.
You then work through this project, writing the code that goes with it. Although
you will probably have to get specific authorization for certain developments
(such as quests you want to write and add), on the whole you usually get pretty
free reign to do what you want, within reason.
It’s likely that your domain administration will check up on your progress from
time to time, but the responsibility for getting everything done falls to you. A lot
of people find this hurdle difficult to clear, so if you feel as if you are falling
behind on this score you should contact some more experienced creators for
advice and guidance. It’s easy to feel that you’re all alone in your development,
but that’s not true – you have your fellow domain creators to call upon, and by the
time you’re developing your first real project you’ll have gotten a better feel for
the culture of creatordom and be more comfortable in participating fully in the
social side of development.
Eventually, your project will be completed, and then it’s on to your domain
administration for approval – the formality of this as a process will depend on how
29
Epitaph Online
http://drakkos.co.uk
active your domain administration has been in following your progress.
Once approval has been given, the next stage for your project is playtesting.
Whether it can be playtested right away is going to depend on where your project
fits into the larger plan of the domain – if it’s part of a larger project, it may have
to wait. If it’s largely contained, you may be able to get it playtested instantly.
Once your project has left playtesting, there are three usual destinations – one is
back into development if you and your domain administration feel it requires
more creator attention. The second is into limbo, where the implementation of
your project in the game proper is dependant on other factors (like other projects
being complete). The third, and happiest outcome, is into the game for players to
enjoy.
At this point, you can wallow in the admiration and praise of your satisfied
players, and consider what’s next. Of course, it’s never all admiration and praise,
you just need to realize that no matter what you do, you can’t keep everyone
happy. Some people live to complain.
There is a tool available to help you keep your domain administration informed as
to where you are with your projects – it’s the Epitaph Project Tracker, available
from the creator section of Epitaph’s web-pages. The use of this tool may or may
not be mandated by your domain administration, but every project going into
playtesting needs to be recorded with this handler. Check with your domain
leader to see what the specific domain policies are regarding this.
You also have a secondary way in which to document your project, and that’s
through the creator Wiki, also linked from the Epitaph creator web-page. You
should spend some time coming to grips with how to use it, and contribute to
your own domain Wiki especially. Depending on how heavily used the Wiki is
within your own domain, you may find it full of interesting plans that will help you
gain a wider perspective of what’s going on in your domain. It’s also a good way
to get people interested in what you’re doing – although not everyone reads the
Wiki, much less contributes, there are enough Compulsive Wiki Watchers that your
words won’t go unread.
I personally am a huge fan of the Wiki, and of Wikis in general, so if you want to
talk Wiki with me, please do so!
Conclusion
What you will have undoubtedly have taken away from this document is an sense
of the responsibilities you have undertaken – it’s okay to feel overwhelmed,
there’s a lot that you need to learn, and you may feel as if you don’t have a lot of
time in which to do it. There’s time, though – and there are people on hand to
help you every step along the way.
We haven’t spoken at all about coding yet – that’s still to come. At the moment,
we just want you to spend some time thinking about how this new and interesting
step of your experience on Epitaph is going to work. Spend some time snooping
around – get a feel for what has come before, and what’s planned for the future.
30
Epitaph Online
http://drakkos.co.uk
You’re part of a large and interesting team of various levels of activity – we’re all
in this together, and regardless of how much we may bicker and argue, each and
every one of us wants what’s best for the MUD.
I’d also hope that what you’re taking away from this document is more than just a
sense of foreboding, but also a sense of excitement. It’s a lot of fun being a
creator, and a lot of the Dire Warnings in this document are to ensure that you
don’t fall foul of some of the more common mistakes. We’d like you to enjoy your
time as a creator, and that’s hard to do if you’re always afraid of what you’re
doing.
Remember that the members of your domain administration are not just there to
ensure that you’re working – they’re there to guide you too. Look to them for
support, as well as to your fellow domain creators and those of us in Learning.
There’s plenty of support to be had, you just need to be willing to look for it.
Of course, that doesn’t take the overall responsibility for your success off of your
own shoulders – we can only take you so far, the rest is up to you!
31
Epitaph Online
http://drakkos.co.uk
Section One
Introductory LPC
32
Epitaph Online
http://drakkos.co.uk
Introductory LPC with the Epitaph lib
Introduction
In this set of introductory creator material, we are going to talk about the way in
which the gameworld of Epitaph MUD is constructed. This is a book about coding,
but don't worry about it - we're going to introduce the topic as gently as we
possibly can. New topics will be introduced only when they help us make our code
better and more interesting. You will find all of the source code that goes along
with this text available in the /d/support/deadville/ directory. It is broken up
into chapters, so you can see exactly what your code should look like at each
stage of development. You can goto the room 'access_point' within this directory
to explore all of these versions and see the differences between them.
The Learning Scenario
Over the course of this text, we'll be building an area – exactly like the kind you
have experienced in the game itself. We're going to create rooms, NPCs, items,
and shops. We're going to stop short of adding unique functionality or quests,
because that's a part of the follow-up creator LPC section. By the end of this
section you should be comfortable with the basic building blocks of LPC. Along
the way you'll even learn a bit about code, but only a bit. Being an Epitaph creator
involves working with code, but you don't need to be a coder to be a creator.
Enough to get by will take you far, although it's always good to learn more if you
can.
Our learning scenario is the development of the village of Deadville. A fully
completed version of this may be found in the learning domain under
/d/support/deadville/mapping_it_out/. I would like you though to construct this
step by step in time with the teaching material – you only get a fraction of the
benefit from reading completed code that you get from writing the code as you go
along.
Deadville is a simple village, with basic facilities. It also doesn't contain much in
the way of descriptions - if you want to see what good descriptions look like, take
a look at some of your favourite game areas. Our scenario is for teaching you how
to put an area together. It is assumed that you'll be able to write the text
yourself. Some guidance on the topic of writing descriptions may be found in the
Being A Better Creator section. What we're interested in here is the structure of
the area, not how it looks.
We're going to approach this functionality according to a development technique
called incremental development. I heartily recommend this for beginners
because it greatly simplifies the job of building complex programming structures.
This is a process of beginning with something very simple until you have the very
33
Epitaph Online
http://drakkos.co.uk
basic framework fitting together. Once you have this skeleton of code, you can
start adding features to it a little bit at a time. In this way, you avoid becoming
bogged down in the overwhelming complexity of a project – you get to see it grow
all around you from the most humble beginnings to a complete and fully featured
area. To put this in context, we start with a single room that does nothing
exciting. We then add a second room and connect these two skeleton rooms
together. Then we add in another, and another, until we have a whole framework
of a village ready for us to fill in the blanks. What we don't do is create the whole
framework to start with and then become flummoxed when it doesn't work.
Incremental development is an exciting and satisfying process, and one with
which you are going to become entirely familiar as you work your way through
this material. You can see your developments shaping up in a very tangible way,
and that invites further development. Trying to do everything to start with can be
a soul-crushing way of developing a project.
The Structure of the Game
Everything on Epitaph MUD is something called an object. Obejcts are quite a
complex topic in themselves, so we're only going to scratch the surface of this for
now. Every room is an object, every item is an object, and every player is an
object. Confusingly, object doesn't mean that it's necessarily something you can
touch or interact with in the game. Your combat commands for example are also
objects. It's not an easy thing to get into your mind to start with, but it'll become
easier with time.
In programming terms, an object is a structure that contains some data, and
instructions for acting on that data. Some objects also take on the properties of
other objects through a mechanism called inheritance. This is the same general
biological principle you'll be familiar with in real life, only cast in programming
terms. In real life, a cat inherits the properties of being a mammal, which in turn
inherits the properties of being an animal. We'll see this mechanism in action from
the very first code we write - our object inherits the properties of being a room.
Most of the code you need to work with on Epitaph has already been written for
you – you just need to tell the MUD what things you want to happen, and when.
Most of the things you'll want to do, at least in the short term, are available for
you. When you want to get a little more adventurous, then you'll be looking at
moving onto the more advanced creator material.
The MUD itself is built on three separate structures. The first of these is the
driver, which is known as FluffOS. You won't need to worry about that just now.
The second part is the mudlib, and it defines all of the code on which the rest of
the game - the domain code - is built. It is domain code that you will be building
in the short term, and that's all the code you'll find underneath the /d/ directory.
The mudlib is everything else, including code that will Scar and Terrify you if you
read it too early.
As a basic guide to the relationship between the mudlib and the driver, it can be
34
Epitaph Online
http://drakkos.co.uk
summed up like this – the mudlib knows what to do, but doesn't know how to do
it, and the driver knows how to do it, but doesn't know what to do. Domain code
includes all of the areas in which you adventure as a player, all the quests you
have encountered (and the ones you haven't), and all the NPCs you've slain.
When an object is made available in the game, it must first be loaded. At that
point, it becomes a master object. Many objects remain as master objects, which
means only one of them is ever in place in the game – rooms and unique NPCs are
good examples of these.
Some objects are clones, and that means they are copies of the master object.
Generic, common or garden NPCs and items are examples of cloned objects.
When we need a new pirate for example, we just take a copy of the master object
and put that copy in the game. That copy is a clone of the master object.
In order for an object to load, we use the update command. If our code is all
Present and Correct, the object will load. We'll look at this more in the upcoming
material. If an object has been incorrectly coded, you will be presented with lines
of red text (syntax errors) and blue text (warnings). We call these compile time
errors - every syntax error in your code must be fixed before an object will load,
but it will load if warnings are present. Fix them anyway - they don't break
anything (usually), but they can make your code behave in unexpected or
inefficient ways. The process of writing code, updating an object, and then fixing
the syntax errors will become second nature to you before too long.
Programming as a Concept
Programming is a difficult task, unlikely almost anything you may have learned
before – it's like learning how to do mathematics in a foreign language – a
confusingly new syntax combined with a formally exact vocabulary. When you
learn how to speak a different language, you can rely on your interlocutor to be
able to divine meaning even when you make mistakes – a computer cannot do
that. And worse, it does exactly what you tell it to do – that sounds great in
theory, but it can be hugely problematic in reality.
Computers don't understand much – at their basest levels, they are machines for
manipulating electrical impulses. At the lowest level, these are interpreted by the
computers as a collection of ones and zeroes (a numerical system called binary,
but you don't need to know anything about that just yet).
However, we don't want to write our programs as ones and zeroes – it would take
a long time, require a lot of tedious busy work, and generally be a massive drag.
The first programmers however did exactly that, they wrote their programs in the
language of the computer itself - machine code.
As time has gone by, it has become simpler to write programs for computers – in
order to manage the complexity of programming as a task, computer people
developed successively more abstracted programming languages, and introduced
the idea of compilation. Rather than writing in machine code, programmers could
35
Epitaph Online
http://drakkos.co.uk
write in an easier, simpler system and then make the computer itself convert that
simpler representation into machine code. This conversion process is called
compilation.
The first attempts to do this were through a language called assembler, which is
only a little bit better than machine code directly. Assembler is known as a low
level language because it's not all that far removed from the low level grunts and
snorts of machine code.
As the years have gone on, more and more programming languages were
developed. Successively they abstracted writing programs into more and more
English-like syntax. Eventually a language named C came along, and
revolutionized how people thought about programs. C was so popular that most
of the successful programming languages of modern times use its style of writing
code in one way or another – C#, C++ and Java especially. These are known as
high level languages because of the level of abstraction involved. Computers do
not understand the code written in these languages, but the process of
compilation converts the code into something the computer can understand.
On Epitaph, we use a language called LPC which was created by Lars Pensjoe.
While it has very similar syntax to all of the above named languages, it has a host
of unusual and useful features. We'll get to what those features are as we go
through the material.
Programming is essentially the task of taking a list of instructions, and writing
them in such a way that the computer can follow your instructions to complete a
task. To give a simple, real world example – imagine the task of making
scrambled eggs. Imagine too that you have an obedient, but simple-minded
kitchen assistant, and you would like them to make some scrambled eggs for you.
They do everything you tell them to do, and they do it exactly as you tell them. It
sounds like an ideal situation, but consider the following instructions:
1.
2.
3.
4.
5.
6.
7.
8.
Get two eggs from the fridge
Get some milk from the fridge
Get some butter from the fridge
Break the eggs into a jug
Whisk the eggs until they are well mixed.
Heat some milk and some butter in a pot.
Add the egg mixture.
Stir until scrambled eggs are made.
The instructions are pretty simple, but your Simple Minded Assistant falters at the
first instruction. He, or she, stands vacantly at the fridge, pawing ineffectually at
its surface. Cursing, you amend your instructions a little:
1.
2.
3.
4.
5.
Open the fridge, you insensitive clod.
Get two eggs from the fridge
Get some milk from the fridge
Get some butter from the fridge
Close the fridge
36
Epitaph Online
http://drakkos.co.uk
6. Break the eggs into a jug
7. Whisk the eggs until they are well mixed.
8. Heat some milk and some butter in a pot.
9. Add the egg mixture.
10.
Stir until scrambled eggs are made.
You set your Simple Minded Assistant back to work – they open the fridge. They
get two eggs out... success! Then they stand there with a confused look on their
face. "What does 'some' mean?", they ask. You curse once more:
1. Open the fridge, you insensitive clod.
2. Get two eggs from the fridge
3. Get two tablespoons of milk from the fridge
4. Get a knob of butter from the fridge
5. Close the fridge.
6. Break the eggs into a jug
7. Whisk the eggs until they are well mixed.
8. Heat some milk and some butter in a pot.
9. Add the egg mixture.
10.
Stir until scrambled eggs are made.
And then you start them on the task. "Uh, the fridge is already open", they say,
and then the metaphysical complexity of the situation causes them to shut down
until you amend the program a little further:
1. If the fridge door is shut, open the fridge, you insensitive clod.
2. Get two eggs from the fridge
3. Get two tablespoons of milk from the fridge
4. Get a knob of butter from the fridge
5. Close the fridge.
6. Break the eggs into a jug
7. Whisk the eggs until they are well mixed.
8. Heat some milk and some butter in a pot.
9. Add the egg mixture.
10.
Stir until scrambled eggs are made.
And so on, until you have a completely correct, entirely unambiguous list of
instructions that any dolt could follow.
This is the essence of what programming is all about. The only difference is, your
instructions are given in LPC, and the Simple Minded Assistant is the MUD itself.
Trust me, it's even more stupid than your kitchen assistant.
This is a much trickier task that I'm making out here – and the only thing that
makes it easier is practice - lots and lots of practice. That leads us to the next
section of this chapter...
37
Epitaph Online
http://drakkos.co.uk
It's All Down To You
Whether you are learning to code on Epitaph, or in a more formal setting like a
university course, the basic proviso is the same – nobody can teach you how to
code. Sure, people can point you in the right direction and provide you with ample
reading and reference material. What they can't do is make you understand,
because you don't learn it by having someone explain it to you. The only way you
learn how to code is by practicing.
But more than that – the only way you really learn is by trying and making
mistakes. If you get it right the first time, that's great – but you would have
learned more by getting it wrong and working out what why your code didn't
function the way you hoped. I guess what I'm saying is – don't get discouraged.
You are going to make mistakes, and sometimes you're not going to know how to
fix them. That's what the your domain leader and fellow domain creators are there
to help you with – but please do try to work it out first before you ask for help.
Not because they don't want to help you, because they do – it's just you'll learn
more if you work out the answer yourself.
You've got to be willing to persevere in this – the easiest way to fail is to not try.
We can support you in your crusade to be a Good Creator, but only if you meet us
half way. Remember, your domain lord thinks you can make it as a creator, or you
wouldn't have been hired in the first place.
Conclusion
In this chapter we've gone over the very, very basics of what programming actually
involves, and how code on Epitaph is put together. All of this is useful knowledge,
but it's not coding yet. That comes in the next chapter when we look at building
our very first room. Are you excited? Touch your nose if you are!
38
Epitaph Online
http://drakkos.co.uk
My First Room
Introduction
Right, let's get started for real then – we've got a lot to learn and you've already
read so much without getting your hands on a bit of code. Now we're going to
change that by creating our very first room and filling it with some of the simpler
elements available to a novice creator. This is a big step - even the largest of our
area domains started off at one point with a single room.
Once we've gotten that first achievement under our belt, we'll talk a little bit about
structuring an area so that we don't get hopelessly bogged down in the minutiae.
We're going to look at the basic skeleton of Deadville, and how many rooms we're
going to develop – having a plan always makes it easier to schedule your time and
effort. You should always sit down and sketch out what you are hoping to
accomplish with an area before you start writing any code. The easiest time to
make corrections is at the conception stage.
All the examples from this chapter can be found in
/d/support/deadville/my_first_room/ - all the paths in there will be based
on /d/support rather than /w/your_name, because – well, that's where it lives!
Your Own Deadville
The assumption throughout this material is that you are going to be building this
village alongside the tutorials. In order to link up what's done in these documents
to what you're doing on the MUD, we need to calibrate! That's just a fancy-pants
way of saying that we're all using the same directory names. First, you create a
directory in your /w/ drive called Deadville. All subsequent directories will be
created in here. You don't need one for each chapter as is present in
/d/support/learning/deadville - you should have one copy of this that grows and
changes as you develop it further.
mkdir /w/your_name_here/deadville
Once you've gotten the directory, you want a subdirectory in there called rooms:
mkdir /w/your_name_here/deadville/rooms/
From this point on, this chapter will assume this is where you are uploading your
files. Make sure it is, because otherwise you're going to have lots of problems
ensuring that the paths you use map up to the paths we use in here.
39
Epitaph Online
http://drakkos.co.uk
The Basic Template
First of all, let's set up our basic template for creating a room. Create a new file in
your development environment (See Welcome To The Creatorbase for a discussion
on that if you haven't already done this), and type the following into your editor:
inherit STD_OUTSIDE;
void setup() {
set_short ("simple village road");
add_property ("determinate", "a ");
set_long ("This is a simple road, leading to a simple village. There "
"is very little of an excitement about it, except that it represents "
"your first steps to becoming a creator!\n");
set_light (100);
}
Save this file in your /rooms/ subdirectory, and call it street_01.c.
Now, in the last chapter we spoke a bit about loading objects – this room doesn't
yet exist on the MUD, even though it exists on the disk-drive. To see if it loads, we
use the update command:
update /w/your_name_here/deadville/rooms/street_01.c
When you hit return, you should see a message something like this:
Updated simple village road (/w/your_name_here/deadville/rooms/street_01)
If it doesn't, then something has gone wrong with what you've typed into the
editor. Look very closely at the example code above and see where your code
differs – it should be identical (if it is identical, it will work). If all else fails, simply
copy and paste the code above into your editor - don't get bogged down in the
errors just yet. You'll get your fair share of those later when you try to do things
without a safety net.
Once your room has loaded, you need to move to it with the goto command:
goto /w/your_name_here/deadville/rooms/street_01
If all has gone well, you'll see the following:
/w/your_name_here/deadville/rooms/street_01 (unset)
You can hear: Nothing.
In the distance you can see: Nothing.
This is a simple road, leading to a simple village. There is very
little of an excitement about it, except that it represents
your first steps to becoming a creator!
40
Epitaph Online
http://drakkos.co.uk
It is a freezing cold winter night with heavy sleet and thick
black clouds.
There are no obvious exits.
Congratulations! You've just taken your first step into a larger world!
Now, let’s talk about what all of that actually means.
Let's take a look at what you've done and explain what each of the different bits
mean. Most of them should be fairly obvious, but it's important that you know the
bits you don't need to worry about:
// This the object that defines all of the functionality of which we're
// going to make use. All of the code that we use in setup is defined in
// this object. In this case, it's an outside room... so it gets all the
// usual weather strings and chats.
inherit STD_OUTSIDE;
// This is a function (we'll come back to this topic later). All you need
// to know for now that the code in setup gets executed automatically by
// the MUD when your room is loaded.
void setup() {
// The short description is what you see when you 'glance' in a room.
set_short ("simple village road");
// The determinate is what gets appended to the short description
// sometimes. In this case, this room becomes 'a simple village road'.
// If we wanted it to be 'the simple village road', we'd change
// the determinate here to 'the'.
add_property ("determinate", "a ");
// This is the long, wordy description of the room that you see when you
// have the MUD output on verbose.
set_long ("This is a simple road, leading to a simple village. There "
"is very little of an excitement about it, except that it represents "
"your first steps to becoming a creator!\n");
// This sets the current light level for the room. 100 equates
// to 'bright sunlight'
set_light (100);
}
The lines that start with the // symbol are comments. The MUD completely
ignores these when it is loading your room – they are there purely for humans
(and other creators) to read. It's good practice to comment your code properly,
but that's a topic for a later day. You'll read more about commenting in Working
With Others.
At the simplest level, all of the code that you incorporate into your rooms goes
into the setup of a room. You need to pay particular attention here to the curly
braces – it's easy to get these wrong when you first start – all of your code goes
between the opening curly brace {, and the closing curly brace }.
That's the basic structure of each room we're going to create – you may want to
copy and paste this somewhere you have easy access to so you can use it for each
room you start.
41
Epitaph Online
http://drakkos.co.uk
My Room Sucks
It sure does, little buddy! Your room is dull, boring, uninteresting. It’s generally
lame and horrible, and something you couldn’t show anyone with a sense of
pride. That’s okay, for now all we want is to ensure that it loads – let's take one
step at a time.
Let's look at adding some more interesting things to your room. The first thing
you'll notice is that you can't look at anything... 'look village' gives a rather
disheartening response:
Cannot find "village", no match.
Let's start off simply and add some things for people to look at. We do this by
using add_item:
add_item ("village", "The village looks untouched by the rampaging dead.");
This goes into the setup of your room – as a matter of convention, it goes after
you've set the long description for the room. Update your room, and 'look village'.
Your response will be much more encouraging:
The village looks untouched by the rampaging dead.
Looking at the road will give us the 'no match' message, so let's add an item for
that too:
add_item ("road", "You're standing on it, you dolt!");
You can add as many items as you feel is appropriate. In general, more is better –
but at the very least every noun in the main description, and every noun in the
add_item descriptions should have an add_item of its own.
But wait! We're not quite safe here, because the long description refers to a
'simple village', and what happens when we look at the simple village? Yep, 'no
match'.
We get around that by being more specific in our add_item definitions:
add_item ("simple village", "The village looks untouched by the "
"rampaging dead.");
add_item ("simple road", "You're standing on it, you dolt!");
Add_item is quite clever – by doing this, we get the root noun (village) plus any
adjectives we define, in any order in which we want them... looking at 'village'
gives us the description, as does look at 'simple village'. If we had an add_item for
'shiny red simple village', we'd be able to look at 'shiny village', 'red village', 'shiny
simple village' and any combination thereof.
We'll return to add_item intermittently as we go along – it's much more powerful
than a lot of people realize, and we'll have good cause to make use of that power
42
Epitaph Online
http://drakkos.co.uk
in later chapters.
It's a little too quiet...
Most rooms in the game have a little more life to them than your first attempt.
They have NPCs (we'll get to those), and they have chats that appear at
semirandom intervals. These chats are a big part of making a room feel alive, so
let's add some of those.
Again, in your setup function, add the following code. It's slightly complicated, so
you may want to copy and paste it:
room_chat
({
"It's
"It's
"It's
})
}));
( ({ 120, 240,
quiet... a little too quiet.",
simple... a little too simple.",
exciting learning to develop rooms!",
Yikes! What's with all the brackets and braces? Let's pretend we don't need to
worry about that just now, because we don't – it'll all come clear in later
documents.
Instead, we shall focus on the salient details here – the first are the two numbers.
These are pretty straightforward – the first is the minimum time that must pass
before a chat is sent to the room, and the second is the longest period of time
that will pass before a chat.
The strings of text are the actual chats that get echoed to the room – you can
have as many of these you like, inserted into the list. Each needs to be separated
by a comma:
room_chat
({
"It's
"It's
"This
"It's
})
}));
( ({ 120, 240,
quiet... a little too quiet.",
simple... a little too simple.",
is a chat in a list!",
exciting learning to develop rooms!",
Note the quotation marks – we'll talk about why these are necessary in a later
chapter of the material. Just take my word for it now that they're needed. The only
thing missing from our most basic of rooms is an exit to another room. For that,
we actually need a second room to move to!
A Second Room
Let's start with out basic template as before, changing the long description a little
to reflect the fact this is somewhere different. We'll call this one street_02.c, so
43
Epitaph Online
http://drakkos.co.uk
create a new file and save it to the MUD:
inherit STD_OUTSIDE;
void setup() {
set_short ("simple village road");
add_property ("determinate", "a ");
set_long ("This is a little way along the path to the village of "
"Deadville. The path continues a little towards the northeast "
"towards the rickety buildings of the market square. To the "
"southwest, the path wends away from what passes as civilization "
"in this learning scenario.\n");
set_light (100);
}
We now want to link these two rooms up – this is done using add_exit. Add_exit
requires three pieces of information before it can make up a connection to a room
– the first of these is in what direction the exit lies. The second is what path the
next room may be found at. The third is the type of exit (is it a door, a path, or
stairs?). Our long description above shows us what our direction is going to be –
our first room lies off to the southwest of this room. It's going to be an exit of
type 'path':
add_exit ("southwest", "/w/your_name_here/deadville/rooms/street_01", "path");
This goes into street_02.c. Street_01.c gets its own exit leading to our second
room:
add_exit ("northeast", "/w/your_name_here/deadville/rooms/street_02", "path");
Update both of these rooms, and you'll be able to walk between them. That is
pretty nifty, I'm sure you'll agree!
One thing to bear in mind is that while we've put the full path of these rooms in
our add_exits, that's not how we should do it 'In Anger' because it makes it really
difficult to move areas around as you need them. Later, we'll talk about the
importance of properly structuring the architecture of your code so that it's easy
to move things around various directories.
You'll note that our second room is missing add_items and room_chats – adding
these is left as an exercise for the interested reader. You should be able to do that
yourself referring to the notes for how it is done in the first room.
Our Overall Plan
So, that's two simple rooms linked up. It's worth making sure we have an actual
plan here so that we know how each room fits into the whole. You may have your
own favourite mechanisms for designing a map for a new area, I personally favour
the humble text-file. This is the map of the area we're going to develop together:
44
Epitaph Online
http://drakkos.co.uk
A B
| |
6-7
| |
3-4-C
/
2
/
1
We're using a number here for each outside room, and a letter for each inside
room. We thus need a key so we know which number is which room:
Number
1
2
3
4
5
6
7
Filename
street_01.c
street_02.c
street_03.c
market_southwest.c
market_southeast.c
market_northwest.c
market_northeast.c
We'll worry about the inside rooms later. It doesn't matter particularly what
filename we give the rooms, but to ensure that your work and these tutorials sync
up, you should be making use of the same filenames as we use. Trust me, it's just
simpler that way, for now.
A text file is ideal because it can be viewed on the MUD, or put easily into the
wiki. Here, I've put a number for each room we're going to create, and letters for
inside rooms with Further Features. It's a small area, but one that will cover
everything we need to discuss in order for you to make a meaningful contribution
to your domain. We've done room 1 and room 2. In the next section we'll add in
the skeleton of the remaining rooms as we encounter new things we can develop.
Property Ladder
As part of our code above, we have a line that references to 'add_property'.
Properties are little tags that can be added to an object whenever they are needed,
but they don't actually do anything other than exist. We can check in other pieces
of code whether an object has a property attached, and if so change the way we
treat it a little.
As an example of this working, you can add the 'gills' property to yourself to give
you the ability to breathe under-water:
call add_property ("gills", 1) me
45
Epitaph Online
http://drakkos.co.uk
The first part of this is the property to add, and the second is the value to give
that property. The 1 simply indicates 'yes, this property should be added'. Now,
you'll never drown when you're in the water.
Properties are used intermittently through the MUD, but you should be wary of
them - they have a tendency to clutter objects. Because they are so easy for
people to add (there is no set list of properties which are valid), it's tremendously
easy for creators to write objects that attach properties to players and items, and
then never write down why or what the property is for. Before too long, everyone
is wandering around with properties like "gnah bag check", and no-one except the
original creator (who may have left by this point) has any idea where it came from.
Properties are a quick and easy hack, but you shouldn't use them too much. In
circumstances where you do, you should always make sure that they are timed
properties. You can do this by providing a third piece of information to the
add_property call - the number of seconds the property should exist for:
call add_property ("gills", 1, 120) me
Here, the gills property will remain on you until 120 seconds have passed, at
which point it won't. These are 'self-cleaning' properties and you should always,
always use them unless you are very sure indeed that you don't need to.
Conclusion
These simple rooms aren't very interesting – but we're going to change that as we
go along. We have taken a big step though – creating an actual real room you can
stand in, and another room you can move to - it's from these basic building blocks
that whole cities are built. We're going to leave them lacking in add_items and
long descriptions and such - nothing is gained by writing these rooms to the
standard that would be expected in the game. Once you've seen one add_item,
you've seen them all - you don't learn more by having something repeated over
and over again. Filling in the blanks is left as an exercise for the interested reader
- I heartily encourage experimentation to see what happens when you change
things - the fact that you have easy access to the original source-code from
/d/learning/deadville means that there is no cost if you break everything beyond
repair.
46
Epitaph Online
http://drakkos.co.uk
My First Area
Introduction
You've taken your first steps to developing a small room within the MUD. In this
chapter, we're going to extend this to an entire area. In the process, we'll talk a
little bit about how to develop an architecture that means we can easily move
things around as we need, and link them up with minimum fuss. This is an
important aspect of developing an area – things get moved around a lot on
Epitaph, especially through development and playtesting. Areas that are difficult
to maintain are a drain on everyone's time. Luckily your areas aren't going to do
that, because we're going to talk about how you can avoid it.
Once we've looked at the setup of the skeleton of the area, we're going to spend a
little more time looking at the exits we've setup. The more detail we put into our
areas, the richer our game world seems. That's a goal I'm sure we can all agree on
being worthwhile.
All the example code for this chapter can be found in
/d/support/deadville/my_first_area/
The Structure
You've seen the map from which we're going to be developing. We can develop
room by room, only adding a room into the whole when it's completed. However,
in order to get a feel for how things are going to link up, it's beneficial to put
skeleton implementations of each room in place, and then incrementally add their
descriptions and features. That's what we're going to do now. It's easier than you
may think.
Remember our map:
A B
| |
6-7-C
| |
3-4-5-D
/
2
/
1
And our key:
47
Epitaph Online
http://drakkos.co.uk
Number
1
2
3
4
5
6
7
Filename
street_01.c
street_02.c
street_03.c
market_southwest.c
market_southeast.c
market_northwest.c
market_northeast.c
We've got the basic outline of rooms 1 and 2. We have another five rooms that will
be outside rooms, and then four that will be inside rooms. We begin putting this
framework together with a template. Save it as street_03.c:
inherit STD_OUTSIDE;
void setup() {
set_short ("skeleton room");
add_property ("determinate", "a ");
set_long ("This is a skeleton room.\n");
set_light (100);
}
This is the room you're going to repeat for each of our outside rooms - there's
nothing in it, but that's fine - we don't want to spend time writing some intricate,
beautiful descriptions we are only going to have to scrap later. The four remaining
rooms of our development are the market rooms. Now comes the magic!
You don't need to painstakingly create a new file for each of these rooms and then
save it – we make use of the cp (copy) command to create the rest of the template
rooms:
cp
cp
cp
cp
street_03.c
street_03.c
street_03.c
street_03.c
market_southwest.c
market_southeast.c
market_northwest.c
market_northeast.c
Now when you ls (list) the directory, you'll find it full of your skeleton rooms.
1 market_northeast.c
1 street_03.c
1 street_02.c
1 market_southeast.c
1 market_northwest.c
3 street_01.c
1 market_southwest.c
We haven't added the exits to these new rooms, but that's okay – we need to talk
a little bit about exits before we leap into that part.
48
Epitaph Online
http://drakkos.co.uk
Exits and Maintainability
If you've been looking through the 'here's one that I made earlier' versions of
these files in the /d/support/deadville directory, you'll see that it's broken up
by chapter. Each directory contains the code as it stands by the end of the
appropriate chapter. I'm not writing this code every time – I'm copying it across
from the last version.
However, the exits from my_first_room don't work after moving it into the
directory of my_first_area – every time a move is made, you end up in the room
for my_first_room. You can change the filenames by hand, and that's not too
bad for two rooms. We've got seven now, and four more to come – it would be a
nightmare to have to change all of those paths by hand, for every chapter of this
book. It's too much to ask of anyone, especially me because I really don't want to
have to do it.
The same thing is true with your code – sometimes things get moved around as
directories are reshuffled, cities are redesigned, and generally treated with an
incredible amount of casual disrespect. Even the Gods don't pick up cities and
rehouse them with the indifference we do. Changing every exit in a large city by
hand would be an impossibly annoying task, and one you'd just need to repeat
when the city moved directories in the future. It's not a sustainable approach.
We have a way around that problem though – we make use of what is known as a
header file.
Create a new file and save it as path.h (note, .h on the end rather than .c). It's
going to contain one single line:
#define
PATH
"/w/your_name_here/deadville/rooms/"
This is a special kind of code statement in LPC. Technically it's called a
preprocessor directive – it's something that LPC deals with before it ever gets to
your code. There is a subsystem in the driver called the preprocessor, and it's
kind of a souped-up search and replace tool. For the code we have here, we have
given the preprocessor a directive to define the text PATH as being another string
entirely.
When you create your path.h file, make sure you hit return at the end of the line. If
you don't, you'll get a worrying error saying something about #pragma
strict_types. If this happens, go back to your path.h file and add a return at the
end.
The preprocessor is essentially a sophisticated search and replace routine –
providing a #define statement tells the preprocessor that every time it sees the
first term to replace it with the second term. Thus, any time you use the text PATH
in your program, it will replace it with the text "/w/your_name_here/deadville".
This happens transparently, and every time the object is loaded. You see no
impact on your code.
49
Epitaph Online
http://drakkos.co.uk
Simply creating a path.h file is not enough though, we need to tell our rooms to
use it. We do this by using another directive called an include directive. At the top
of each of your room files, before your inherit line, add the following:
#include "path.h"
This tells the preprocessor to take the path.h file you just created, and
incorporate it into your rooms – as such, each of your rooms gets the define that
is set. In your skeleton rooms, the code will now look like this:
#include "path.h"
inherit STD_OUTSIDE;
void setup() {
set_short ("skeleton room");
add_property ("determinate", "a ");
set_long ("This is a skeleton room.\n");
set_light (100);
}
Now, let's see what impact that has on our original two rooms. Remember we
have already set up their exits, like so. In street_01.c, we have:
add_exit ("northeast", "/w/your_name_here/deadville/street_02", "path");
In street_02.c, we have:
add_exit ("southwest", "/w/your_name_here/deadville/street_01", "path");
We're going to change these so that they read as follows. For street_01.c, we will
have:
add_exit ("northeast", PATH + "street_02", "path");
For street_02.c, we will have:
add_exit ("southwest", PATH + "street_01", "path");
Now, update your two rooms – you'll see you can move between them just as
easily as you were able to do before. That's because before LPC even begins to try
and load your code, the preprocessor looks through your code for any instance of
PATH and replaces it with your define – so what LPC sees when it comes to load
your room is the following:
add_exit ("southwest", "/d/support/deadville/my_first_area/rooms/" +
"street_01", "path");
LPC is perfectly capable of adding two strings together, and once it's done that it
ends up with the full path to the room. The difference here though is that if you
change it in your path.h file, it changes in every file that makes use of that path.h
– one change in the right place and an entire city can be shifted from one
directory to another without anyone lifting a finger. It's very powerful, and an
approach to development into which you should definitely get into the habit.
50
Epitaph Online
http://drakkos.co.uk
Check with your domain administration to see how this gets handled in your own
domain. Some domains have a single .h file (such as game.h) that serves as a
master file for every room. Some have a more distributed approach whereby
smaller .h files are available to subsections within a development.
Our Exitses
Now, we need to go back over our skeleton rooms and add in the exits – you
should be able to do this quite easily by yourself now, as long as you know which
rooms are heading where (and the map will tell you that). Don't add any exits for
the inside rooms yet – we'll get to those later.
To make sure your exits look the way they are supposed to, here's the code for
the exits in each of your rooms:
street_01:
add_exit ("northeast", PATH + "street_02", "path");
street_02
add_exit ("northeast", PATH + "street_03", "path");
add_exit ("southwest", PATH + "street_01", "path");
street_03
add_exit ("east", PATH + "market_southwest", "path");
add_exit ("southwest", PATH + "street_02", "path");
market_northeast
add_exit ("south", PATH + "market_southeast", "path");
add_exit ("west", PATH + "market_northwest", "path");
market_northwest
add_exit ("south", PATH + "market_southwest", "path");
add_exit ("east", PATH + "market_northeast", "path");
market_southeast
add_exit ("north", PATH + "market_northeast", "path");
add_exit ("west", PATH + "market_southwest", "path");
market_southwest
add_exit ("north", PATH + "market_northwest", "path");
add_exit ("east", PATH + "market_southeast", "path");
add_exit ("west", PATH + "street_03", "path");
There's a particular convention that is followed when adding multiple exits – when
you walk around the game, the exits are presented to you in the order in which
they are added in code. To improve consistency, try to add relevant exits in the
following order: north, south, east, west, northeast, northwest, southeast,
southwest, and then any other exits. No-one will shout at you if you don't do this,
but it's the convention.
One thing you may have noticed from other parts of the game, especially in
51
Epitaph Online
http://drakkos.co.uk
market squares, is that there are sometimes diagonal exits you can take that
aren't listed – if you can go north and then east to get to a particular room, why
can't you go northeast?
There's no reason why we should exclude this, so let's first add in the diagonal
exits in the market rooms:
market_northeast
add_exit ("southwest", PATH + "market_southwest", "path");
market_northwest
add_exit ("southeast", PATH + "market_southeast", "path");
market_southeast
add_exit ("northwest", PATH + "market_northwest", "path");
market_southwest
add_exit ("northeast", PATH + "market_northeast", "path");
Next, we will set them to be hidden. Why do we do this? Mainly it's to avoid
cluttering up the obvious exits list - whether you want to make your exits hidden
is entirely up to yourself, but there's no reason why you shouldn't know how to do
it. We make use of a new piece of code called modify_exit for this – modify_exit
is tremendously powerful, and we'll return to it several times in the future.
In each of the market rooms, we'll modify the exit of the diagonal so that it is set
as 'non-obvious'. It's still there, just not listed. For example, in market_northeast
we'd just the following line of code:
modify_exit ("southwest", ({"obvious", 0}));
Do the equivalent of this in all four market rooms and update – you'll find the
exits disappear from the list, but you're still able to take them just as before.
Chain, chain, chain...
Another thing you'll have noticed as you wander around the game is that some
parts of various cities give you informs as to what's happening in another part...
for example, you'll see something like
Drakkos moves southeast onto the centre of the city square.
It would be cool if our market rooms did that too – and they're going to by making
use of a thing called the linker. First though, we need to make sure their shorts
are set properly, because that's what's used to build the message. In each of the
market rooms, change the shorts as follows:
market_northeast
set_short ("northwest corner of the marketplace");
52
Epitaph Online
http://drakkos.co.uk
market_northwest
set_short ("northwest corner of the marketplace");
market_southeast
set_short ("southeast corner of the marketplace");
market_southwest
set_short ("southwest corner of the marketplace");
For each of these, you should also set the determinate as 'the' instead of 'a'. In
that way, people will see, for example, 'the northeast corner of the marketplace'
when people move about. If it's set to 'a', then they'll see 'a northeast corner of
the marketplace', which doesn't look right, not right at all!
Setting the shorts properly ensures that the messages will be properly formed,
but still need to tell the MUD we want it to happen. We do this using set_linker.
We give the MUD each room we want to link together except for the room in which
we're defining the code.
For market_northeast:
set_linker ( ({
PATH + "market_northwest",
PATH + "market_southwest",
PATH + "market_southeast",
}));
For market_northwest:
set_linker ( ({
PATH + "market_northeast",
PATH + "market_southwest",
PATH + "market_southeast",
}) );
market_southeast:
set_linker ( ({
PATH + "market_northeast",
PATH + "market_northeast",
PATH + "market_southwest",
}) );
And market_southwest:
set_linker ( ({
PATH + "market_northeast",
PATH + "market_northeast",
PATH + "market_southeast",
}) );
You'll need to log on a test character to make sure that you've got this all setup
properly, you can't test it from your own perspective. Create a test character,
bring them into the world, and then set them as a test character using the
testchar command:
testchar <testchar_name> on
Trans your testchar to your village and make them dance around a bit for you.
53
Epitaph Online
http://drakkos.co.uk
What you should see are messages along the lines of the following:
Draktest moves south into the southwest corner of the marketplace.
Provided that message looks right from every part of the marketplace, you've got
it all configured properly. Well done!
The linker is also used to determine what is an valid direction for things like
binoculars and hunting rifles - it's not just a presentational thing, it's a neccessary
component of making sure that these systems work correctly in your areas.
What is all that crazy STD_OUTSIDE stuff, anyway?
When we tell the MUD we want to inherit something, we basically say 'Hey, I don't
want to write all the code for this myself. Give me some code that already exists'.
In the case of Epitaph, all our outside rooms are based on, at some point in their
code, an object that has been #defined as STD_OUTSIDE.
'But Drakkos', I hear you ask, 'I didn't define that!'
That's exactly right, and well done for noticing. Every object on Epitaph gets a
'free' file #included. You don't get a say in this, it just happens – that file is
/include/all_inc.h. One of the lines in that file is:
#include <mudlib.h>
That's the file that holds all of the standard defines for inherits. One of those is:
#define
STD_OUTSIDE
MUDLIB_INHERITS + "outside"
MUDLIB_INHERITS is another define, which is in itself based on another define. If
you want to find what file a define in mudlib.h points to, you can do this:
> exec return STD_OUTSIDE
Returns: "/mudlib/inherits/outside"
So, in our code it is effectively the same as if we had the following line of code:
inherit "/mudlib/inherits/outside";
In fact, if you change all your inherits to that line, it will work exactly the same
way as it did before.
Sadly, you won't know which of these you are supposed to use at particular times
yet, but that will come as we progress through the material. We do it this way
because it makes our mudlib easier to move around as needed – if we need to
change the directory all of our inherits are in, we don't need to manually change
every inherit in every file – we just change it once in mudlib.h and away we go.
That is a huge deal, and not just because I once had to do exactly that – go over
EVERY SINGLE FILE and change the paths. Oh god, the horror.
54
Epitaph Online
http://drakkos.co.uk
Conclusion
Having a skeleton of an area is a great way to give you a perspective of how it all
fits together, as well as a solid idea of how much work you have to do. There's
nothing wrong with the 'write one room and link it in' approach, but you get a
much better idea of the bigger picture by architecting it all together and just
seeing how it feels. That way, if you think that the layout needs to change, you
can do it before you've hooked too much of it together. It's just a nice way to get
some perspective.
My personal preference is to do this and then watch as the area starts to evolve in
line with my development. It's very nice to be able to see an area taking shape
before you. When we did the city of Dunglen, the outer core of the city was all
created and linked together before anything had really been done, and it was
great to watch it slowly constructed around me as various creators went about
their business. You should however find an approach that works best for you.
Your mileage may vary, as they say.
55
Epitaph Online
http://drakkos.co.uk
Building the Perfect Beast
Introduction
It's awfully lonely in our little village, isn't it? I think it is, anyway – and surely, like
I, you crave some kind of company on your quest to become a fully capable
Epitaph creator. In this chapter we're going to build the first inhabitant of our
area, and provide him with equipment, responses, and chats. He's going to be our
little living doll, for us to taunt and make dance for our sport.
In the process, we'll have to look at some new syntax and introduce a new
concept of LPC programming, that of the variable. We've come quite far without
actually talking abut variables, but you've been using them all along – yes, that's
right! That's the SHOCKING TWIST of this chapter - variables are the Kaiser Soze of
LPC programming!
A First NPC
Let's start off by creating a new subdir in our Deadville directory – this one will be
called chars:
mkdir /w/your_name_here/deadville/chars/
It is in this directory that we will include the code that creates any NPCs in our
area. Keeping a clean divide between rooms, NPCs and items is an important part
in ensuring it's easy to integrate your code into a larger domain plan. This is
going to cause some complications for our path.h file, which we'll talk about a bit
later – there is no problem so great that we cannot solve together, though!
Anyway, creating an NPC follows a very similar process to creating a room – what
changes is the inherit we use, and the specific code that goes into the setup of the
object. Consider the following for a basic template – save it into chars with the
filename captain_beefy.c.
#include <npc.h>
inherit STD_MONSTER;
void setup() {
set_name ("beefy");
set_short ("Captain Beefy");
add_property ("determinate", "");
add_property ("unique", 1);
add_adjective ("captain");
add_alias ("captain");
set_gender (1);
56
Epitaph Online
http://drakkos.co.uk
set_long ("This is Captain Beefy, a former officer of Her Majesty's "
"Secret Service. Once he was a pretty impressive guy, with a large "
"string of accomplishments to his name. Now, he's just another "
"survivor in the grim darkness of the LPC apocalypse.\n");
basic_setup ("human", "survivor", MONSTER_LEVEL_NORMAL);
}
Let's go through that line by line, as we did for our first room:
// npc.h is a mudlib include file – it works much like your path.h,
// but contains definitions used by all NPCs.
#include <npc.h>
// If we want to create a basic NPC, this is the file we inherit.
inherit STD_MONSTER;
void setup() {
// The name of the NPC is how it is identified by the MUD's matching
// system. This should be one single word - it should usually too be
// the last word in the short, for simplicity's sake.
set_name ("beefy");
// This is what players see when they encounter the NPC.
set_short ("Captain Beefy");
// This is what gets prepended to the short of the NPC. Since Captain
// Beefy is a unique person, he gets his determinate set to empty. If
// he was one of a number of clones (for example, a beggar), then
// the determinate could be set to 'a', or even 'the'.
add_property ("determinate", "");
// Captain Beefy is a unique NPC - there should only ever be one of him.
// This code doesn't ensure that, but it does mean when he's killed he'll
// trigger a death inform.
add_property ("unique", 1);
// The name is used to match an NPC, but we also need to set valid
// adjectives. If we don't include this, our NPC can be referred to as
// 'beefy', but not 'captain beefy'. We want both to be valid.
add_adjective ("captain");
// We also want people to be able to refer to him just as 'captain', so
// we add an alias for him.
add_alias ("captain");
// He needs a gender, because he's a he. Setting the gender to 1 makes
// him male. 2 makes him female. Everything else makes him an 'it'.
set_gender (1);
// The long is what players see when they 'look' at him.
set_long ("This is Captain Beefy, a former officer of Her Majesty's "
"Secret Service. Once he was a pretty impressive guy, with a large "
"string of accomplishments to his name. Now, he's just another "
"survivor in the grim darkness of the LPC apocalypse.\n");
// You need this in the code, or try as you might he won't clone when you
// want him to. The first piece of information we provide is his race.
// The second is his guild. The third is his 'monster level'.
basic_setup ("human", "survivor", MONSTER_LEVEL_NORMAL);
}
57
Epitaph Online
http://drakkos.co.uk
So, update your code, and clone him – he'll appear in the same room as you if all
has gone to plan:
You can hear: Nothing.
In the distance you can see: Nothing.
This is a simple road, leading to a simple village. There is very little of an
excitement about it, except that it represents your first steps to becoming
a creator!
It is a very cold winter afternoon with torrents of stinging sleet, medium
cloud cover and almost no wind.
There are two obvious exits: northeast (clear) and southwest (clear).
Captain Beefy is standing here.
We can interact with him in the same way we can with any object in the game, but
there's not much point. He doesn't do anything interesting at all. He just stands
there looking gormless. But he loads! Chant it like a mantra, 'But he loads!'. That's
always the first promising step you take. After that, the rest is inevitable.
Breathing Life into the Lifeless
First of all, let's make him emit some chats. Much like with our rooms, we can
make our NPCs a little more interesting by adding chats. Moreover, we can make
these chats different depending on whether the NPC is being attacked or not. The
mechanisms by which we do this are identical in terms of syntax and meaning,
but they're quite different from how it is handled in rooms. The code we need is
called load_chat for normal chats, and load_a_chat for attack chat.
Let's add some chats into our NPC, and talk about what the code means:
load_chat(10, ({
2, "' Please don't hurt me.",
1, "@cower",
1, "' Here come the drums!",
1, ": stares into space.",
}))
This sets up the random chats for the NPC. The first number dictates how often a
random chat is made. Every two seconds, (a period of time known as a heartbeat)
the mud rolls a one-thousand sided dice (metaphorically), and if the result is lower
than the number set here, the NPC will make a chat. It's not exactly fine-grained
control, but control it is.
Each of the chats has two parts - a weighting, and a command string. The
weighting is the relative chance a chat will be selected. The string is the command
that will be sent for the NPC to perform. If you want the NPC to say something,
then start the command with an apostrophe. If you want the NPC to emote, then
start the command with a semi-colon. If you want the NPC to perform a soul, start
the command with an at symbol.
To understand the way the weightings work, think of it as a roulette wheel – add
up the weightings of all the chats, and it'll come to 5. When the MUD determines
58
Epitaph Online
http://drakkos.co.uk
the NPC should make a chat, there is a 2/5 chance it'll be the first chat, and a 1/5
chance it'll be each of the others. As usual, there is more that you can do with
load_chat than we've covered here – we'll get to some advanced stuff later in the
tutorials.
One proviso here is that if you want your NPC to actually say things, it's going to
need a language and a nationality. We can provide that by using the
setup_nationality method providing in an appropriate combination of nationality
and region. Your domain leader will be able to tell you which of these is
appropriate for your domain, but for demonstration purposes we'll make Captain
Beefy a Scotsman. Put it after basic_setup in your code:
setup_nationality(MUDLIB_NATIONALITIES + "british", "scotland");
To add the attack chats, it works the same way – we just use load_a_chat instead:
load_a_chat(10, ({
1, "' Oh please, not again!",
1, "' I don't get medical insurance!",
1, "@weep",
}));
When you make changes to Beefy, you'll need to dest him before you update and
reclone. Once you've added the chats, go on – take a swing at him! You may find
he doesn't swing back – if you're invisible, that'll be why. But you should find he
pleads pitifully before your Awesome Might:
You punch at Captain Beefy.
Captain Beefy exclaims: Oh please, not again!
Poor fellow. He doesn't even know what Fresh Horrors we have in store for him to
come.
So, that's fine for making him a little more interesting – however, we also want to
be able to make him reply when we say things to him. Let's add some of that now
using add_response. The syntax for this is similar to that for add_item:
add_response (({"murder"}), "' Please don't kill me!");
This sets up a response to a message that originates with the command 'ask'. It
will match on any string containing either the words 'hello', 'hi', or 'hiya', provided
it originates from an ask command. Beefy's response to this will be to say "Er,
hello. Please don't kill me.", like so:
> ask beefy about murder
You ask Captain Beefy a question about murder.
> Captain Beefy exclaims: Please don't kill me!
Let's add in a second response, since he's already opened us up to the possibility
of killing him:
add_response ("kill", "' No, please!
God, please don't kill me!");
59
Epitaph Online
http://drakkos.co.uk
We can also make him respond to the 'greet' command by adding in a greet
response:
set_greet_response ("' Oh, I hope you are not here to kill me.
"hello.");
I mean, "
This has the following happy outcome:
> greet beefy
You greet Captain Beefy.
> Captain Beefy says: Oh, I hope you are not hear to kill me. I mean, hello.
In a similar vein, if we want to make him have a response whenever we ask him
about something he doesn't have a specific response for, we can use
set_unknown_response:
set_unknown_response ("' I don't know anything about that.");
And then:
> ask beefy about beef
You ask Captain Beefy a question about beef.
> Captain Beefy says: I don't know anything about that.
While add_response doesn't give especially fine grained control over interactions
with NPCs, the nature of the system (for example, the MXP links or coloured text it
will provide for conversational keywords) ensures that NPCs are appropriately
interactive for players.
Cover Yourself Up, You'll Catch a Cold
Captain Beefy will now chat with us a bit, but he's still horribly naked. That's
embarrassing for everyone concerned, so let's dress him up a bit, like the little
doll he is. We have two mechanisms for doing this.
If we are completely unconcerned about the outfit that Beefy should wear, and
instead are more interested in simply ensuring that we can't see his reproductive
business, then we simply say dress_me within the setup for the NPC:
dress_me();
You get some control over how the dressing should work by passing parameters
(more on this later), but by default it will dress him up as a survivor from the
appropriate game region (based on his nationality). Once we update him and
reclone him, we'll see something like:
This is Captain Beefy, a former officer of Her Majesty's Secret Service. Once
he was a pretty impressive guy, with a large string of accomplishments to
his name. Now, he's just another survivor in the grim darkness of the LPC
apocalypse.
He is in good shape.
He is standing.
60
Epitaph Online
http://drakkos.co.uk
Holding : a machete (left hand).
Wearing : a pair of white cotton sports socks, a pair of blue denim jeans and
an orange football scarf.
He'll get a different outfit each time, and it'll always look this ramshackle (here,
because he's a survivor of a zombie apocalypse and is thus making do the best he
can), but it will be as far as is possible gender appropriate. It's a quick and easy
way when you don't really want or need to worry about the equipment with which
an NPC is provided.
Request Item
If we don't want some random clothes, or if we want to supplement his inventory
with something more specific, we have available another piece of code called
request_item. This lets us conjour up specific items from the armoury in the
same way that the creator request command permits. Let's give him a bra. Just
because we can.
request_item ("plain bra", 100);
init_equip();
The first part of the request_item line is the item we want from the armoury. The
second is the condition that it should have. The init_equip line means 'now
wear those things we have given you'. If he can't wear the clothes we give him
(because he is already wearing clothes of that kind), they'll just hang around in his
inventory.
You can use both of these techniques if you want (dress an NPC randomly and
then give him or her specific items), or you can use one or the other exclusively.
Either approach is appropriate – it just depends on your specific requirements.
Challenges
By default, Beefy has only the standard five levels of each skill that everyone gets.
If you want him to be a little more challenging, then you need to add challenge
profiles to him.
On Epitaph, we don't set an NPC's skill levels directly. Instead, we use challenge
profiles - these let us say 'this NPC should be this good at this particular thing'. A
profile consists of several skills, each of which gets layered onto the NPC. These
are defined in challenges.h (so you will need to #include that), and are used like
so:
set_challenge_profiles (([
PROFILE_SHARP_WEAPONS : CHALLENGE_INCREDIBLY_EASY,
PROFILE_PIERCE_WEAPONS : CHALLENGE_INCREDIBLY_EASY,
PROFILE_BLUNT_WEAPONS : CHALLENGE_INCREDIBLY_EASY,
PROFILE_UNARMED : CHALLENGE_INCREDIBLY_EASYd,
]));
61
Epitaph Online
http://drakkos.co.uk
This handles everything from setting up skill levels, adjusting stats where
appropriate, and adding neccessary commands. This system permits us to balance
NPCs against each other and against the various tiers of difficulty we already have
in the game. Additionally, these challenge profiles are used when considering an
NPC, permitting players to get a fine-grained breakdown of an NPCs capabilities:
> consider beefy
As best as you can tell, Captain Beefy is extremely bad at fighting unarmed,
extremely bad at combat with pierce weapons, extremely bad at combat with
sharp weapons and extremely bad at combat with blunt weapons.
You consider Captain Beefy.
If you don't really want this level of fine grained control, and you just want to be
able say 'he should be pretty good as a soldier', then you can use one of the
skillset packages, like so:
set_challenge_profiles (([
"warrior" : CHALLENGE_AVERAGE,
]));
Skillset packages are defined in /data/skillsets/, and essentially work like a
package of challenge profiles that get assigned to an NPC. The one for warriors
looks, in part, like this:
::item "warrior"::
::->name:: "warrior"
::->other_skillsets::
({
PROFILE_SHARP_WEAPONS,
PROFILE_FIREARMS,
PROFILE_PIERCE_WEAPONS,
PROFILE_BLUNT_WEAPONS,
PROFILE_UNARMED,
PROFILE_OBSERVANT
})
::->stats:: ([
"strength" : 1,
"dexterity" : 1,
"constitution" : 1,
])
This system means you don't need to worry about the book-keeping of NPC skill
levels – the various game handlers will configure the numbers for you.
Chatting Away
There's one thing left for us to talk about in this chapter, and it's the special
codes that can be used within load_chat and load_a_chat to make our NPCs more
involved in the world around them. We can build special strings that get
interpreted by the MUD and turned into meaningful output based on the things
around our NPC. It's easier to see what that means in practise than describe it, so
let's add a load_chat to Captain Beefy:
62
Epitaph Online
http://drakkos.co.uk
load_chat(10, ({
2, "' Please don't hurt me.",
1, "@cower",
1, "' Here come the drums!",
1, ": stares into space.",
1, "' Oh, hello there $lcname$.",
}));
See the last chat there? The weird looking code is interpreted by the MUD to take
the form of the name of a living object in the NPC's inventory. The first letter (l)
defines what object will be the target of the chat, and the string that follows
(cname) defines what is used to build the rest of the string. When the chat is
selected, a random object is picked from the specified set, and then the requested
query method is called on it and substituted for the special code we provide. The
letters we have available for choosing the set of objects is as follows:
Letter
m
l
a
o
i
Object
The NPC itself.
A random living object in the NPC's
environment.
A random attacker in the NPC's
environment.
A random non living object in the NPC's
environment.
A random item in the NPC's inventory.
Following the initial letter comes the type of information being requested. This
gets called on the random object that is selected when the chat triggers. Some of
these are more useful than others, but you may find cause to use even the more
specialised ones on occasion:
Code
name
cname
gender
poss
pronoun
ashort
possshort
theshort
oneshort
Request
The file name of the NPC - used for
targeting souls
The capitalised name of the object
The gender string of the object
The possessive string
The pronoun of the selected object
The a_short() of the object
The poss_short of the object
The the_short of the object
The one_short of the object
As an example, we could get the short of an object in the NPC's inventory with
$itheshort$. We could get the name of a random attacker in load_a_chat with
$acname$. Unfortunately, we get no fine-grained control over the object selected if we choose the item set of objects, we can't further specialise it, so a chat like
the following will not be appropriate:
"' I am going to stab you in the eyes with $ipossshort$!"
63
Epitaph Online
http://drakkos.co.uk
It'll parse properly, but he'll end up saying things like 'I am going to stab you in
the eyes with my floppy clown shoes!' which, while frightening, is not really
sensible. Despite the limitations, combining these codes will allow for you to
make your NPC chats more dynamic and responsive to the context in which it
finds itself.
64
Epitaph Online
http://drakkos.co.uk
Conclusion
We've come quite far in this chapter, having created an interactive NPC who is
dressed in a fashion that does not offend our Victorian sense of decency.
However, we need to manually clone Captain Beefy into our village each time we
want him there. In the next chapter we'll look at how we can get that to happen
automatically without our intervention.
At the moment we're making use of only those items that the armoury can
provide, but we'll also look at ways in which we can write our own objects and
make them available to our NPCs and our rooms.
65
Epitaph Online
http://drakkos.co.uk
Hooking Up
Introduction
So, we now have a set of rooms, and we have an NPC. They're simple, but they
work. It's not appropriate though that you have to clone the NPC directly into your
room – it should happen automatically, and we're going to talk about how that
works in this chapter.
We're also going to resolve the path.h problem that we introduced in the last
chapter by looking at relative and absolute directory referencing. So buckle up,
time to take LPC out for another spin!
The Path.h Problem
Look at where we've got our path.h file stored – it's in our rooms directory.
Although we haven't needed to refer to it in the NPC we created, we should still be
able to get access to it without too much complication... the idea of a header file
is that it's shared between all relevant objects, after all.
We have a couple of possibilities. One is to copy the path.h file into each directory
that we are likely to need it. This is a bad solution because it reintroduces the
problem it was designed to fix – we need to change the defines in multiple places
if we want to shuffle things around. That's not ideal – we want to be able to
change things in one place and have it reflected in all appropriate locations.
Our second possibility is to make everything use the same path.h file – that's a
better solution, but it's going to need us to change all the references to the path.h
in all our code. We'll need to put it in a central location, and then have all of the
files #include it from there. That's not a bad solution - there are plenty of .h files
in /include/ that work this way, but you need special file access to put files in
there, and comparatively few creators have that access. We'd need to store it then
in a set directory in our /w/ drive. We can do better than that though – after all, if
we move our code from /w/ to /d/, we're going to have to remember to move the
.h file along with it, and then change all the references to the path.h file to reflect
its new location. Ideally it should just be a case of copying a directory across and
having done with it.
How about this though – we keep a path.h in each subdirectory, but we have that
path.h in itself include a path.h in a higher level directory? That way, provided the
basic structure of the directory remains intact, we have a chain of path files that
define all the values we need.
That may sound confusing, but let's see it in practice – it should become a bit
clearer with an example.
66
Epitaph Online
http://drakkos.co.uk
Let's start with a new path.h file in our base Deadville directory:
#define
#define
#define
PATH
ROOMS
CHARS
"/w/your_name_here/deadville/"
PATH + "rooms/"
PATH + "chars/"
In each of your subdirs, you add a further path.h file that includes the path.h from
the higher level directory. We can do that using the .. symbol to to represent the
higher level directory in the #include. The double period symbol has a special
meaning in a #include - it means 'go to the directory one up from the current
directory':
#include "../path.h"
Now, we are going to have to change our room code a bit because we're making a
distinction between ROOMS and CHARS. Luckily we don't need to do that by hand,
we can use the sar command to do a search and replace. I shall warn you in
advance though, be VERY CAREFUL when using this command. One creator, who
shall remain nameless (Terano@Discworld) once mistakenly changed every
instance of the letter a in all the priest rituals to the letter e. While tremendously
funny (to everyone else), it was hugely problematic for him to fix.
Anyway, the sar command needs three pieces of information – the string of text
you want to replace (surrounded in exclamation marks), the string of text you
want to replace it with (again, surrounded in exclamation marks), and the files you
want the text replaced in. Let's run that puppy over our code. First, you change
your your current directory to the rooms subdirectory, and then:
sar !PATH! !ROOMS! *.c
Upon uttering this mystical incantation, you'll find all of your rooms now reflect
the New Regime. Update them all (you can do this using update *.c), and you'll
find everything is Hunky Dory.
If it's not, remember what we discussed about path.h files in a previous chapter if they don't work properly, make sure there's a carriage return at the last line.
Sometimes LPC chokes on a file if that's not the case.
With regards to sar, please remember – use this command with caution. It's
incredibly easy to do some really quite impressive damage to your hard work with
only a few misplaced keystrokes. You Have Been Warned!
67
Epitaph Online
http://drakkos.co.uk
Sorted!
Right, now we've gotten that out of the way, let's look at how we can make
Captain Beefy appear in our rooms. We're going to pick a room for him (we'll
choose market_northwest) and talk about how it works.
Cloning a unique NPC into a room actually involves only four pieces of
information – the path to the NPC, the message to present when the NPC appears
in a room, and the message to present if the NPC has to be moved from another
room. The code that does this is called add_unique_spawn.
It goes into the setup of a room, just like most of the other things we've spoken
about, and in its simplest form it will look something like this:
add_unique_spawn (CHARS + "captain_beefy", "$N appear$s with a pop.",
"$N disappear$s with a pop.");
The move messages here get handled by the MUD so that they look sensible for
everyone viewing them - $N becomes the name of the NPC, and the $s code
means 'add an s to this word where it is appropriate'. If you were Captain Beefy,
you'd see 'you appear with a pop', whereas everyone else in the room would see
'Captain Beefy appears with a pop'. The MUD's message handling routines are
pretty cool like that.
Update your room, and lo and behold – Captain Beefy appears:
Captain Beefy appears with a pop.
For now, that's all we need to do – add_unique_spawn lets us do a lot more than I
am letting on here, but that's functionality you'll come to uncover as the need
arises.
Reset Your Phasers for Stun
For the next part of this material, we need to talk about a new kind of
programming concept - the function. A function is essentially a little selfcontained parcel of code that gets triggered upon request - sometimes on our
request, sometimes on the request of another object in the MUD. We don't need
to worry too much about it just now, we just need to know that's what we're about
to do. Functions have many different names - they most common one you'll also
see in this material is the word 'method'. It's just another word for the same thing.
There are certain functions that the MUD executes on all Epitaph objects at
predetermined intervals. One of these we're familiar with – the code that belongs
to setup gets executed when we update our objects. There's another function that
gets called at regular intervals, and that's the reset function. The reset function is
called on rooms when a room is loaded (and it's called just after setup), and also
at regular intervals (of around thirty minutes or so) on all currently loaded rooms.
Just for the hell of it, we're going to make use of this to, every so often, make
68
Epitaph Online
http://drakkos.co.uk
Beefy have a heart attack and die. Why? No reason, other than sadism – and the
fact it is instructional.
To do this, we're going to add a new function to market_northwest, like so:
void reset() {
::reset();
}
This function exists outside of any code you've already got. So within your room, it
will look like this:
void setup() {
// All your current code
}
void reset()
{
::reset();
}
Yikes! What does that code inside the reset method mean? Well, you know how at
the top of your code you have an inherit line? Well, that STD_OUTSIDE object
has a reset method of its own as part of its code. The ::reset() line says to the
MUD: 'Oh, call reset in the object from which I'm inheriting, please'. Don't worry
too much about it just now, just make sure it's there.
Next, we need to find Beefy in our function. He's in our room, but we don't have
any way to refer to him in code at the moment. First, we need a variable that is
going to hold him when we find him:
object beefy;
This line of code comes before the ::reset() in your reset function – all
variables have to be declared at the top of a function, before any other code.
That's just a thing LPC insists upon.
Now we can start building our hateful heart-attack code, which we do after the
::reset() line. Our process is as follows:



Find Beefy
Check to see if he's in our current room
If he's there and we roll under ten on a d100, then kill him.
Poor Beefy! Little did he know that zombies were only the start of his problems!
Before we can manipulate Beefy, we need to find his object reference, which is
the unique identifier that the MUD uses to reference him. Every object on the
MUD has an object reference – including you! You can find yours by doing
something like:
pathof <your_name_here>
69
Epitaph Online
http://drakkos.co.uk
The code you get back is your object reference. Beefy has one of those too, which
you can also find by using pathof:
> pathof beefy
Path of Captain Beefy in northeast corner of the marketplace:
/w/<your_name_here>/deadville/chars/captain_beefy
The object reference of objects change on a regular basis – when you log off and
log back on, your object reference will change. If we want to find what the current
reference of Beefy is, then we can use the find_object function:
beefy = find_object (CHARS + "captain_beefy");
Now, our variable 'beefy' contains, in a real sense, our sweet, innocent Captain
Beefy. And we can do stuff to him. Before we can do that though, we need to
explore the first of our programming structures – the if statement. Oh man, life
is about to get PRETTY EXCITING for you!
If at First You Don't Succeed
The if statement is the first programming structure we're going to learn how to
use. It allows you to set a course of action that is contingent on some preset
factor. The basic structure is as follows:
if (some_condition) {
some_code;
}
The condition is the most important part of this – it's what determines whether
the code between the braces is going to be executed. A condition in LPC is
defined as any comparison between two values in which the comparison may be
true or false. If the comparison is true, then the code between the braces is
executed. If the comparison is false, LPC skips over the code in the braces and
instead continues with the next line of code after the if statement.
The type of comparison depends on which of the comparison operators are
used... these go between the two values to be compared, and determine the kind
of comparison to be used:
70
Epitaph Online
http://drakkos.co.uk
Comparison
operator
==
<
>
<=
>=
!=
Meaning
Equivalence – does the left hand side equal the
right hand side?
Is the left hand side less than the right hand side?
Is the left hand side greater than the right hand
side?
Is the left hand side less than or equal to the right
hand side?
Is the left hand side greater than or equal to the
right hand side?
Does the left hand side not equal the right hand
side?
Let's look at a simple example of this in practise using whole numbers (the int
variable type):
int num1;
int num2;
num1 = 10;
num2 = 20;
if (num1 < num2) {
tell_creator ("your_name_here", "num1 is less than num2!\n");
}
tell_creator ("your_name_here", "I'm out of the if!\n");
So, if the value contained in the variable num1 is less than the value contained in
the variable num2, then we see the message sent to our screen. If it isn't, then we
don't. In either case, we'll see the "I'm out of the if!" message. So, with the values
we've given num1 and num2, our output is:
num1 is less than num2
I'm out of the if!
If we change the two variables around a bit:
num1 = 20;
num2 = 10;
Then all we see is:
I'm out of the if!
An if statement by itself allows for you to set a course of action that may or may
not occur. We can also combine it with an else to give two exclusive courses of
action:
if (num1 < num2) {
tell_creator ("your_name", "num1 is less than num2!\n");
}
71
Epitaph Online
http://drakkos.co.uk
else {
tell_creator ("your_name", "num1 is greater than or equal to num2!\n");
}
tell_creator ("your_name", "I'm out of the if!\n");
So now, if the condition is true, we'll see:
num1 is less than num2
I'm out of the if!
And if it's not, we'll see:
num1 is greater than or equal to num2
I'm out of the if!
We can also provide a selection of exclusive courses of action by making use of an
else-if between our original if (the one that starts the structure) and the
concluding else (if we want one – else is always optional):
if (num1 < num2) {
tell_creator ("your_name", "num1 is less than num2!\n");
}
else if (num1 == num2){
tell_creator ("your_name", "num1 is equal to num2!\n");
}
else {
tell_creator ("your_name", "num1 is greater than num2\n");
}
We can add as many else-ifs as we like into the structure until we get the
behaviour we're looking for.
So, that's what an if statement looks like. Let's tie that into our reset function
above. Any variable that does not have anything in will have a null value (we can
use 0 to represent this in an if statement). So if we want to know if our beefy
variable contains an actual Captain Beefy, ready to torture. We want to see 'if the
beefy variable is not zero', which is written like this:
if (beefy != 0) {
// some_code;
}
We can even write this in a simpler fashion – LPC lets us check to see if a variable
has a value set by simply including it as its own condition in an if statement:
if (ob) {
// some_code;
}
So, that puts us firmly in the position of having met the first of our requirements
to find our Captain exists. Now, let's look at the second requirement – checking to
see if he's in our current room.
72
Epitaph Online
http://drakkos.co.uk
We can do that too by putting an if statement inside our if statement – this is
known as nesting. To tell whether or not Captain Beefy is in the same room as the
code we're working, we use the following check:
if (environment (beefy) == this_object()) {
}
If both of those things are true, then we can have our code to say whether or not
he should have a heart attack – for that, we use the random efun.
if (beefy) {
if (environment (beefy) == this_object()) {
if (random (100) < 10) {
beefy->do_command ("' Oh, goodness gracious me!");
beefy->do_death();
}
}
}
This is not ideal though – in general, having too many nested structures leads to
clunky, inelegant code. There are sometimes very good reasons for code to be
nested, but if you don't have to do it, you shouldn't. In this case, we wouldn't have
to do it if we could get one if statement to check for both things. Luckily, that's
something we can indeed do!
Compound Interest
We're not restricted to having a single condition in an if statement – we can link
two or more together into what's called a compound conditional. To do that, we
need to decide the nature of the link.
Things become more complicated the more conditions that are part of a
compound – we can have as many as we like, but let's start out as simply as we
can. Because we only want to execute the code in our if statement if both
conditionals are true, we use the and compound operator. In LPC, this is
represented by a double ampersand: &&. If we wanted the code to be executed if
one condition or the other were true, we'd use the or operator, which is a double
bar: ||.
We can join our two if statements together into one Beautiful Whole using our and
operator:
if (beefy && environment (beefy) == this_object()) {
beefy->do_command ("' Oh, goodness gracious me!");
beefy->do_death();
}
This loses the random element, so he'll die every single time – but we can either
reincorporate it as part of the compound:
73
Epitaph Online
http://drakkos.co.uk
if (beefy && environment (beefy) == this_object() && random (100) < 10) {
beefy->do_command ("' Oh, goodness gracious me!");
beefy->do_death();
}
Or, we can have a nested if – in this case, it's okay because it actually does make
the code a bit more readable and makes it obvious what's supposed to happen if
Beefy is in the room:
if (beefy && environment (beefy) == this_object()) {
if (random (100) < 10) {
beefy->do_command ("' Oh, goodness gracious me!");
beefy->do_death();
}
}
That's much neater all around.
It's sometimes confusing to new coders which of the compound operators they
want for a particular situations. There is a concise representation of what each of
these conditions means – it's called a truth table. The truth table for and is as
follows:
First Condition
false
true
false
true
Second Condition
false
false
true
true
Overall Condition
false
false
false
true
This means that if both conditions in the compound evaluate to true, only then is
the overall condition that governs the if statement evaluated to true. In all other
cases, it is evaluated to false.
For or, the truth table looks like this:
First Condition
false
true
false
true
Second Condition
false
false
true
true
Overall Condition
false
true
true
true
More complex conditionals can be built by linking together conditional operators.
That's a discussion for a later chapter though.
74
Epitaph Online
http://drakkos.co.uk
Conclusion
We've now hooked up our NPC and our rooms – and in the process incorporated a
header file that spans multiple directories. Not only is our area starting to shape
up in terms of the contents and the features, we're doing it in such a way as to
guarantee the maintainability of the code. That's incredibly important, although I
appreciate it may appear underwhelming for now.
We're still not talking much about code, although you've now been introduced to
the first of your programming structures – the function, and the if statement.
You've reached a point where you now have the capability to make objects react
intelligently to the circumstances in which they find themselves – that's
tremendously powerful! Onwards and upwards!
75
Epitaph Online
http://drakkos.co.uk
Back to the Beginning
Introduction
In this chapter, we're going to take a further look at the code we can make use of
in our rooms. Hardly any of our rooms have any descriptions, and we still need to
discuss some of the cooler things that can be done with add_item, as well as the
way in which we can provide more realistic descriptions by incorporating the
changes between night and day. This requires us to write rooms with two sets of
descriptions, but the effect is really very appealing.
We're also going to make Captain Beefy wander around this fine village of ours,
and add a special skill based search to one of our rooms. It's all very exciting!
Touch your nose!
Captain Beefy's Return
First, we're going to make Captain Beefy wander around our village – after all, it
gets so lonely when we're left by ourselves. NPCs wander according to a series of
move zones that are defined. They are defined firstly in themselves (to determine
what zones an NPC may roam within) and secondly in the rooms (to define to
which zone a room belongs). We're going to define all of Deadville as a single
zone, so add this to each of your rooms, somewhere in the setup function. It
doesn't especially matter where.
add_zone ("deadville");
We can add multiple zones to a room, allowing for NPCs to have shared but
distinct wandering areas.
Once you've added that zone to each room, we need to add the correct zones to
Captain Beefy. In his setup, add the following:
add_move_zone ("deadville");
set_move_after (60, 30);
We use add_move_zone to setup which zones within which our NPC is permitted
to roam. We use set_move_after to set the speed Beefy will wander – the first
number sets the fixed delay, and the second sets the maximum number of
random seconds that is added to that delay. With that code, Beefy will wander
every 60 to 89 seconds.
That's enough to set Beefy wandering around our village. It's quite a hassle to
manually add a zone to every room – there are ways and means by which the
Industrious Creator can avoid this hassle, but they're a bit too advanced for us at
the moment. We shall thus simply live with the inconvenience. Introductory LPC 2
will open up new worlds of shared functionality for us.
76
Epitaph Online
http://drakkos.co.uk
The Road Less Travelled
We're going to return to street_03 here – it's still set as a skeleton room and has
no long description, or any items. We're going to use this blank canvas as the
exploration point for some new functionality.
First of all, what we've done for the items and long in street_01 isn't, strictly
speaking, correct. Oh, it works and it does what we said it would, but it doesn't
capture the dynamism that we normally associate with Epitaph MUD. On your
travels, you have undoubtedly noticed how in many areas the room descriptions,
add_items and even chats in a room are different when it's night to when it's day.
All rooms on the MUD should include this basic level of responsiveness to the
world.
Instead of using set_long, we use a pair of related methods – set_day_long and
set_night_long. Functionally, they are identical to set_long except that they are
time of day dependant. The MUD itself handles all the switching between the right
version of the long, you just need to tell it what the long should be. Like so:
#include "path.h"
inherit STD_OUTSIDE;
void setup() {
set_short ("skeleton room");
add_property ("determinate", "a ");
set_day_long ("This path is lit by bright, beautiful daylight. "
"From the sun. High above. Because it's daytime, see?.\n");
set_night_long ("It's dark here, because it's night-time. As opposed "
"to day time. Do you understand what I mean?");
set_light (100);
add_zone ("deadville");
add_exit ("east", ROOMS + "market_southwest", "path");
add_exit ("southwest", ROOMS + "street_02", "path");
}
Note that we don't use set_long at all – we only use that for rooms in which the
description does not change at all from day to night – an underground passage,
for example, would fit that bill.
Similarly, we can add day and night items to reflect the changing situations
described in our longs:
add_day_item ("bright beautiful daylight", "It illuminates the world "
"around you!");
add_day_item ("sun", "It burns our eyes, precious!");
add_day_item ("daytime", "That's what it is.");
add_day_item ("nighttime", "Don't worry, it'll probably be daytime "
77
Epitaph Online
http://drakkos.co.uk
"forever. No need to fret.");
add_night_item ("nighttime", "It's night, alright. You can tell by all "
"the dark around.");
add_night_item ("dark", "You can't see the dark, because it's too dark.");
add_night_item ("daytime", "Those halcyon hours are gone for good, or at "
"least until the sun comes up again.");
add_day_item ("sun", "There's no sun, because it's NIGHT.");
If we want things that are available for day and night, we just use a normal
add_item - but only if they shouldn't change their appearance.
We can also enrich our rooms with alternating day and night chats:
room_day_chat ( ({ 120, 240, ({
"The daytime is full of sunlight.",
"You can see everything around you, because of the sun.",
"The sun is shining in the sky.",
})}));
room_night_chat ( ({ 120, 240, ({
"It is pitch black. You are likely to be eaten by a grue.",
"Was that a grue? It sounded like a grue.",
"If that's not a grue you can hear, it might be Vashta Nerada.",
})}));
Obviously these are all terrible descriptions and violate all conventions of what
you should put into long descriptions, items and chats – that doesn't matter for
now because our focus is on function not form. Simply providing day and night
descriptions goes a long way to increasing the sense of richness people
experience in your areas, and you should definitely get into the habit of writing
them. It adds a fair bit of extra work to room development, but the payoff is worth
it. It's one of the reasons why our MUD looks so much slicker than many.
Sadly, we have to wait for the hours to tick by before the MUD swaps from night
to day, so let us leave our descriptions there. You can use the check command to
verify that they are present, but you'll need to wait until the time of day changes
before you can come back and see them in their proper context. So let's move on
to a different topic while we wait for the cruel, unyielding sun to set on our
development.
Bigger, Better, Faster
Earlier in these documents, I mentioned that add_item was tremendously
powerful. It is – it lets you do all sorts of things with your items that you may not
have realized. In this section, we're going to look at some of the things that
add_item lets us do. Note that we need to use a more complicated version of the
add_item syntax to do all of these things – rather than just giving the name of the
item and its description, we need to specifically state which parts of the add_item
we're setting.
First of all, if an item is large and solid enough for people to sit, or stand, or kneel
on – we should let them do that. We do that by adding a position tag to the item,
giving the string that should be appended to the position, like so:
78
Epitaph Online
http://drakkos.co.uk
add_item ("jagged rock", ({
"long", "This is a jagged rock.",
"position", "on the jagged rock"
}));
Add this to your street_03 room, and then update. You'll now find that you can 'sit
on jagged rock' – but careful, that's bound to hurt. You can lie on it, stand on it,
kneel on it... the usual suspects in terms of interaction choices. If you sit on it,
everyone will see something like 'Drakkos is sitting on the jagged rock.". The text
you set in the add_item is what defines how that message appears.
You can add interaction options to the items too – for example, if I wanted to
make it possible to kick the stone, I add a kick tag to the code:
add_item ("jagged rock", ({
"long", "This is a jagged rock.",
"position", "on the jagged rock",
"kick", "Ow! That stung!\n"
}));
Note that you need to end any specific verb response you define with a newline
character. There's no limit to what verbs you may include – it's just a string of text
that gets shown to the player when they attempt to use that verb on your item.
You can provide synonyms for verbs too:
add_item ("jagged rock", ({
"long", "This is a jagged rock.",
"position", "on the jagged rock",
({"kick", "punch"}), "Ow! That stung!\n",
}));
Next, we're going to add a search profile to another room in the village – search
profiles are ways of providing a set of items that can be searched up, limited by
keywords, skill checks, or availability. Whatever you like, really.
Search Profiles
So, let's add a search profile to market_southwest. The function for this is called
add_search_profile, and requires four bits of information – the first is a
keyword, or list of keywords – these are the things you'll need to search for in
order to find the associated items. The second piece of information is the random
categories that might be found on a search, and the third is the random items.
The fourth piece of information is the number that can be found every reset.
Let's start off simple – let's make it so people searching a 'stall' might find a bottle
of water. Our function, in setup, would look like this:
add_search_profile ("stall", 0, ({"bottle of water"}), 1);
That's it – everything else gets handled for us. Provided someone searches the
79
Epitaph Online
http://drakkos.co.uk
stall, they will find a water bottle:
> search stall
You start to search around.
As a result of your searching, you see a plastic bottle in a corner.
That's almost what we're looking for, but – 'in a corner'? That doesn't make any
sense – we want the bottle to be on a stall (and we won't spend any time worrying
about how it got there). If we want to make an add_item a potential container for
items that might be found by searching, we use add_scavenge_item rather than
add_item:
add_scavenge_item ("water stall", "There is a stall here, for the "
"selling of water.");
This function causes an object to be created in the room – an actual piece of
furniture that can be manipulated in the usual ways. This means that you don't
get all the flexibility of add_item, but that's okay – you get all the functionality of
a piece of furniture instead. Anyway, when searching the stall, we now see:
> search stall
You start to search around.
You see a plastic bottle on a stall.
That's much better.
Search profiles are much more powerful than this – there are existing profiles that
contain preset lists of items. For example, let's add in a second search profile:
add_search_profile (0, 0, "generic food", 1);
This search profile requires no keyword, and will draw on the items already set up
in the "generic food" search profile, which contains all the packaged food in the
game.
The Taskmapper
Now that we've added a few features here and there, we're going to go back to our
rock and add a command that is a bit more interesting – the ability to 'vault' the
rock. We need to use a little, complicated looking bit of code here, and we won't
really explain it until the intermediate LPC documentation. Adjust your rock item
like this:
add_item ("jagged rock", ({
"long", "This is a jagged rock.",
"position", "on the jagged rock",
"vault", (: do_vault :),
({"kick", "punch"}), "Ow! That stung!\n",
}));
When you update your room, you'll find that it no longer works – that's because
80
Epitaph Online
http://drakkos.co.uk
what the (: do_vault :) part of the code says 'I want you to use a function to
evaluate this', and your room doesn't know anything about a do_vault function.
We fix this by putting underneath our inherit line the following:
string do_vault();
And underneath our setup:
string do_vault() {
return "You vault the rock.\n";
}
We'll return to this topic later, but what we've done here is link up our rock, a
'vault' command, and a function that we can use to determine what our player
should see when they attempt to vault it. Right now, you can vault it with no
problem:
> vault rock
You vault the rock.
What we're going to do is make it skill checked. In order to do that, we need to
talk about Epitaph's task mapping system.
Task Mapping
Epitaph uses a system that maps task codes to skills, and uses a centralized
system for setting difficulties and modifiers on tasks. This means you don't get
fine grained control over the bonuses required to perform certain tasks, but from
the perspective of the overall game balance of the MUD, it means it's very easy for
us to adjust game difficulty across the board.
In order to use a skill check, you must include two header files in your code –
tasks.h, and task_mapper.h:
#include <tasks.h>
#include <task_mapper.h>
These make available the various defines that you need in order to do a skill
check. One of these is TASK_MAPPER, which makes use of a piece of code known
as a handler – handlers are LPC objects that have functions designed to handle a
particular task within the game. Again, this is something you don't need to worry
about much for now.
To see the range of tasks available in the game, you can use the taskmapper
command, like so:
taskmapper list
This provides you with a description of the tasks, and the skills they use.
However, when you want to use one of these tasks, you must use the defined
81
Epitaph Online
http://drakkos.co.uk
value which is set in task_mapper.h. All of the tasks are located in
/include/task_mappings/, and you can quickly search through these headers
using the grep command. If you wanted to find the tasks that mention vaulting,
you could use:
grep vaulting /include/task_mappings/*.h
And as a result, you would get:
include/task_mappings/adventuring.h:#define TASKMAP_VAULT_BARRIER "vaulting a
barrier"
It is the code in bold that you will use in your task mapping.
Vaulting a barrier seems like a pretty good description for vaulting a rock, so we'll
use that in our call to the task mapper.
The task mapper has two versions – one is a simple check, which gives either a
success or a failure value. The other is a complex check which involves criticality
(degrees of success). We'll use the former, leaving the latter for a later chapter of
a later book (it's a good deal more complicated). Much like with when we used
find_object, we need something to hold the result of our skill check, and this will
be an int:
int result;
Then we do the skill check, passing in several pieces of information:
result = TASK_MAPPER->perform_task (this_player(), TASKMAP_VAULT_BARRIER,
TTSM_DIFF_INCREDIBLY_EASY, 0, TM_FREE);
Yikes, that does look complicated – but the good news is, it's mostly stuff we've
already seen. The first part of the function call to perform_task is the object
doing the task. This_player() is a special function and it gives us the object
that triggered the command (which is, in this case, us). The second piece of
information is the task – TASKMAP_VAULT_BARRIER, as we saw above in our grep.
The third part is how difficult the task is – this is a define set in task_mapper.h,
and TTSM_DIFF_INCREDIBLY_EASY is the easiest of tasks. We can set modifiers
on the performing of a task (to represent especially favourable or unfavourable
conditions), but we don't have any of those so we just pass in a 0. Finally, we
have the TM type – these set the frequency with which TM awards are made. Since
this consumes no GP, it is a TM_FREE.
What comes out of our perform_task is a number that represents the result of the
skill check. It will have one of three defined values:
82
Epitaph Online
http://drakkos.co.uk
Result
TASKER_AWARD
TASKER_SUCCEED
TASKER_FAIL
Meaning
The player passed the check and
also gained a skill level and
should be given an appropriate
message to indicate that. The
awarding of the level is handled
for you.
The player succeeded in
performing the task.
The player failed to perform the
task.
We need some code in place for all of these possibilities. When we want to
provide a TM message to a player, we use the taskmaster_mess function – that
ensures that messages honour player colour settings. Our final do_vault function
will look like this:
string do_vault() {
int result;
int success;
result = TASK_MAPPER->perform_task (this_player(), TASKMAP_VAULT_BARRIER,
TTSM_DIFF_INCREDIBLY_EASY, 0, TM_FREE);
if (result == TASKER_AWARD) {
taskmaster_mess (this_player(), ({"You feel more vaulty!",
"You feel pretty nimble!"}));
success = 1;
}
else if (result == TASKER_SUCCEED) {
success = 1;
}
else {
success = 0;
}
if (!success) {
return "You try to vault the rock but land flat on your face.\n";
}
else {
return "You vault the rock like a pro!\n";
}
}
Now, when you try to vault, sometimes you will succeed and sometimes you will
fail, and it will be based on your skill levels. You'll also, on occasion, get a TM for
it.
The task mapping system is a little complex to begin with, but it gives us a huge
number of benefits. If later on, we need to change the name of our skills for
example, we don't need to change every skillcheck manually – we just change it in
the task mapper and every piece of code uses the new skill name. Don't worry,
you'll get used to it!
83
Epitaph Online
http://drakkos.co.uk
Switching Things Around
The structure we have in place here is rather clunky – luckily LPC provides us with
a more elegant alternative – the switch statement. A switch is essentially a
compact representation of a complex if, else-if, else structure. First, we choose a
variable to switch on – in this case, it's result. We then define a case for each of
the possible alternate values the switch variable may have. The code that follows
the case will be the code that is executed if the switch variable has the specified
value.
result = TASK_MAPPER->perform_task (this_player(), TASKMAP_VAULT_BARRIER,
TTSM_DIFF_INCREDIBLY_EASY, 0, TM_FREE);
switch (result) {
case TASKER_AWARD:
taskmaster_mess (this_player(), ({"You feel more vaulty!",
"You feel pretty nimble!"}));
case TASKER_SUCCEED:
success = 1;
break;
case TASKER_FAIL:
success = 0;
break;
}
Switch statements are somewhat more flexible than if statements, because each
case is fall-through. That means that when the MUD finds a matching case
statement, it executes that statement and every statement that follows until it
finds a line of code marked as break.
For the above code, if the result is a TASKER_AWARD it will display the TM
message, and then continue on to the next case statement (TASKER_SUCCEED). So
getting an AWARD gives the TM message and sets the success variable to 1. It
then stops, because it hits a break. If the result is TASKER_SUCCESD, it sets the
success variable to 1 and then stops. If it's a TASKER_FAIL, then it sets the success
variable to 0.
We can add a general catch-all to a switch by adding a special default case. We can
use this to deal with results we didn't anticipate.
switch (result) {
case TASKER_AWARD:
taskmaster_mess (this_player(), ({"You feel more vaulty!",
"You feel pretty nimble!"}));
case TASKER_SUCCEED:
success = 1;
break;
case TASKER_FAIL:
success = 0;
break;
default:
tell_creator ("your_name", "Yeah, I don't know what happened here.\n");
break;
}
84
Epitaph Online
http://drakkos.co.uk
Aside from this new structure, the code should be fairly self explanatory – if the
skill check succeeds, the player leaps like a gazelle. If it fail, they lose their dignity
as they fall flat on their face.
However, we're not done yet – TM_FREE only reduces the chance of a TM, it
doesn't actually remove it – people can vault our rock as many times as they like
to try and TM. Let's be draconian and stop that – we'll make it so a rock can only
be vaulted once per reset. Yes, I know that doesn't make any sense, but it's a
useful conceit because it introduces the newest programming concept we need to
address – scope.
Scoping Things Out
So, how do we limit the number of times a rock can be vaulted? The obvious
thought is to use a variable – something like found, in fact. How about if we just
put a check at the top to see if a 'jumped' variable has been set to 1... will that
work?
string do_vault() {
int result;
int success;
int jumped;
if (jumped) {
return "You can't vault the rock because it has already been vaulted.\n";
}
result = TASK_MAPPER->perform_task (this_player(), TASKMAP_VAULT_BARRIER,
TTSM_DIFF_INCREDIBLY_EASY, 0, TM_FREE);
switch (result) {
case TASKER_AWARD:
taskmaster_mess (this_player(), ({"You feel more vaulty!",
"You feel pretty nimble!"}));
case TASKER_SUCCEED:
success = 1;
break;
case TASKER_FAIL:
success = 0;
break;
default:
tell_creator ("your_name", "Yeah, I don't know what happened here.\n");
break;
}
jumped = 1;
if (!success) {
return "You try to vault the rock but land flat on your face.\n";
}
else {
return "You vault the rock like a pro!\n";
}
}
85
Epitaph Online
http://drakkos.co.uk
Alas, it turns out no. The code doesn't seem to do anything. Why is that?
The answer lies in a programming concept called scope. Every variable that is
created takes up memory on the computer in which the program is running. This
is true regardless of whether the code is on a MUD or on your own computer.
In order to ensure that this memory is made available when you are finished with
it, the computer frees up the memory it has allocated once the variable falls out of
scope. Variables that are defined within a function are called local variables, and
exist only as long as that function is executing. Once your do_vault function has
finished executing, the memory location occupied by the found variable is
released. Then, when it is vaulted again, a new variable is setup and starts with
the value 0 until it is again set by later code. The scope of the variable is the
function in which it is defined. That also means that you cannot make use of that
variable in other functions.
We can move a variable declaration to the start of the object itself, after the
inherit and before the setup:
inherit STD_OUTSIDE;
int jumped;
string do_vault();
This increases the scope of the variable to be class-wide. It is available to all
functions, and persists as long as the object is loaded. Every function can change
the state of the variable, but that value the variable gets persists as long as the
object is loaded. Sounds like just what we need!
Update your room and vault the rock – You'll get to do it once and then you'll be
told you can't do it again. That's a win!
The last part of getting this working properly is to reset the value of found every
time reset is called. That bit, at least, is easy:
void reset() {
::reset();
jumped = 0;
}
You can test this quite easily – update, search the rock, search it again and get the
'already searched' message. Then, call reset on the room manually:
call reset() here
Vault once more, and you'll see you're permitted to do it again, as if another reset
period had just passed. Pretty neat, huh?
86
Epitaph Online
http://drakkos.co.uk
Conclusion
We're starting to pick up speed in our discussion of LPC – in this chapter we've
learned about movement zones, the task mapper, search profiles, switch
statements and variable scope. That's a lot to digest, and you may want to step
away from the tutorials at this point to allow the information to sink it. It's a good
idea to practice with all of this – try setting up other items you can manipulate
with functions in other rooms. Practise is the way to gain understanding, after all.
You should be feeling quite proud of yourself at this point – you're starting to add
some fairly sophisticated behaviour to your rooms, and your capabilities are only
going to grow as we continue.
87
Epitaph Online
http://drakkos.co.uk
Now That We're an Item
Introduction
So, we've got an NPC, and we've got some rooms – the next thing we need to learn
how to develop is an item. This is somewhat more complicated than developing
either of the others because of the sheer variety of items that can exist – clothes,
weapons, armours, backpacks – all of them are created using different inherits
and code. There are some commonalities to be sure, but they each have their own
little quirks that you need to learn.
More than this, there are two entirely different ways of creating items – one way is
a variation of what we've done before – we write an object, and clone it when we
need it. The other way is using the MUD's virtual item system. We'll discuss both
of these in the course of this document.
Virtually Self-Explanatory
Every object that is loaded on the MUD takes up memory on the system – because
there are So Many Items carried by So Many NPCs and Players, there's a huge
performance gain to be had by reducing the memory required for these items.
The Virtual Item system was introduced to reduce the memory load on our poor
little server.
Remember how we talked briefly about the idea of a master object from which we
create clones? Every .c file that is loaded on the MUD is a master object, and a
master object comes with a memory burden. Virtual objects are just clones of an
already existing master object, with all the functions we'd normally associate with
setup (such as setting the name, short, and so on) are handled by the MUD as a
series of calls. That may sound confusing, so I'll give an actual example of what
this does when we've discussed our first virtual object.
Virtual object code files don't look like normal code files – they have their own
syntax, and that can be quite confusing. They also have an extension other than
.c, and the extension tells the MUD what kind of object we're working with. The
basic extensions you'll be working with are as follows:
88
Epitaph Online
Extension
.clo
.arm
.jew
.wep
.ran
.food
.sca
http://drakkos.co.uk
Type of Item
Clothing
Armour
Jewellery
Meele Weapons
Ranged weapons
Food
Scabbards
In Game Directory
/items/clothes/
/items/armours/
/items/jewellery/
/items/weapons/
/items/weapons/ranged/
/items/food/
/items/scabbards/
There are more of these, but we're only going to discuss clothing in this section.
You should browse the indicated directories for examples of other kinds of virtual
objects.
Okay, let's start by creating a new directory in our Deadville directory – this one
will be called items.
mkdir /w/your_name_here/deadville/items/
And we'll need to add a new entry to our base path.h file in
/w/your_name_here/deadville/:
#define
ITEMS
PATH + "items/"
Now, create this file in your new directory, under the name beefy_boots.clo:
::Name::"boots"
::Short::"pair of beefy leather boots"
::Adjective::({ "pair", "of", "beefy" })
::Main_plural::"pairs of beef leather boots"
::Plural::"boots"
::Plural Adjective:: "pairs"
::Long::"This is a pair of extremely beefy leather boots.
"need to be very beefy indeed to wear these!\n"
::Material:: (["leather" : 1])
::Type::"boot"
A person would "
That, my young friends, is a virtual object. Although it looks entirely different
from the LPC code with which you are slowly becoming accustomed, you should
be able to see commonalities. Virtual files come as a list of settings, with the
values those settings are to have. We update these virtual files in the same way as
we do normal files, and we likewise clone them in the same way – clone a pair of
them into your inventory once you've updated them.
From the perspective of the person who has them in their inventory, they are
indistinguishable from normal LPC objects. That's because that's exactly what they
are – they're just written in a different way, and the MUD creates them in a
different, more efficient way.
When you tell the MUD to clone a virtual item, it is the extension of the file that
tells it what base object it needs to make a clone of – in the case of clothing, it's
/mudlib/inherits/clothing.c. It just takes a copy of this object, which has all
the functionality but none of the configuration details, and it takes your virtual file
89
Epitaph Online
http://drakkos.co.uk
as a template for what it should do with it.
It the goes over each of the lines in the virtual file you gave it. The presence of the
double colons gives a set pattern for the MUD to parse – It knows that the
whatever comes between the first set of double colons and the second set is the
name of the setting it needs to change, and whatever comes after the second set
of double colons is the value that setting should have.
When it encounters the setting marked name, it knows that it translates that into
calling set_name on the object it has cloned. The value for the set_name is what
follows the second set of double colons. Likewise, when it gets to the setting
short, it knows to call set_short.
Don't copy the next bit of code into your project, it's for explanation only.
Essentially cloning this virtual clothing file is the same thing to you doing the
following:
object ob = clone_object ("/obj/clothing");
ob->set_name ("boots");
ob->set_short ("pair of beefy leather boots");
ob->add_adjective (({"pair", "of", "beefy"}));
ob->set_main_plural ("pairs of beefy leather boots");
ob->add_plural ("boots");
ob->add_plural_adjective ("pairs");
ob->set_long ("This is a pair of extremely beefy leather boots.
"person would need to be very beefy indeed to wear these!\n"
ob->set_material ((["leather": 1]));
ob->set_type ("boot");
A "
It probably won't be obvious at this point why this is a good way to do things –
trust me when I say though it saves on the memory the MUD uses, and that's a
very good thing. The MUD routinely sits at around one hundred and fifty
megabytes of memory usage, and it would be a great deal higher still if it weren't
for systems like this to keep the requirements low.
Virtual files are perfect for providing simple behaviour, but they do not offer a
facility for more complex behaviour. Essentially anything that involves you adding
commands, special defences, or general 'less than usual' functionality. For that,
we must rely on a standard .c file. We'll see that in practise when we progress
onto Intermediate LPC, later in this book.
Beefy's Boots
Now that we have a pair of delicious beefy boots for our NPC, let's give them to
him! Sadly, we can't do this through request_item. Not yet.
Request_item works through a handler called the armoury, and the armoury
works only on 'live' code. In general we don't want personal creator code (residing
in /w/) to be handled through the armoury. The armoury makes a list (and checks
it twice) of all the items in the in game directories (such as /items/clothes,
90
Epitaph Online
http://drakkos.co.uk
/items/armour, and so on) and then a list of all the items available to the domain
items directories (/d/game/items and so forth). What it doesn't do is make the
code in your home directory available, or code that is in non-supported directories
(all domain objects have to be in /d/domain/items or a subdirectory, for
example).
We're thus going to have to handle the provision of a pair of boots manually. First,
we make ourselves a container for the boots at the top of Beefy's setup:
object boots;
Then we clone the boots using the ITEMS #define we added earlier. First though
we'll need to add a #include to our path.h at the top of his file, since we currently
don't have one.
boots = clone_object (ITEMS + "beefy_boots.clo");
boots->move (this_object());
Dest and update Beefy and the room from which he is cloned, and you'll find he's
now wearing the lovely boots we created for him!
Bling
Let's have a look at a second kind of item created using the virtual object notation
– we're going to give Beefy a ring that was given to him by his Dearly Departed
wife. Any player who kills him for his jewellery will thus feel like a Real Bastard.
Jewellery is created according to the same basic system, although the specifics of
the setup are slightly different:
::Name::"ring"
::Short::"beefy diamond ring"
::Adjective::({"beefy", "diamond"})
::Plural::"jewellery"
::Alias:: "jewellery"
::long::"This is a beautiful golden ring set with a gleaming diamond.
"It smells vaguely of beef.\n"
::Type::"ring"
::Material:: (["gold" : 1])
"
Save this one as beefy_ring.jew. Most of this should be fairly obvious by now –
it shares most of the settings with clothing. How do people know this is a ring
given to him by his dead wife? Well, let's add an engraving to it so that people can
read the loving message she left for him. We can do this directly in the virtual file,
if we like:
::Read Mess:: "From your dead wife.", "neat engraving", "english"
The first part is the deeply moving message. The second is how the writing is
described when someone reads it, and the third is the language in which the
message is written. Reading this ring thus will give the following:
You read the beefy diamond ring:
Written in neat engraving:
From your dead wife.
91
Epitaph Online
http://drakkos.co.uk
Tragically moving, isn't it?
It's not usually a good idea to include such a read message in the base file,
because as far as possible virtual files should be entirely generic for what they
are. If we include it in the base file, then anyone who gets a Beefy Ring gets it
inscribed with the beautiful poetry of Beefy's wife.
Instead, we can configure this message after the ring has been cloned – that way
the basic ring is defined and usable by anyone, but only Beefy's ring has the
engraving.
We make the ring available to Beefy in exactly the same way as we made the boots
available. We define an object at the top of his setup:
object ring;
Then we clone and move that ring into him:
ring = clone_object (ITEMS + "beefy_ring.jew");
ring->move (this_object());
And then we manually add the read message afterwards:
ring->add_read_mess ("From your dead wife.", "neat engraving", "english");
Almost every item in the game can have a read message attached to it in this way,
and it's a nice way to add a little bit of sparkle to otherwise non-specific items.
Item Normalisation
In order to achieve a measure of consistency across Epitaph, most of our items
make use of a normalization setting – virtual files permit the setting of much of
the 'appearance' of an item, but things like weapon damage, weight, condition
and monetary value are all 'normalised' – they're all calculated from a central
source.
While that does limit your ability to differentiate the items you create, it comes
with many benefits for us – it means we don't need to worry about things like new
creators developing super weapons – all the values are handled internally. The
weight of the items you create will be based on, for example, the type of item, its
material composition, and so forth.
92
Epitaph Online
http://drakkos.co.uk
Long Dollars
One of the more frustrating aspects of item utilization is when you see something
with a filename that looks as if it is the exact item you want. You're looking for a
black shirt, and you see /items/clothes/black_shirt.clo, and you go 'Ah yes, my
prayers have been answered!'
But then you clone that black shirt, and what you find is that it is a ripped,
mutiliated black shirt coated in blood and through which your nipples can be
seen. All you wanted was a black shirt that anyone could wear, but that clothing
is not what you need.
We get around this problem on Epitaph by ensuring that items in the /items/
directory are truly generic, and this is done by using what are known as long
dollars. If we look at /items/clothes/shirts/simple_shirt.clo, for
example:
::Name::"shirt"
::Short::"$colour$ $material$ shirt"
::Long:: "This is a simple $colour$ $material$ shirt. It has absolutely "
"no features that distinguish it from any other shirt you may have "
"seen in your life.\n"
::Type:: "shirt"
::Material:: (["linen":1])
::colour:: "white"
::Common Materials:: ({"silk", "cotton", "velvet", "chiffon", "satin",
"wool"})
::Common Colours:: ({"white", "black", "grey", "blue"})
There are several features here that aren't present in the boots you created above
– for one, there is no direct mention of colour in the short or the long description.
The MUD knows what you mean when you put $colour$ in a short or long – it
means, 'the colour this item has been set to'. Similarly with material – it will
automatically replace the $material$ code with the material of the item. The
result is that I can use set_colour and set_material on this base shirt to create a
black silk shirt, a white linen shirt, or a pink leather shirt if I so desire.
Common materials and common colours are part of this system – when random
items are searched up, they will be set to have one of these materials and one of
these colours – that way people don't always find the white linen shirt – they
might just find a blue wool shirt instead.
In the items you create, please use this long dollars system – it gives us the
greatest benefit from the items you develop.
More Item Additions
It's not much of a horror setting if we never describe the carnage of the postinfection world. Things should be smeared with blood, or dripping with entrails,
or whatever. There has been an intensely violent, bloody daily battle against the
93
Epitaph Online
http://drakkos.co.uk
undead, and that leaves traces behind.
However it's usually not particularly effective to describe that in descriptions. I
could have for, example a bloody baseball bat, but if every bloody baseball bat
has the same gore on it, it looks a bit artificial. Also, what if I want to clean it off?
To deal with this, Epitaph has a system of automated gore. To enable it on an
item, you use the following in its setup:
set_gore (1);
You can then see the gore added to the description of the item, like so:
This is a steel bladed machete. Machetes are long knives used for hacking
away at vegetation and undergrowth. They also make pretty serviceable
cleavers for hacking away at limbs.
You can see a bloody handprint on the machete.
The gore message is generated from a random set of messages and a random set
of locations (Such as 'There is a bloody handprint on the $bit$ of $the short$').
This makes it work with the cleaning system that is available through the 'wipe'
command
.
That's straightforward for items, but to see how it works in the add_items we put
in our game, we need to handle it as one of the more complex syntaxes for
add_item, like so:
add_item (({"stone floor", "stone wall", "stone"}), ({
"long",
"There's little to distinguish the stone floor or walls. "
"They're just stone... immaculate.",
"gory",
"stones"
}));
When a room gets a gore message added, it picks out all the items that have been
marked as potential targets for gore, picks one of the items you provide as a bit,
and generates a message something like:
There is a bloodstained handprint on the stones of the stone floor.
If you want it to say something like:
There is a bloodstained handprint on the stone floor.
You simply make the the add_item reference the string for the add_item, like so:
add_item (({"stone floor", "stone wall", "stone"}), ({
"long",
"There's little to distinguish the stone floor or walls. "
"They're just stone... immaculate.",
"gory",
"stone floor"
94
Epitaph Online
http://drakkos.co.uk
}));
Finally, you can also indicate that an add_item should provide cover and
concealment by setting the item as having 'cover' - this makes it available for
people to take cover behind in the event of gun-fire. This is done using the normal
position commands, but with the 'behind' tag, like so:
> crouch behind counter
You crouch behind the fixed counter.
At this point, you gain cover, the amount of which is determined by the add_item,
or the cover property of a normal item. Using both cover and gore, you'd create
something like this:
add_item ("fixed counter", ({
"long", "The contents of the counter have long since been looted, "
"but the counter itself is immovable.",
"position", "the fixed counter",
"gory", "fixed counter",
"cover", 10
}));
The value for the cover part of the add_item is a percentage modifier that is given
to the defender when they are attacked by firearms (and also a bonus provided to
stealth checks when using a stealth type that takes into account cover). Ten is a
roughly appropriate default for this, unless the item is especially solid (like a brick
wall).
Conclusion
We've looked at two kinds of item here – a pair of boots, and a ring. All virtual
objects work on largely the same general principles – a file contains a number of
settings and the values those settings have. The MUD works out what kind of base
object is needed from the extension the virtual object has. It then clones an
instance of the base file and configures it with calls (rather than creating a new
master object). As far as anyone using the item is concerned, it works identically
to any other object written in LPC.
Item normalization, and the long dollars system, give us an awful lot of freedom
when developing items – we don't need to be constrained by worries of game
balance or mini-maxing low level values. We have the freedom to concentrate
entirely on the things that make our items unique. We don't even need to worry
overmuch about colours or materials – we can let people set those as they like
when they make use of the items.
In short, these systems give us the freedom to be creators, not accountants
fretting over a spreadsheet of implications.
95
Epitaph Online
http://drakkos.co.uk
An Inside Job
Introduction
We've got the skeleton of our outside rooms in place already – in this chapter
we're going to look at developing our first inside room – a faction shop in which
we can make available any further items we develop. As a process, this is a largely
identical to creating an outside room, except that we have the extra step of
setting up stock and working with the faction code. We'll have cause to encounter
some new concepts as we go through this chapter, so there's plenty for us to talk
about.
We're actually going to put in the base code for two rooms – one is a vanilla room
that has no special functionality except for a locked door leading to it. The second
is the faction shop. On our village map (remember that from chapters two and
three?), these will be rooms A and B, and have the following filenames:
Map Key
A
B
Filename
mysterious_room.c
stabby_joe.c
So, with no further ado, Let's Get To It!
The Mysterious Room
Firstly, let's create the template for the mysterious room. Everything we've talked
about for our outside rooms works for our inside rooms, but we inherit STD_ROOM
instead of STD_OUTSIDE. So, for the mysterious room:
#include "path.h"
inherit STD_ROOM;
void setup() {
set_short ("mysterious room");
add_property ("determinate", "a ");
set_day_long ("This is a mysterious room during the daytime. It fairly "
"reeks of mystery.\n");
set_night_long ("This is a mysterious room during the nighttime. The "
"oppressive darkness hints mysteriously at mystery!\n");
add_day_item ("mystery", "Daytime all around, and yet you cannot see it.");
add_night_item ("mystery", "You can't see the mystery for all the "
"darkness.");
room_day_chat ( ({ 120, 240, ({
"The room emits a sense of mystery.",
"No doubt about it, this is a mysterious room.",
})}));
room_night_chat ( ({ 120, 240, ({
"Was that a mystery there, glinting in the darkness?",
96
Epitaph Online
http://drakkos.co.uk
"Maybe the mystery is a grue?",
})}));
set_light (100);
add_exit ("south", ROOMS + "market_northwest", "door");
}
Notice here that the exit we add is of type 'door' – that means pretty much what
you'd expect it to mean – the exit is blocked by a door. We need to match this up
with a corresponding exit in market_northwest.c:
add_exit ("north", ROOMS + "mysterious_room", "door");
We've created a door, but it isn't locked. In order to maintain the mystery of the
door, we can use modify_exit to lock it shut. Remember how I said earlier that
modify_exit had all sorts of cool and interesting powers? Well, let's take a look at
what we can do with our mysterious exit here, to heighten it's mystery!
Modifying Exits
The settings to modify_exit come as a listing of pairs of settings and values. With
it, we can change the messages people see when they move through the exit,
what they see when they look at the exit, and how it behaves when people try to
walk through it. We can even add complicated code handlers to an exit – you can
control extremely precisely under what conditions an exit may be taken. If you
want it to be impossible to take an exit while you're carrying iron in your
inventory, you can do that. If you want to make it so that entry is permissible only
on the 25th of December, you can do that too. We won't talk about that in this
section, but we will in a later section of Introductory LPC.
The simplest settings just require some numbers and text to describe what should
happen. Let's begin simply – let's change how the door appears when people look
at it. In market_northwest's setup, we add the following:
modify_exit ("north", ({
"door long", "The door hints at mystery within.
"likely.\n",
}));
Riches too, most "
Now, when we look north we'll see:
> look north
The door hints at mystery within.
It is closed.
Riches too, most likely.
If you go north, and look south however, you see:
> look south
It's just the south door.
It is closed.
The modify exit thus must go in both rooms to which it applies – that way you can
97
Epitaph Online
http://drakkos.co.uk
have doors that have one description on one side, and a different one on the
other. In mysterious_room thus:
modify_exit ("south", ({
"door long", "The door hints at mystery within.
"likely.\n",
}));
Riches too, most "
If we want to lock it (and we do), we add the locked setting to our modify_exit – a
1 indicates the door is locked, a 0 indicates it is not. We also need to provide the
name of a key that will open the door:
So, in market_northwest:
modify_exit ("north",
({
"door long", "The door hints at mystery within.
"likely.\n",
"locked", 1,
"key", "Mysterious Room Key",
})
);
Riches too, most "
mysterious_room will need an identical modify_exit, except that it will modify the
south exit rather than the north one. Update our rooms, and voila! The door is
locked.
Locked doors can be a pain in the backside for a creator, so you can give yourself
the ability to walk through such doors by adding the demon property to yourself:
call add_property ("demon", 1) me
The key to this door is something we're going to make available in our shop... a
key is any object that has a property matching the name we've given to the key in
our modify exit. When the player attempts to take the exit, the MUD looks through
all the items on that player for anything that unlocks the door. You can see this in
action by picking any random item in your inventory and using the following call:
call add_property ("Mysterious Room Key", 1) <some random object>
Make sure you remove the demon property from yourself though, or you'll just
ghost through the door as before:
call remove_property ("demon") me
Modify_exit lets us do much more than we've done here – we can set a door to
automatically lock after we close it with the autolock setting (1 indicating it
autolocks, and a 0 indicating it doesn't). We can set a difficulty for people
attempting to lockpick the door using the difficulty setting – you give this a value
from 1 to 10. The lower the value, the easier the lock is to lockpick – the values,
as you might expect, correspond to the difficulty levels set in task_mapper.h.
We can also change the messages people see when objects pass through the door
by setting three values in the same manner as we did for Beefy's move message.
98
Epitaph Online
http://drakkos.co.uk
For example, we could do the following in market_northwest:
"enter mess", "$N mysteriously enter$s the room. How mysterious!",
"exit mess", "$N exit$s through the mysterious door. What wonders "
"must be found within?",
"move mess", "You walk through the door, excited by the possibilities "
"within!\n",
And in the mysterious_room:
"enter mess", "$N mysteriously enter$s from the mysterious door. What "
"wonders were seen on the other side of that portal?",
"exit mess", "$N exit$s through the mysterious door.",
"move mess", "You leave the room, satisfied its mysteries have been "
"revealed to you.\n",
Enter mess is what people see when someone arrives in a room through the exit.
Exit mess is what people see when someone leaves through the exit.
Move mess is what gets displayed to the player.
So, we've given the door to our mysterious room enough mystery for now. Let's
make the key available to those who may be tempted to explore.
Shop 'Till You Drop
We're going to create a faction shop, which is a shop that is associated with a
faction (that faction has already been created for you). Faction shops buy and sell
using faction reputation rather than anything as gauche as 'money'. The inherit
we use for this is STD_GENERAL_SHOP. Let's create the basic template for that
room, and hook it up to our marketplace:
#include "path.h"
inherit STD_GENERAL_SHOP;
void setup() {
set_short ("Stabby Joe's Emporium of Wonder");
add_property ("determinate", "");
set_long ("This is Stabby Joe's Emporium of Wonder, where he sells "
"wonderful things. He also stabs people.\n");
set_light (100);
add_exit ("south", ROOMS + "market_northeast", "door");
}
And in market_northeast, we need an exit linking back to the shop we're setting
up:
add_exit ("north", ROOMS + "stabby_joe", "door");
Update both rooms, and wander into the shop. Sadly, if you try to list or browse
99
Epitaph Online
http://drakkos.co.uk
you'll find that you can't – it will say:
Please notify a creator: the storeroom for this shop cannot load or has gone
missing.
It's okay, because you're a creator!
The storeroom for a general store is actually another room you create, except
that's not how it works for faction shops. When you create a faction shop, it
draws the inventory of the shop from the faction data file. We just need to link up
this room to the faction system, which we do by adding this line into the shop's
setup:
set_faction ("deadville");
This marks the shop as belonging to the Deadville faction, which is a creator only
example faction. You'll find now that you can browse, list, buy and sell items, and
that it even has some inventory as discussed above. Inventory for this faction is
shared amongst all faction shops, so you may find strange stuff appearing in it as
a result of the actions of your fellow creators!
Stabby Joe
The MUD won't force you to have a shopkeeper for the shop, but it's a good idea
to have one – it adds a sense of immersion that is otherwise lacking. So let's
create our second NPC – Stabby Joe! He's going to be our shopkeeper and resident
psycho. We're going to give him an add_response for his cousin, but we're not
going to do anything with it right away. Save him in your chars directory under the
name joe.c:
#include <npc.h>
#include "path.h"
inherit STD_MONSTER;
void setup() {
set_name ("joe");
set_short ("Stabby Joe");
add_property ("determinate", "");
add_property ("unique", 1);
add_adjective ("stabby");
add_alias ("stabby");
set_gender (1);
set_long ("This is Stabby Joe, Deadville's main shopkeeper and "
"resident psycho. While generally at peace, he can be riled "
"into fits of towering rage by making reference to his cousin, "
"Slicey Pete.\n");
basic_setup ("human", "survivor", MONSTER_LEVEL_NORMAL);
setup_nationality (MUDLIB_NATIONALITIES + "british", "scotland");
load_chat(10, ({
1, "' Buy my stuff, or I'll kill you.",
1, "' Good prices on all my stuff! If you can find anything cheaper "
"in all of Deadville, I'll kill you!",
1, "' Satisfaction guaranteed, or I'll kill you!",
100
Epitaph Online
http://drakkos.co.uk
}));
load_a_chat(20, ({
1, "' cut cut cut cut cut cut cut cut!",
1, "' Gods, that was violent! I blame... the sea!",
}));
add_response (({"slicey", "pete"}), "#cousin_response");
dress_me();
}
Everything here is As We Know It. We load him in a room slightly differently
though – we just call set_shopkeeper in the shop and give the path to the NPC.
The general shop code will handle the rest, including bringing him into the room
on resets. So, in our stabby_joe shop:
set_shopkeeper (CHARS + "joe");
Update the room, and there's our shopkeeper. You will find, right away, that if
you dest Joe you'll no longer be able to make use of his shop – the facilities are
available only while he is there to serve you.
Conclusion
In this section of the notes we've explored a little more of the power available in
modify_exit, and set up a faction shop complete with stock. In the next section,
we're going to look at filling out the functionality that's missing here – we're going
to need to discuss a new topic in the next section, one that's hopefully going to
tie up a lot of what we've been doing into a cohesive whole.
101
Epitaph Online
http://drakkos.co.uk
Dysfunctional Behaviour
Introduction
In this chapter we're going to talk about something that will hopefully clarify how
everything on Epitaph actually fits together. Of a necessity, we have to be quite
selective in the theory we cover in this text – we only introduce theory as it
impacts on what it is we're trying to do in the examples. As such, there's a lot of
'put this thing here in this way, but don't worry about why'.
Hopefully after you've worked your way through this chapter, a whole lot of what
you're doing will make more sense.
Our Task
We've left our shop, and Stabby Joe himself, in a state of incompleteness. While
he runs his shop perfectly well (for a given value of perfectly well), he's got a
response dangling there that just isn't being used at all. We want Stabby to fly
into a fit of apoplectic rage when his cousin, Slicey Pete is mentioned. However,
this requires us to do more than set a few values in a file with pre-written
functions – it needs us to write some code of our own.
There's one place already you've written actual code, and that's in the vaulting of
the rock in street_03. Sometimes we want to do something very specific in our
areas, and in order to do that we need to go beyond the generic functionality
provided by our inherits – for that we need to make use of a programming system
called functions. They are also sometimes called methods, and in this chapter we
will use the two terms interchangeably.
The Science Bit... Concentrate!
A function is a little section of code that you tag by a name. You've already been
writing functions – setup and reset are functions, as is after_reset and do_vault.
These are functions that you write so that your rooms do what you want them to.
At certain points, the MUD will execute the code that goes with your function – in
technical jargon, the MUD calls your function. When you room is created, setup is
called. When the MUD wants to reset the internal state of your room, reset is
called. You have no control over these two events – if you change the name of
your setup function to set_me_up, you'll find the room doesn't work.
This is a convention to which you must adhere.
However, do_vault is a function that you told the MUD to call. As such, it doesn't
102
Epitaph Online
http://drakkos.co.uk
matter what name you give them as long as the name is meaningful. You set up
the connection between your rock and your function using that weird 'happy face'
notation, like so:
add_item ("jagged rock", ({
"long", "This is a jagged rock.",
"position", "on the jagged rock",
"vault", (: do_vault :),
({"kick", "punch"}), "Ow! That stung!\n",
}));
If you change it thusly:
add_item ("jagged rock", ({
"long", "This is a jagged rock.",
"position", "on the jagged rock",
"vault", (: jump_over_rock :),
({"kick", "punch"}), "Ow! That stung!\n",
}));
It would instead call the jump_over_rock method when you attempt to vault it. It's
our choice what we call this function – however, it is an informal convention on
the MUD that when we have a function that is called in response to a player
performing a command, we call it do_<command> (such as, yes, in do_vault!).
As long as the name of the function you define is the same as the function you tell
the MUD to call, everything will be hunky dory. However, the conventions we
adopt for naming these functions were adopted for a reason - they make it
considerably easier for people to read and maintain code if they know where they
are to look for certain pieces of functionality.
The Structure of a Function
A function has a very particular structure to which it must adhere when written.
<return_type> <name_of_function> (<parameter_list>) {
}
First, it must declare its return type – that's the kind of information that comes
out of the function when it's finished. Our do_vault function returns a string that
contains the message a player should see, so the function returns a string.
Next comes the name of the function – we've already seen this in action and
should be familiar with it by now.
The parameter list is something we haven't encountered yet (formally – we've
actually been using parameters all along) – it's information that is provided to our
function by the code that calls it. Sometimes the MUD provides information for
you when you tell it a function is to be called at some point. Other times you have
to provide it yourself.
103
Epitaph Online
http://drakkos.co.uk
A parameter list consists of pair of variable types and names, separated by a
comma. For example, consider a very simple function that takes two whole
numbers and adds them together;
int add_numbers (int num1, int num2) {
}
The braces indicate the ownership of a function – all code that exists between
these two braces consist of the code belonging to it. These are the body of the
method. Within the body of the method, we cam make use of variables defined in
the parameter list just as if they had already been defined and assigned values:
int add_numbers (int num1, int num2) {
return num1 + num2;
}
When the MUD gets to a return statement in the function, it stops executing the
code that belongs to to the function and returns whatever value was indicated to
the code responsible for calling the method.
Some functions don't return any value, and we indicate those as being of return
type void (like setup and reset). They require no return statement in the body of
their code, and if you wish to terminate them before executing all of the code, you
can use a return statement by itself:
return;
We add functions to our code whenever we want to make a certain piece of code
repeatable – rather than copying and pasting the code, we create a function. This
means that if the code is broken in one part of the program, we don't need to fix
it in all the other places we pasted it – we fix it in the function, and it's fixed
everywhere.
Finally, when we want to make use of a function, we simply tell the MUD to call it
by giving its name, and any parameters we wish to send it:
add_number (4, 5);
Does that look vaguely familiar? It should – it's how you've been setting up all of
your objects so far – with a series of function calls in which you provide
parameters to functions that have already been written.
This function call won't do anything we can see, because although it will add the
two numbers together, the sum of these numbers will never get stored anywhere.
In the same way that you need an object variable to hold a pair of boots or a ring
on the MUD, you need an integer variable to hold the returned value of your
function:
int num;
num = add_number (4, 5);
At the the end of this, the variable num will have the value 9.
104
Epitaph Online
http://drakkos.co.uk
This should hopefully be making it clearer what's happening in a lot of your code
– while we haven't done a lot with dealing with returned variables, pretty much
everything we've been doing has been through function calls. When we added the
code to give Beefy his constant heart attacks, we used the find_object method,
which returned an object reference.
Everything you have done so far has been built on the use of functions. Functions
are the engine that drive the MUD.
A Little Bit More...
Okay, let's talk about just a little bit more theory and then we'll actually go on to
do something with these functions. Functions on Epitaph are actually broken up
into three types.
The first of these are local functions, or lfuns. They are functions that are defined
inside a game object. For example, add_property is a function that is defined in
/mudlib/mixins/basic/property.c, and any object that inherits that file will have
access to add_property. Objects that don't inherit that file will no have access to
that method. Luckily the objects we're creating all have that handled for you, so
properties are available to all your rooms, NPCs and items.
Local functions can be called directly using the call command – we've done this a
few times as a creator. Call lets us manually call a function, providing the
parameters as we do so. The returned value of a call is displayed to us, but we
can't do anything with it. They can also be called in other objects in code using
the -> operator, such as when we do something like:
beefy->do_command ("dance madly");
The -> operator tells the MUD 'call the local function do_command on the object
referenced with the name beefy'.
The second type of function are external functions, or efuns. These are hardcoded into the driver and are available to all objects regardless of what they
inherit. You cannot call these functions with the call command, but you can make
use of them with the exec command. They do not get used on objects with the ->
operator, because that operator requires the function to be 'defined locally'.
These cannot be changed easily, as they require a new version of the driver to be
developed and installed. The environment() function is an example of such an
efun. We call it like this:
object env = environment (beefy);
We do not call it like this:
object env = beefy->environment();
105
Epitaph Online
http://drakkos.co.uk
The last type of function are simulated efuns, or sfuns. While they are not hardcoded into the driver they are defined in a MUD object (/secure/simul_efun) that
makes them available to all of our objects just like an efun. There is no difference
to you was a creator between an efun and an sfun – the difference is in terms of
performance. Since efuns are defined in the driver, they are executed much more
quickly than an equivalent sfun would be. Sfuns can also be changed and added
much easier than efuns can, although the set of people who can actually add or
change sfuns is fairly limited. You'll often find new sfuns being made available
more often than new efuns are though.
Function Scope
Like variables, functions have a scope – you can't directly make use of a function
that is defined in an object other than your current object. You need to use the
special arrow notation we've seen – for example, when we did our task check:
TASK_MAPPER->perform_task (this_player(), TASKMAP_VAULT_BARRIER,
TTSM_DIFF_INCREDIBLY_EASY, 0, TM_FREE);
TASK_MAPPER is an object that is defined in task_mapper.h, and what this code is
telling the MUD is 'call the method perform_task on the object defined as
TASK_MAPPER'.
There is a secondary syntax for this using an efun called call_other. Call other
works like this:
call_other (beefy, "do_command", "say Cor, this is cool.");
Functionally, this is identical to the following syntax:
beefy->do_command ("say Cor, this is cool.");
The main benefit is that since the function to be called through call_other is
defined as a string, we can actually have variable method calls:
string str = "do_command";
call_other (beefy, str, "say Cor, this is even cooler than before!");
I don't recommend you actually do this at this point, but you may see it in use
throughout the code you read and you should know what's happening when you
see it.
Onwards and Upwards!
Okay, now we've gotten that out of the way let's incorporate a few functions into
our new objects. First of all, let's define what Stabby Joe is going to do when you
mention his cousin. Firstly, we're going to keep an internal 'anger counter' for
106
Epitaph Online
http://drakkos.co.uk
Stabby. This is a number against which we'll roll a random number. If the number
we roll is less than his anger, he's angry! If it's more, then he's not quite so angry.
Players mentioning his cousin will receive one of two responses – violence or a
warning. The exact response a player will receive will depend on the anger check.
First of all, let's write a stub function – a stub is a function that has no real code in
it, but is there so that our object will actually compile when we refer to it:
int anger_check() {
return 1;
}
We're going to tell add_respond_to_with that it should use a function to determine
how it responds to people mentioning his cousin – we do this by giving the name
of the function as a response, with a # symbol at the start of it, like we did in the
last chapter:
add_response (({"slicey", "pete"}), "#cousin_response");
So, whenever this response is triggered, it will call the method cousin_response.
Be aware that you can't use this syntax for everything – for example, the following
will not work:
set_long ("#random_long");
It only works in specific cases, and you should consult the documentation on the
function you are using to see whether or not this syntax is supported. It does
work as a chat in load_chat and load_a_chat though, which is very useful.
Anyway, back to cousin_response – we add in a stub for this to make him say that
he doesn't like his cousin.
void cousin_response() {
do_command ("' Don't mention my cousin, or Stabby cut your pretty face!");
}
We put these in as stubs first to ensure that everything is working properly – we
need to ensure that the connection between our functions is set up before we
start writing any more complicated code. First we check the link between our
add_respond_to_with and our cousin_response function. We can easily see this
simply by mentioning the name to Joe to see if he responds:
> ask joe about pete
You ask Stabby Joe a question about pete.
Stabby Joe exclaims: Don't mention my cousin, or Stabby cut your pretty face!
Right, that's working – so now we link up cousin_response and anger_check to see
if they connect up properly:
void cousin_response() {
if (anger_check() == 1) {
do_command ("' I KILL YOU!");
do_command ("kill " + file_name (this_player()));
}
107
Epitaph Online
http://drakkos.co.uk
else {
do_command ("' Don't mention my cousin, or Stabby cut your pretty face!");
}
}
int anger_check() {
return 1;
}
Note the way in which we're handling the killin', using the file_name efun:
do_command ("kill " + file_name (this_player()));
The file_name of an object is an unambiguous unique reference to the object. Find
our your own with the following command:
exec return file_name (this_player());
Then, using the reference it gives you, try interacting with yourself. For example,
if your reference was /global/lord#7325 (which mine was), then try:
/smile global/lord#7325
You smile at yourself.
This means we don't need to worry about targeting specific objects by their
names – we target them by their references. This is important, it solves several
weird issues with targeting and means that your NPC can genuinely differentiate
between two objects with the same name.
Anyway, update Joe, and... wait, what's this? It comes up with a syntax error that
says:
Undefined function anger_check before
== 1) {
But what does it mean, undefined function? We've defined it right there, in the
code!
Actually, this is a layover from the C programming language upon which LPC is
based – the MUD reads in our file top to bottom, and it gets to the anger_check
function call before it gets to the definition of the function itself. It panics, and
halts the process saying 'Hang on! I don't know anything about this anger_check
to which you refer! ABORT, ABORT!'
We can simply swap the two functions around to solve the problem:
int anger_check() {
return 1;
}
void cousin_response() {
if (anger_check() == 1) {
do_command ("' I KILL YOU!");
do_command ("kill " + file_name (this_player()));
}
108
Epitaph Online
http://drakkos.co.uk
else {
do_command ("' Don't mention my cousin, or Stabby cut your pretty face!");
}
}
That's an inelegant solution though – you shouldn't have to dramatically alter the
layout of your code just to make it run. There's a better solution that involves just
telling the MUD 'Don't worry, this function is defined later in the program'. It's
called function prototyping, and we already did it earlier when we wrote the
do_vault function.
At the top of your code, just after the inherit (but not before, or you'll introduce a
new error) you simply put the definition of the function – its return type, its name,
and the parameters, like so:
int anger_check();
This is the function definition, and as long as LPC sees that before it gets to the
function call, it will be happy. There's no need to restructure your code around
such an error, just add a function prototype.
Now when we mention Joe's cousin, he says he'll kill us, and actually makes an
attempt to do so:
> ask joe about pete
You ask Stabby Joe a question about pete.
Stabby Joe exclaims: I KILL YOU!
Stabby Joe moves aggressively towards you!
Progress is being made!
Violence Begets Violence
So, every time someone mentions Pete's cousin, we want to increase our anger
counter. For that, we first need an anger counter!
void cousin_response() {
int anger_counter;
anger_counter = anger_counter + 1;
if (anger_check() == 1) {
do_command ("' I KILL YOU!");
do_command ("kill " + file_name (this_player()));
}
else {
do_command ("' Don't mention my cousin, or Stabby cut your pretty face!");
}
}
And then in anger_check, we roll a random number against that anger_counter to
see if he's angry:
int anger_check() {
109
Epitaph Online
http://drakkos.co.uk
int anger_check() {
if (random (100) < anger_counter) {
return 1;
}
return 0;
}
However, when we update Joe the MUD will once again complain. It's our old
friend, the scope problem. Because anger_counter is defined locally in
cousin_response, anger_check doesn't have access to it. We could solve this by
passing it as a parameter. First we'd need to change our function prototype:
int anger_check (int);
Then hook up the functions in the new way:
void cousin_response() {
int anger_counter;
anger_counter = anger_counter + 1;
if (anger_check(anger_counter) == 1) {
do_command ("' I KILL YOU!");
do_command ("kill " + file_name (this_player()));
}
else {
do_command ("' Don't mention my cousin!");
}
}
int anger_check(int anger_counter) {
if (random (100) < anger_counter) {
return 1;
}
return 0;
}
That solves our scope problem, but it doesn't solve our persistence problem – just
like with vaulting the rock, if we create the variable locally it gets recreated every
time the function is called – anger counter will never increase beyond one. We
solve the problem then by having the code linked up in the original way, but make
anger_counter a class wide variable instead:
#include <npc.h>
#include "path.h"
inherit STD_MONSTER;
int anger_counter;
int anger_check();
void setup() {
// Usual setup stuff
}
void cousin_response() {
int anger_counter;
anger_counter = anger_counter + 1;
110
Epitaph Online
http://drakkos.co.uk
if (anger_check() == 1) {
do_command ("' I KILL YOU!");
do_command ("kill " + file_name (this_player()));
}
else {
do_command ("' Don't mention my cousin, or Stabby cut your pretty face!");
}
}
int anger_check() {
if (random (100) < anger_counter) {
return 1;
}
return 0;
}
We won't add a reset for anger_counter – Joe just gets angrier, he never calms
down over time.
It can take a while before we can tell whether Joe is angry enough to strike, so
let's add another function that we can call to tell us his internal state:
int query_anger() {
return anger_counter;
}
Just to make sure that it's all working the way we expect:
> call query_anger() joe
Returned: 0
> ask joe about pete
You ask Stabby Joe a question about pete.
Stabby Joe exclaims: Don't mention my cousin, or Stabby cut your pretty face!
> call query_anger() joe
Returned: 1
> ask joe about pete
You ask Stabby Joe a question about pete.
Stabby Joe exclaims: Don't mention my cousin, or Stabby cut your pretty face!
> call query_anger() joe
Returned: 2
His anger is increasing nicely! Let's make it easier to test him by allowing
ourselves a way to set his anger high enough so that we don't need to repeat the
same thing over and over again:
void set_anger (int val) {
anger_counter = val;
}
Now set it to something that will definitely trigger his rage, such as one hundred,
and try say his name:
111
Epitaph Online
http://drakkos.co.uk
> call set_anger (100) joe
Returned: 0
> call query_anger() joe
Returned: 100
> ask joe about pete
You ask Stabby Joe a question about pete.
Stabby Joe exclaims: I KILL YOU!
Stabby Joe moves aggressively towards you!
That's one psycho shopkeeper, right there!
Conclusion
This chapter is somewhat 'think heavy' because of the need for us to talk about
how functions work – they are critical to gaining any real understanding of how
bits of the MUD communicates with other bits, and so it's worth our while talking
about them properly. Hopefully by this point you've now gained a fairly strong
understanding of why the magic words you're typing actually make the MUD do
interesting things!
We're not done yet of course, there's plenty more for us to talk about before we
reach the end of Introductory LPC!
112
Epitaph Online
http://drakkos.co.uk
Going Loopy
Introduction
For the moment, Stabby's shop is not exactly exciting in terms of its stock – it
gets only what is set in the faction handler. In this chapter, we're going to look at
how we can provide some more bespoke stock options, and and in the process
we're going to be introduced to the next of our major programming structures –
the loop.
So, let's not stand around chattering all day – let's roll up our sleeves and have at
it like the LPC crusaders we are!
Stabby's Shop
We're always going to get the normal faction stuff as part of having a faction
shop, but we're not limited to that – we can also add in our own stock as we go
along. That way we can have shops that all serve a single faction, but each can be
distinguished by having their own unique supplies where it is appropriate – which
it is, now!
In our shop, we have a set of functions that let us relatively easily manipulate the
store room. One of these is query_num_of, which allows us to pass in an
identifier and get how many items match that identifier. The other is
add_to_storeroom, which takes an object and adds it to the current stock.
We're going to use these functions to add the boots and ring we created earlier to
the shop. Yes, I know – they are Beefy's equipment, but to hell with him. What
has he done for us recently? Nothing, that's what.
To add one of each is pretty straightforward – in the setup of our shop, we have
the following:
add_to_storeroom (ITEMS + "beefy_ring.jew");
add_to_storeroom (ITEMS + "beefy_boots.clo");
This will make the stock available, but only once – it will never replenish itself.
That may be what we want, but it's probably not. Instead, what we want to do is
provide a reset function that ensures the restocking:
void reset() {
::reset();
add_to_storeroom (ITEMS + "beefy_ring.jew");
add_to_storeroom (ITEMS + "beefy_boots.clo");
}
Unfortunately, there is a flaw here – every reset, without any kind of check, a new
ring and a new pair of boots are added to the storeroom. Leave this running for a
113
Epitaph Online
http://drakkos.co.uk
couple of days, and there will be dozens of both. We don't want that, we want to
limit it to something sensible. Let's say – ten:
void reset() {
::reset();
if (query_num_of ("beefy ring") < 10) {
add_to_storeroom (ITEMS + "beefy_ring.jew");
}
if (query_num_of ("beefy boots") < 10) {
add_to_storeroom (ITEMS + "beefy_boots.clo");
}
}
Every reset, we'll get another one of each of these until we have a total of ten.
That's better, but it's still not ideal – what we really want is for it to start off with
ten of these, and then even reset restock it back to the full amount. Well – maybe
that's not really what we want, but it's what we're going to do.
Long Hand
So, using the tools we already have, how would we accomplish this task? Well, we
could do something like:
void reset() {
::reset();
if (query_num_of ("beefy ring") < 10) {
add_to_storeroom (ITEMS + "beefy_ring.jew");
}
if (query_num_of ("beefy ring") < 10) {
add_to_storeroom (ITEMS + "beefy_ring.jew");
}
if (query_num_of ("beefy ring") < 10) {
add_to_storeroom (ITEMS + "beefy_ring.jew");
}
if (query_num_of ("beefy ring") < 10) {
add_to_storeroom (ITEMS + "beefy_ring.jew");
}
if (query_num_of ("beefy ring") < 10) {
add_to_storeroom (ITEMS + "beefy_ring.jew");
}
if (query_num_of ("beefy ring") < 10) {
add_to_storeroom (ITEMS + "beefy_ring.jew");
}
if (query_num_of ("beefy ring") < 10) {
add_to_storeroom (ITEMS + "beefy_ring.jew");
}
if (query_num_of ("beefy ring") < 10) {
add_to_storeroom (ITEMS + "beefy_ring.jew");
114
Epitaph Online
http://drakkos.co.uk
}
if (query_num_of ("beefy ring") < 10) {
add_to_storeroom (ITEMS + "beefy_ring.jew");
}
if (query_num_of ("beefy ring") < 10) {
add_to_storeroom (ITEMS + "beefy_ring.jew");
}
}
Blurgh. Hopefully by now you should be aware of why this is a bad solution – it
has a lot of copying and pasting, and if we ever need to change the code for
cloning an object, we need to change it ten times. That's not so bad in itself, but
once we do that for the boots we have twenty lines of code we'd need to change.
If we add in more objects, there is more we need to fix. Programmers, you see,
are fundamentally lazy – we don't believe that we should do any work if the
computer can do it for us. Luckily, there is a programming structure that will do
this for us – the loop.
Loops
When we want to repeat a piece of code a multiple number of times, we provide
the MUD with a loop to make it do the actual work for us. The simplest of these
loops is called the while loop, and it will repeat code until a particular
continuation condition is no longer met. For example, imagine the following:
void reset() {
::reset();
while (query_num_of ("beefy ring") < 10) {
add_to_storeroom (ITEMS + "beefy_ring.jew");
}
}
The continuation condition works just like the condition in an if statement. The
difference is that the while loop will keep doing the code in the braces until the
condition is no longer met. In this case, it will continue to perform the code within
the braces while query_num_of returns a number that is less than ten.
That's it – that is all you need to do! We can then add another while loop to
handle the boots:
void reset() {
::reset();
while (query_num_of ("beefy ring") < 10) {
add_to_storeroom (ITEMS + "beefy_ring.jew");
}
while (query_num_of ("beefy boots") < 10) {
add_to_storeroom (ITEMS + "beefy_boots.clo");
}
}
115
Epitaph Online
http://drakkos.co.uk
Notice that we do this with two separate loops rather than one loop handling both
– that's because there may be different numbers of rings and boots in the shop
(perhaps the rings are selling especially well, or maybe the boots have just been
featured on the feet of an influential celebrity), and we want to make sure that
both of the items are appropriately restocked.
Doing While...
A do while loop works in exactly the same way as a while loop, except that its
guaranteed that the code will be executed at least once. If there were already ten
of an item in the storeroom, our while code would never execute. With a do_while,
we can ensure that the code is run once before the continuation condition is
checked:
do {
add_to_storeroom (ITEMS + "beefy_ring.jew");
} while (query_num_of ("beefy ring") < 10);
Do while loops have somewhat specialised use - if you can't think of a reason why
you would want to do this, it's fine... you don't have to use them unless you
actually need to, but when you do need to they are invaluable.
While loops and do while loops are examples of a set of structures called
unbounded loops - their continuation can be dependant on unknown constraints.
Here we're working with numbers, but that's not where they are best used. They
are best used in situations where we don't know how many time we're going to
loop. For example, consider the following:
while (there_are_enemies_in_the_roo) {
kill_them();
kill_them_all();
}
We don't know when the enemies will leave the room - they may be killed, they
may choose to run away - it's something over which we have no control and thus
we manage that uncertainty by using a while loop.
116
Epitaph Online
http://drakkos.co.uk
For Loops
We can use a for loop to deal with situations in which we know how many times a
piece of code will be executed. That doesn't mean that we know when writing the
code how many times we have to execute the loop, just that when the MUD gets
to the loop, somewhere we have a variable storing how many times it is going to
need to loop over the code. This is a bounded loop. It has all the parts of our first
while loop, just presented in a slightly different syntax. If we wanted to make ten
rings available, then:
int counter;
for (counter = 0; counter < 10; counter = counter + 1) {
add_to_storeroom (ITEMS + "beefy_ring.jew");
}
The for loop handles keeping the counter variable correct for us - we can
concentrate purely on the functionality we wish to repeat. It otherwise works like a
standard while loop:
for (initialization ; continuation ; upkeep) {
// code goes here
}
A for loop can also declare a variable as part of its counter inside the loop
declaration, neatly encapsulating everything into a single syntactic structure, like
so:
for (int counter = 0; counter < 10; counter = counter + 1) {
add_to_storeroom (ITEMS + "beefy_ring.jew");
}
You can also write the upkeep in a more compact fashion:
counter++;
Or :
counter += 1;
Both of these lines of code do (almost) the same thing - they increase the value of
num_carrots by one. They actually do very subtly different things in the way they
increase the value, but that's of no consequence to us at the moment:
for (int counter = 0; counter < 10; counter++) {
add_to_storeroom (ITEMS + "beefy_ring.jew");
}
As the code is currently written, we've made it so that every reset, ten rings get
cloned into the store-room. That's really not what we want (for the same reason
that we don't want to clone a single ring every reset, just multiplied by ten).
117
Epitaph Online
http://drakkos.co.uk
Luckily, the for loop gives us fine-grained control over our counter – if we want to
start it counting from a number other than zero (say, for example, the current
number of items in the storeroom), we can do that:
void reset() {
int current_count;
::reset();
current_count = query_num_of ("beefy boots");
for (int counter = current_count; counter < 10; counter++) {
add_to_storeroom (ITEMS + "beefy_ring.jew");
}
}
The reason that this kind of approach works is because of the way the for loop
works – the first thing it does is handle the initialization portion of the loop
(setting the counter to current_count). Then, it checks the continuation portion (is
our counter less than ten). If and only if that continuation portion evaluates to
true will it perform the loop. Once the loop has been performed, it then executes
the upkeep code (the third 'compartment' in the for loop).
Initialization happens once, before anything else is done. The continuation
happens at the start of each loop, and determines whether the code between the
braces should be executed. The upkeep happens only at the end of the loop.
Each of these loops around a structure is called an iteration – so the for loop
checks the condition, iterates if it should, and then executes the upkeep.
Conclusion
This chapter introduced us to the loop – a tremendously powerful programming
structure that lets us make the MUD do a lot of work for us. Whenever we need to
repeat code, we put in a loop, and wave an imperious hand as we do. It means we
don't need to copy and paste, and that we have an easy way of adjusting the
number of times code must repeat, sometimes on the basis of information we
simply don't have when we're writing the code. That's extremely useful, and you'll
find it a powerful ally in your quest to master the Epitaph mudlib.
118
Epitaph Online
http://drakkos.co.uk
Arrays, You Say?
Introduction
In this chapter we are going to discuss one of the most important coding tools
that you have available to you as a creator – the array. Arrays are one of the most
important concepts to understand if you want to provide genuinely interesting
code in your areas, and it's worth reading over this chapter obsessively until it all
makes sense. Then, practice, practice, practice until you are entirely comfortable
with the topic. I honestly can't stress this enough – an understanding of arrays
forms the most important skill of a creator, as all of the more complex subjects
are inextricably linked with it.
We're going to explore arrays in this chapter through the medium of Slicey Pete,
Joe's cousin. Slicey Pete is a wandering vagrant who scavenges the streets of
Deadville for its meager salvage opportunities. We'll also continue with the topic
in the next chapter. So, with no more ado, on with the show!
Slicey Pete
Pete isn't just a normal NPC – he's a wandering merchant, and thus he uses a
different inherit. Beyond that, he works like all of the other NPCs we've developed
this far. Let's look at his template:
#include <npc.h>
#include "path.h"
inherit STD_MONSTER;
void setup() {
set_name ("pete");
set_short ("Slicey Pete");
add_property ("determinate", "");
add_property ("unique", 1);
add_adjective ("slicey");
add_alias ("slicey");
set_gender (1);
set_long ("This is Slicey Pete, the cousin of Stabby Joe. While people "
"often assume the worst because of his name, he's not a violent man. "
"He just used to be a butcher.\n");
basic_setup ("human", "survivor", MONSTER_LEVEL_NORMAL);
setup_nationality (MUDLIB_NATIONALITIES + "british", "scotland");
add_move_zone ("deadville");
set_move_after (30, 60);
load_chat(10, ({
119
Epitaph Online
http://drakkos.co.uk
1, "' Stabby's real name is Madeline, you know.",
1, "' Poor Madeline - he was such a nice boy at one point.",
1, "' So hungry.",
}));
load_a_chat(20, ({
1, "' No, I was just a butcher! Leave me alone!",
1, "' I think I just had an accident!",
}));
add_response (({"stabby", "joe"}), "' He was a nice lad once.");
dress_me();
init_equip();
}
So far, so good – there's nothing really new here. His interest lies in what he's
going to do as he wanders around the game – specifically, he's going to pick up
everything he finds, and eat it. It doesn't matter what it is – guns, concrete,
smaller animals – he'll just pick them up and toss them in his mouth hole.
There are various ways in which we can handle this, but the simplest way is to
simply give him a load_chat that calls a function every so often – that function will
be 'eat_everything':
load_chat(10, ({
1, "' Stabby's real name is Madeline, you know.",
1, "' Poor Madeline - he was such a nice boy at one point.",
1, "' So hungry.",
1, "#eat_everything",
}));
In this function, we'll do all the magic work of having him consume all around him
(except for players, and perhaps other NPCs). For now, just put in a small
skeleton for the function:
void eat_everything() {
do_command (": looks around for something to eat.");
}
Finally, let's spawn him in a room – for this, since he's not a shopkeeper, we use
the usual add_unique_spawn routines we used for Captain Beefy. Let's have him
spawn in street_02, just for the hell of it:
add_unique_spawn (CHARS + "pete", "$N appear$s with a pop.",
"$N disappear$s with a pop.");
The stage is set, the lights are dimmed, but before we get to the eatin', let's talk a
bit about arrays.
120
Epitaph Online
http://drakkos.co.uk
The Array
Simply stated, arrays are just lists of data. You've been using them all along,
although we haven't stressed then. You've encountered an array whenever you've
seen a line of code like:
({"some", "things", "here"})
Arrays are phenomenally useful, and it's fair to say that until you have mastered
them you are only paddling around in the shallow end of programming. Let's talk
about how they work!
Arrays are just variables, the difference is that they have multiple compartments
into which data can be inserted. All the data has to be of the same type. We
declare them slightly differently from the variables we have seen so far – they are
indicated with a star symbol by the variable's name. To create an array of strings,
we'd use the following syntax:
string *my_array;
We also set it with a value in a slightly different way – to create an empty array, we
use the ({ }) notation:
my_array = ({ });
We need to set the array to have something before we use it, or we'll likely get
Terrible Errors.
If we want to set an array up with some starting values, then we use a variation on
that syntax:
my_array = ({"some", "things", "here"});
When this code is interpreted by the MUD, it sets up a variable with three
compartments:
Compartment
0
1
2
Contents
"some"
"things"
"here"
The number of the compartment is called its index, and the contents are known
as the element, as in 'Give me the element at index 2'.
We can pull information out of these compartments using a special notation:
string bing = my_array[1];
This code will pull out the element at index 1 (in the case of our example, it will
be the string "things"). If we want to change a value, then we can do that too:
121
Epitaph Online
http://drakkos.co.uk
my_array[1] = "pirates!";
After this line of code is executed, our array will look like this:
Index
0
1
2
Element
"some"
"pirates!"
"here"
In both cases you must be careful not to access an index that is either larger than
the size of the array, or a negative number. If you do, the MUD will complain of an
'array index out of bounds', which just means you tried to access an element that
doesn't actually exist.
If we want to add in a fourth element, then we do that by adding another array to
our first:
my_array += ({ "matey" });
This adds the new element to the end of the array:
Index
0
1
2
3
Element
"some"
"pirates!"
"here"
"matey"
And to remove an element, like so:
my_array -= ({ "here" });
This will delete every instance of the word 'here' in the array:
Index
0
1
2
Element
"some"
"pirates!"
"matey"
If you want to delete an element at a specific index, you use the delete sfun. This
takes three parameters – the name of the array, the index from which you want to
delete, and how many elements you want to delete:
my_array = delete (my_array, 1, 1);
The result of this would be:
Index
0
1
Element
"some"
"matey"
122
Epitaph Online
http://drakkos.co.uk
It may not seem immediately obvious as to why this is so useful, but we'll see
before too long. Oh yes, you'll see. YOU'LL ALL SEE.
Array Indexing
LPC provides a number of ways in which we can index elements without simply
using integers. We can start indexing from the last element by using the <
operator within an array index notation. If we want the last element in an array,
we can do it like so:
string str = my_array[<1];
Or if we want the second last:
string str = my_array[<2];
We can get a range out of an array by using the .. notation, like so:
string *sub_array;
sub_array = my_array[1..2];
The range notation is inclusive, so with the above code you would get elements 1
and 2 out of it. You can also combine these notations:
string *sub_array;
sub_array = my_array[0..<2];
The effect of this array index would be to get all of the elements from the first
element (indexed by 0) up until the second last element (indexed with <2). You
can even leave off the end part of the range:
string *sub_array;
sub_array = my_array[1..];
This would get all of the elements of the array from the second one onwards.
There's a lot that we can do with this kind of indexing system, but don't worry
about it just yet. Just know that when you see it, that's what's happening.
Array management
Arrays have conceptual complexity to go with them, and as such there are a
number of 'management' functions that exist to help you manipulate them.
Perhaps the most important of these is sizeof, which gives you the number of
elements in an array:
int num = sizeof (my_array);
With our two elements, the variable num will contain the value 2. We can then use
123
Epitaph Online
http://drakkos.co.uk
this as a basis for array manipulation – after all, to manipulate an array properly
we need to know how big it is - that makes it amenable to manipulation with a
bounded loop.
Perhaps the next most important task we perform on a day to day basis with an
array is to tell if a particular value is contained with. We do that using the
member_array function. This take two parameters – one is the value we wish to
search for, and the second is the array through which we wish to search. The
function returns a -1 if the item is not present, or the index of the first match if it
is:
int found = member_array ("matey", my_array);
With this example, found will be equal to 1 because that's where the search text
can be found. If we search instead for 'me hearties', found will be set to -1
because that string is not present in the array.
If we want to pull a random element out of the array, we can use the element_of
function:
string random_element = element_of (my_array);
And we can get several random elements out of the array using the elements_of
function. The first parameter to this method is the number of elements you want
out of it. The second is the array from which you want to extract them.
string *some_elements = elements_of (2, my_array);
We can randomise the order of the contents of an array, just like a pack of cards,
with the shuffle method:
my_array = shuffle (my_array);
You can also sort the contents of an array into ascending or descending order
using the sort_array function. The number you provide to this method indicates
the direction of the sorting - 1 indicates ascending order, and -1 indicates
descending order.
my_array = sort_array (my_array, 1);
That should be enough of a start for us to start using them in our code – like with
everything, the only way we ever really start to understand code is when we start
to use it. So let's begin!
Fine Dining
So, now that we know how arrays work, it's time to apply this knowledge to Slicey
Pete. For this, we're going to encounter a few more efuns, but ones that require
us to understand the nature of arrays.
124
Epitaph Online
http://drakkos.co.uk
The first of these is the all_inventory efun, which gives all of the objects that
are in a provided object (such as a room). To get the room in which an NPC is
standing, we use the environment efun. To begin with, our function will look like
this:
void eat_everything() {
object room = environment (this_object());
object *contents = all_inventory (room);
do_command (": looks around for something to eat.");
}
this_object represents the object in which the code is found – so it's a nice
shorthand way of saying 'this object, this one right here, the one that this code is
within'.
The all_inventory efun gives us out an array of objects, and these objects are the
entire contents of the room (including Pete himself). Let's do something with this
information first, so we can see what we're working with:
void eat_everything() {
object room = environment (this_object());
object *contents = all_inventory (room);
object current;
for (int i = 0; i < sizeof (contents); i++) {
current = contents[i];
do_command ("' I see " + current->a_short());
}
}
This is an important combination of topics here – the real reason why loops are so
powerful is that they are ideally suited to manipulating an array, as they are used
here. Here, we use the counter variable of the loop to index elements in an array,
in order.
Bring him into a room with some things – some NPCs, maybe, and call
eat_everything on him:
> call
Slicey
Slicey
Slicey
Slicey
eat_everything()
Pete says: I see
Pete says: I see
Pete says: I see
Pete says: I see
pete
you
Captain Beefy
Drakkos Wyrmstalker
a stall
Okay so far – the 'I see you' part refers to himself – it's just a quirk of the a_short()
call – from his perspective, a_short on himself is 'you', just like if you call a_short()
on yourself you'll see 'you' in return.
Let's make Pete a little voracious to begin with – we'll have him eat everything
except players. We can tell when an object is a player, because it will return 1 to
the efun interactive, so we can do the following:
void eat_everything() {
125
Epitaph Online
http://drakkos.co.uk
object room = environment (this_object());
object *contents = all_inventory (room);
object current;
for (int i = 0; i < sizeof (contents); i++) {
current = contents[i];
if (current == this_object()) {
continue;
}
if (!interactive (current)) {
do_command ("' Get in ma belly, " + current->query_short() + "!");
do_command (": consumes " + current->the_short() + " with gusto!");
current->move ("/room/rubbish");
}
}
}
The first if condition inside our for loop checks to see if the current object in our
array is Pete himself – we don't want him to consume his own flesh. In the
context of a loop, the continue statement means 'stop with this iteration of the
loop and continue on with the next'. In a loop, a break (like we saw in the switch
statement, way back when) means 'stop with this loop entirely'. Continue causes
us to just skip the current iteration of the loop.
The second if condition checks to see if the object is not interactive – if it's not,
Pete will eat it. It doesn't matter what it is, Pete will quite happily shove the object
in his mouth:
> look
You can hear: Nothing.
In the distance you can see: Nothing.
This is a skeleton room.
It is a very cold winter afternoon with scattered puffy clouds and a steady
breeze from the northeast.
There are three obvious exits: north (clear), east (clear) and west (clear).
Captain Beefy and Slicey Pete are standing here.
> call eat_everything() pete
Slicey Pete exclaims: Get in ma belly, Captain Beefy!
Slicey Pete consumes Captain Beefy with gusto!
Slicey Pete exclaims: Get in ma belly, stall!
Slicey Pete consumes the stall with gusto!
Oh Pete, you so crazy!
The Foreach Structure
There exists a specific kind of programming structure designed purely to step
over each element in an array – it's called foreach, and you'll see it quite a lot as
your read over existing code. It's just a neater way of doing what our loop above
does – we can dispense with manually pulling each item off of the array and let
the code do it for us. A foreach loop starts at the beginning of the array, and ends
at the end, and at each stage it moves on one element forwards through the array.
126
Epitaph Online
http://drakkos.co.uk
It's not good for when we need fine grained control over how we work with an
array, or when we need to know how many iterations we have gone over, or when
we need to know where in an array we currently are. However, for straight-forward
stepping over an array, it would look like this:
foreach (object current in contents) {
if (current == this_object()) {
continue;
}
if (!interactive (current)) {
do_command ("' Get in ma belly, " + current->query_short() + "!");
do_command (": consumes " + current->the_short() + " with gusto!");
current->move ("/room/rubbish");
}
}
We use the current variable in the array syntax as a condition for the structure –
we don't need to manually query the size of the array or pull the variable off of
the array. The loop will begin at the very first element, and pull that element off
the array and store it in current. Next time around the loop, it will take the second
element and put that into current, and so on until all of the elements of the array
have been stepped over.
This is a very lovely syntactic structure, and a feature that LPC had long before it
made its way into more mainstream languages like Java. The sole drawback is that
it doesn't give you access to a specific index number – if you need that at any
point in your code, you'll need to rely on a standard for loop
You'll find that a combination of for and foreach will be required to make full use
of arrays as you progress through your projects – learning which is best for a
particular task is something that will come with experience.
Conclusion
Arrays are a complicated topic, and in the next chapter we're going to look at
some of the many varied cool things we can do now that we've added them to our
programming toolbox. I'm not kidding when I say that this is what promotes you
from the shallow end of the programming swimming pool – without even talking
about any other data types, we can do practically anything we'd ever need by
relying purely on arrays.
There's one more standard data type we're going to introduce in Introductory LPC,
but our journey through introductory syntax is starting to come to a conclusion –
we've covered some scary territory over the past chapters and you are to be
congratulated for reaching this far! Now, go practice arrays until your brain
bleeds!
127
Epitaph Online
http://drakkos.co.uk
Hooray for Arrays
Introduction
In the last chapter we looked at the basics of using arrays in LPC – that was a big
step, and it opens up a lot of new things we can talk about. Arrays are embedded
deeply in the mudlib, to the point that you can't do anything of consequence
without working with them in one capacity or another. Most of the useful
functions of the MUD return arrays rather than single bits of data, and in this
chapter we're going to look at some of these.
In the process, we'll add the last of the rooms to our village, adding in interactivity
based on what a player has in their inventory, among other things.
A Secret to be Discovered
Look back at our map of Deadville – there's one room we still haven't done,
marked C on the map. It's connected to market_southeast, but we don't have
anything for it yet. The room itself is going to be a normal room, except it will be
absolutely full to the rafters of grues!
We'll start with the usual first step, our skeleton implementation. We'll save this as
grue_room.c:
#include "path.h";
inherit STD_ROOM;
void setup() {
set_short ("abandoned shop");
add_property ("determinate", "");
set_day_long ("The fingers of sunlight do not touch this occult "
"place. Hope has long deserted this room.\n");
set_night_long ("In the darkness, it's possible to sense occult, "
"eldritch energies seeking entry. Anyone in the room at "
"night without the special magical safety item is likely "
"to be eaten by a grue.\n");
set_light (100);
add_day_item("occult place", "There's a definite sense of occultness "
"in the air.");
add_day_item (({"occult", "occultness"}), "Yes, that sort of thing.");
add_night_item ("occult eldritch energies", "They are very oblong.");
add_night_item ("special magical safety item", "Hoo-boy, you better hope "
"you have that with you!");
room_day_chat ( ({ 120, 240, ({
"You are probably safe here, for the time being.",
"This room makes you sad.",
128
Epitaph Online
http://drakkos.co.uk
"You sense that the room doesn't want you to be here.",
})}));
room_night_chat ( ({ 120, 240, ({
"You better hope you have the magic item!",
"I feel sorry for anyone who doesn't have that special item!",
"#eaten_by_grue",
})}));
add_exit ("west", ROOMS + "market_southeast", "door");
}
void eaten_by_grue() {
// We'll fill in the code for this later.
}
Note that one of the room_night_chat entries is to a function called
eaten_by_grue. We'll come back that one soon, but we'll just add in a stub for it
now. Stub methods are a great way to structure your code without needing you to
have to actually code everything at once.
It starts off simply enough, but now we're going to do some hard-core array
manipulation, for reals. We're going to make it so that if anyone wanders into this
room, at night, and doesn't have Beefy's ring on them – well, bad stuff is going to
happen.
A Grue Some Fate!
The criteria for being eaten by a grue is fairly simple – if it's night-time and don't
have a beefy diamond ring on you, you get eaten, arr num num num. This is a fate
for everyone in the room, and so our function has to check, for every player and
NPC in the room, if they have protection. Let that be a lesson, boys and girls don't go out without some protection on you.
Let's start by just getting all the objects that are currently in the room. We do this
using all_inventory. This will get everything in the room – players, NPCs, and
items that happen to be lying on the ground. It's exactly the same as we did for
Slicey Pete in the last chapter, except we don't need to get an environment –
this_object is already an environment:
void eaten_by_grue() {
object *room_contents;
room_contents = all_inventory (this_object());
}
There are more compact ways to do the next few bits, but don't worry about those
– we're going for simple, easily comprehensible code here so as each step is
clearly understandable.
Once we have our list of objects, we want to get the ones that are classed by the
MUD as living (NPCs and players). We use the living efun for this – if we wanted
players only, we'd use the interactive or userp efuns instead:
129
Epitaph Online
http://drakkos.co.uk
void eaten_by_grue() {
object *room_contents;
object *living_objects = ({ });
object ob;
room_contents = all_inventory (this_object());
foreach (ob in room_contents) {
if (living (ob)) {
living_objects += ({ ob });
}
}
}
Now, we want to step over each of those living objects we've found, and detect
whether they have the amulet. Let's slow down here and think about this
incrementally – how would we do it for one living object?
Well, first we'd need to see whether that object had an amulet on them – that's
easy, that's what match_objects_for_existence does:
matches = match_objects_for_existence ("beefy ring", living_objects[0]);
The ring itself has a query_worn_by function that tells us who is wearing it, so we
can use that. We need to check over each of the amulets returned by
match_objects_for_existence, even we expect players to just have one. They may
have two, and it would be a little unfair if they were eaten because we happened
to check if they were wearing the wrong one.
We're going to use a integer variable here called safe to determine if someone is
to be eaten. If safe is 1, that individual doesn't get eaten. If it's 0, they do.
void eaten_by_grue() {
object *room_contents;
object *living_objects = ({ });
object ob;
object *matches;
int safe;
room_contents = all_inventory (this_object());
foreach (ob in room_contents) {
if (living (ob)) {
living_objects += ({ ob });
}
}
matches = match_objects_for_existence ("beefy ring", living_objects[0]);
foreach (object ring in matches) {
if (ring->query_worn_by() == living_objects[0]) {
safe = 1;
}
}
}
130
Epitaph Online
http://drakkos.co.uk
We can assume at the end of this foreach that any player it is still dealing with is
not protected from our vicious grues. We tell them they have been found wanting,
and then unceremoniously slay them:
if (safe == 1) {
tell_object (living_objects[0], "You are judged and found worthy!\n");
}
else {
tell_object (living_objects[0], "You are devoured by a grue!\n");
tell_room (this_object(), living_objects[0]->query_short()
+ " is devoured by a grue!\n", ({ living_objects[0] }));
living_objects[0]->do_death();
}
There's a lot happening here, so let's look at it in context:
void eaten_by_grue() {
object *room_contents;
object *living_objects = ({ });
object ob;
object *matches;
int safe;
room_contents = all_inventory (this_object());
foreach (ob in room_contents) {
if (living (ob)) {
living_objects += ({ ob });
}
}
matches = match_objects_for_existence ("beefy ring", living_objects[0]);
foreach (object ring in matches) {
if (ring->query_worn_by() == living_objects[0]) {
safe = 1;
}
}
if (safe == 1) {
tell_object (living_objects[0], "You are judged and found worthy!\n");
}
else {
tell_object (living_objects[0], "You are devoured by a grue!\n");
tell_room (this_object(), living_objects[0]->query_short()
+ " is devoured by a grue!\n", ({ living_objects[0] }));
living_objects[0]->do_death();
}
}
Please make sure you understand each step and how all the variables relate to
each other - it's quite complicated. This is easily the most complicated piece of
code we've encountered thus far. However, it provides a baseline for some very
common functionality.
Note that this only works for one single individual in the room – our last step is to
do all of this in a foreach so it steps over each individual and performs the same
checks:
131
Epitaph Online
http://drakkos.co.uk
foreach (ob in living_objects) {
safe = 0;
matches = match_objects_for_existence ("beefy ring", ob);
foreach (object ring in matches) {
if (ring->query_worn_by() == ob) {
safe = 1;
}
}
if (safe == 1) {
tell_object (ob, "You are judged and found worthy!\n");
}
else {
tell_object (ob, "You are devoured by a grue!\n");
tell_room (this_object(), ob->query_short()
+ " is devoured by a grue!\n", ({ ob }));
ob->do_death();
}
}
The tell_room and tell_object functions are new to us – they're what's used to
send messages around the MUD. The first tells the player specifically what
happened to them:
tell_object (ob, "You are judged and found worthy!\n");
The first parameter is the object to which we send the message, and the second is
the message itself.
Tell_room is a little more complicated – it tells everyone in the specific container
object (in this case, a room), except for those specified as being excluded. The
first parameter is the room to send the message to. The second is the message
itself, and the third is an array of objects that are not to see the message. We
exclude the current object from the tell_room, because they already get a
tell_object sent to them.
Update the room and call eaten_by_grue to make sure it works – try it while
wearing the ring, and try it without.
When you've tried it without, you'll notice something disappointing – the death
message is Very Dull:
[Drakkos had a very lucky escape -- killed by Nothing]
Jeez, what is the point of being eaten by a grue if you don't at least get to see an
interesting death message? No point at all, I say... so let's make sure we have one.
Death messages are based on a query_death_reason function defined in the object
that is responsible for calling do_death. So let's add one of those:
132
Epitaph Online
http://drakkos.co.uk
string query_death_reason() {
return "being eaten by a grue";
}
This by itself is not enough, we also need to add the room to the individual's list
of attackers so that the MUD knows what caused the death. We use the attack_by
method for this, before we call do_death:
ob->attack_by (this_object());
ob->do_death();
And that, as they say, is it! Next time the grues find you wanting, the following
death message will be triggered:
[Drakkos had a very lucky escape -- killed by being eaten by a grue]
That's much more entertaining, and that's what we're all here for.
Getting To Our Shop of Horror
At the moment our shop is inaccessible. We need to change that in our
market_southeast file. To avoid people simply stumbling into the store with no
warning, we're going to make entrance based on an exit function. First, we add
the exit as normal to our room:
add_exit ("east", ROOMS + "grue_room", "door");
And then we modify that exit – in this case, to indicate a function that is to be
called when people attempt to take the exit:
modify_exit ("east", ({
"function", "warn_player"
}));
The exit function will only be called if you are not currently sporting the demon
property, so make sure you remove that before you attempt to write this part of
the room.
Whenever anyone tries to take this exit, the MUD will call the function
warn_player. If that function returns a 1, the player is permitted to take the exit.
If that function returns a 0, then they are stopped.
A special word of warning – don't use this_player() in an exit function – if a player
is following another player through an exit, only the first player will ever be
returned from this_player(). Instead, use the parameters passed to the function by
the MUD:
int warn_player (string direction, object ob, string special_mess) {
}
The first parameter is the direction of the exit. The second is the object taking the
133
Epitaph Online
http://drakkos.co.uk
exit (you use this rather than this_player()), and the third is the message that will
be displayed to the destination room. Usually we don't need to worry about either
the first or third parameters, and can concentrate purely on the second.
We're going to provide a warning here - when a player is warned, they get a timed
property placed on them. If they attempt to take the exit while they have the
property, they get let through:
int warn_player (string direction, object ob, string special_mess) {
if (ob->query_property ("been warned about grues") == 1) {
return 1;
}
tell_object (ob, "The room ahead smells of grues... are you sure you "
"want to risk it?\n");
ob->add_property ("been warned about grues", 1, 120);
return 0;
}
However, when taking this exit for the first time, we get an ugly bit out output:
You open the east door.
The room ahead smells of grues... are you sure you want to risk it?
What?
That 'what?' is a consequence of the way the MUD deals with error messages. We
want rid of that, so we need to add a little bit of black magic to our function - a
notify_fail function, which can be used to override the normal 'try something else'
error message as we see above. This is the only place you should use notify_fail
without a very good reason – if you don't know what those reasons might be, then
you don't have one.
int warn_player (string direction, object ob, string special_mess) {
if (ob->query_property ("been warned about grues") == 1) {
return 1;
}
tell_object (ob, "The room ahead smells of grues... are you sure you "
"want to risk it?\n");
ob->add_property ("been warned about grues", 1, 120);
notify_fail ("");
return 0;
}
Notify_fail lets you change the fail message that appears, and in this case we
simply suppress it entirely - we already give them the information they need as
part of the tell_object. Update the room, and everything should fit together like a
greased up jigsaw.
Tell Events
One of the things we have available on Epitaph is a more sophisticated method of
providing information to objects and rooms – it's the tell event system, and lets
people set colours for the messages that the MUD sends. There are several
134
Epitaph Online
http://drakkos.co.uk
versions of this function, but the two you'll be most interested in are
tell_object_event and tell_room_event – they work much like the methods
you've seen above, but also require an event category. These are defined in the
header tell_event.h.
This header file contains various 'tell event classes' – the one we want for a
message such as 'you have just been eaten by a grue' is
EVENT_CLASS_BAD_THING. So we can change our eaten_by_grue function to
honour player colour choices:
tell_object_event (ob, "You are devoured by a grue!\n",
EVENT_CLASS_BAD_THING);
tell_room_event (this_object(), ob->query_short() + " is devoured "
"by a grue!\n", EVENT_CLASS_BAD_THING, ({ ob }));
We need to do the same thing in our warn_player function:
tell_object_event (ob, "The room ahead smells of grues... are you sure you "
"want to risk it?\n", EVENT_CLASS_BAD_THING);
There is a very good reason why we use tell event classes like this – it makes our
game much more accessible if people can change the colours of messages by
event, or even earmuff them entirely. If you are partially sighted and want to limit
game output to a degree, tell events are our way of supporting that – people can
switch off room chats, NPC souls, weather, clan output, effects chatter, and lots
more using this system.. Please, if you're ever sending info via tell_object or
tell_room, use tell_object_event or tell_room_event instead.
Conclusion
Arrays have unlocked some truly powerful functionality for us, but the
manipulation of arrays may still be confusing. All I can recommend is practice – it
doesn't get any more important than understanding how arrays work, and if you
are not one hundred percent on how they work you should find code and read
code and ask questions and shout and scream and stamp your feet until you are.
I'm not even kidding!
The only bit of syntax left for us in Introductory LPC one is the mapping, and
that's the topic for the next chapter. You've already come so far – there's not long
left to go!
135
Epitaph Online
http://drakkos.co.uk
Mapping It Out
Introduction
The last data type we're going to explore as part of our introductory exposure to
LPC is the mapping. It goes by different names in different programming
languages, but if you ever hear people talking about an 'associative array' or a
'hashtable' or 'hashmap', it's this data type to which they are referring. Mappings
are an internally complex data type, but you don't need to worry about any of that
– we just need to know how to manipulate them, and that's our topic for this
section.
So, let's not dilly dally, let's get stuck in!
The Mapping
Imagine you wanted a way to link together pieces of data. For example, if we
wanted to store the average skill level and the name of players, we'd need two
arrays – one of strings containing names, and another of integers holding levels,
like so:
int *average_levels = ({ });
string *player_names = ({ });
Then, when we wanted to add a player's guild level, we'd add their name and level
to the appropriate arrays:
player_names += ({ "drakkos" });
average_levels += ({ 1337 });
We'd need to do this each time that we wanted to add a new pairing of data:
Index
0
1
2
player_names
"drakkos"
"hambone"
"eek"
average_levels
1337
1227
4444
A mapping is essentially a data structure that allows for that kind of relationship
between elements. Writing your own association between arrays is time
consuming and prone to problems when it comes to making sure everything gets
manipulated at the right time at the right way in both arrays, and as soon as one
mistake is made with this the entire set of data loses integrity. Using a mapping
means that the MUD does all the Tedious Busy Work for you, and that's what we're
looking to do as programmers – offload tedious busy work onto the computer.
136
Epitaph Online
http://drakkos.co.uk
A mapping is declared thusly:
mapping my_map;
And set with an empty internal state using the mapping notation:
my_map = ([ ]);
Note that this is subtly different from an array – it's square brackets within
rounded brackets rather than braces within rounded brackets.
If we want to setup a mapping with some starting values, we use the following:
my_map = ([
"drakkos" : 1337,
"eek" : 4444,
"hambone": 1227,
]);
Note that each of the pairs are separated by a colon rather than a comma.
As with the array, we need to know how to manipulate a mapping before we can
really make use of it, and before we do that we need to talk about the terminology
of a mapping. Unlike an array, a mapping does not have an index by which it can
refer to elements. Instead, a mapping has a key and value pairing. A particular
key is linked to a particular value. Our example above, in a mapping, would look
like this:
Key
"drakkos"
"hambone"
"eek"
Value
1337
1227
4444
Thus, the key "drakkos" will have the value 1337, and the key "taffyd" will have the
value 666.
To put a value into the mapping, we use the following notation:
my_map ["<key>"] = <value>;
So, to add in a fourth entry:
my_map ["gruper"] = 69;
At this point, our mapping looks like this:
Key
"drakkos"
"hambone"
"eek"
"gruper"
Value
1337
1227
4444
69
137
Epitaph Online
http://drakkos.co.uk
To pull out a value, we use a similar notation:
int value;
value = my_map["gruper"];
We don't need to know in what position of the mapping we need to look for
values, because the mapping does that for us. Indeed, you can't guarantee that
the order in which you add values has any relationship to how they are stored
internally for all sorts of Complicated Technical Reasons. You have to think of it a
bit like a bag of data rather than a list. This is an important point – just because
we added things in a particular order, that does not guarantee in the slightest
that's the order they are stored in the mapping.
To remove an element from the mapping, we use the map_delete function, like so:
map_delete (my_map, "eek");
This looks through our keys in the mapping
match from the mapping:
Key
"drakkos"
"hambone"
"gruper"
for a match, and then removes that
Value
1337
1227
69
Although syntactically simple, they are quite complicated conceptually – so once
again, we're going to use an example to illustrate how they actually work.
The Magic Hate Ball
We're going to add a new item to our marketplace – a magic hate ball mounted on
a plinth. This will be in market_southwest:
#include "path.h"
inherit STD_OUTSIDE;
int do_hate();
void setup() {
set_short ("southwest corner of the marketplace");
add_property ("determinate", "the ");
set_day_long ("This is the northeast corner of the marketplace. "
"A magic hate ball is mounted on a plinth, attached by a chain. "
"Sunlight glints on its malevolent surface.\n");
set_night_long ("This is the northeast corner of the marketplace. "
"A magic hate ball is mounted on a plinth, attached by a chain. "
"Starlight glints on its malevolent surface.\n");
add_item ("magic hate ball",
({
"long", "The magic hate ball gives wisdom when you shake it!",
"shake", (: do_hate :),
}));
set_light (100);
138
Epitaph Online
http://drakkos.co.uk
// Setup stuff as before
}
int do_hate() {
return 1;
}
Note that we're using the function pointer notation for the add_item – we need to
use that when we attach functions to verbs in add_items. In Introductory LPC 2 it
will become clear why, but for now you're going to have to accept that sometimes
you can use the # notation, and sometimes you need to use the function pointer
notation.
What we're going to get the magic hate ball to do is give people who shake it a
venomous message, and if they shake it again it repeats the same message. Thus,
everyone gets a random message the first time they shake it, but the ball is
consistent with its advice. We need a class-wide variable for this so that it gets
stored between executions of our function:
mapping previous_responses = ([ ]);
In our function, we first need an array of possible responses:
string *random_responses = ({
"Shut up and die.",
"I slept with your wife.",
"Suicide is your only option.",
"Everyone hates you.",
"You suck.",
});
Back to the idea of incremental development – let's just follow through the
process of someone getting a response for the first time. We select a random
response, and display it:
int do_hate() {
string response;
string *random_responses = ({
"Shut up and die.",
"I slept with your wife.",
"Suicide is your only option.",
"Everyone hates you.",
"You suck.",
});
response = element_of (random_responses);
tell_object (this_player(), "The magic hate ball says: "
+ response + "\n");
return 1;
}
To actually use our mapping, we need to start setting and querying values in it.
First, we need a key – this will be the name of the player. We declare a string
variable called player at the top of our function, and then set it to be whatever the
139
Epitaph Online
http://drakkos.co.uk
player's name is:
player = this_player()->query_name();
Before we get to generating a response, we need to check to see if there's been a
previous response by querying our mapping with the key we just acquired:
response = previous_responses[player];
The response we then give to the player is dependant on whether there is
anything in this variable, like so:
Putting that all together gives us the following:
int do_hate() {
string response;
string player;
string *random_responses = ({
"Shut up and die.",
"I slept with your wife.",
"Suicide is your only option.",
"Everyone hates you.",
"You suck.",
});
player = this_player()->query_name();
response = previous_responses[player];
if (!response) {
response = element_of (random_responses);
previous_responses[player] = response;
tell_object (this_player(), "The magic hate ball says: "
+ response + "\n");
}
else {
tell_object (this_player(), "The magic hate ball says:
"told you: " + response + "\n");
}
I already "
return 1;
}
Let's step through that process from the perspective of a player. Player comes
along and says 'Hey, cool eight ball. I am going to shake it 'till I break it!'. They
shake the ball, and the first thing the function does, after setting up the random
responses, is to get the player's name, which is "drakkos", and then query our
mapping to see if the key "drakkos" has a value associated with it. Our mapping
though is completely empty.
It's empty, so no response is to be found. The code then picks a random response
from our list, let's say it's "You suck", and then puts that value into the mapping
associated with the key of the player name:
140
Epitaph Online
http://drakkos.co.uk
Key
"drakkos"
Value
"You suck."
It then prints out our message:
> shake ball
The magic hate ball says: You suck.
You shake the magic hate ball.
'My word!', exclaims Drakkos. 'That is rather rude, and no mistake! Why, I shall
shake this little rascal again to see what it has to say!"
Drakkos shakes the ball again – this time, when it comes time to query the
mapping, there's already a response stored. That gets stored in the variable
'response', and when we get to the if statement response is no longer empty and
so the else clause is executed:
> shake ball
The magic hate ball says: I already told you: You suck.
You shake the magic hate ball.
Saddened and appalled by this inhuman criticism, Drakkos wanders off into a
nearby room, and ends up eaten by a grue. This area, he decides, was not written
by a nice person. Why so mean, magic hate ball? Why so mean?
More Mapping Manipulation, Matey
There are three further things we might like to do with a mapping in order to
manipulate it effectively. One is to get a list of all the keys in a mapping – we can
do that with the keys function. It returns it as an array:
string *names;
names = keys (my_map);
This will give us everything in the left hand column of the mapping. Going back to
our example mapping:
Key
"drakkos"
"hambone"
"eek"
"gruper"
Value
1337
1227
4444
69
Getting the keys of this will return the following array:
({"drakkos", "hambone", "eek", "gruper"})
We cannot guarantee that the elements will be returned in that particular order,
141
Epitaph Online
http://drakkos.co.uk
but all of those elements will be present.
Another thing we might like to be able to do is get all of the values. This is done
in a similar way, using the values function:
int *levels;
levels= values (my_map);
This will return the following array from our example mapping:
({ 1337, 1227,4444, 69 })
Again, in no guaranteed order. If we want to enforce some kind of order on this,
the we must handle it ourselves.
Finally, we may wish to step over each of the key and value pairings in turn,
performing some kind of operation. We can do that longhand like so:
string *keys;
string key, value;
keys = keys (previous_responses);
foreach (key in keys) {
value = previous_responses[key];
tell_object (this_player(), key + " was told '" + value + "'\n");
}
However, LPC is nice enough to give us a foreach structure purely for handling
mappings! The above code rolls into a simple foreach, like so:
string *keys;
string key, value;
foreach (string key, string value in previous_responses) {
tell_object (this_player(), key + " was told '" + value + "'\n");
}
Let's try that out by adding a 'consult' option to our ball:
add_item ("magic hate ball", ({
"long", "The magic hate ball gives wisdom when you shake it!",
"shake", (: do_hate :),
"consult", (: do_consult :),
}));
We'll need a function prototype at the top of the code:
int do_hate();
int do_consult();
And folding in the code we just discussed:
142
Epitaph Online
http://drakkos.co.uk
int do_consult() {
string key, value;
foreach (key,value in previous_responses) {
tell_object (this_player(), key + " was told '" + value + "'\n");
}
return 1;
}
Now we can consult to see what everyone has been told by the hate ball:
> consult ball
Drakkos was told 'I slept with your wife.'
You consult the magic hate ball.
Alas though, if no-one has been told anything, we get a fairly uninspiring
message:
> consult ball
You consult the magic hate ball.
We should put a check to ensure that if the mapping is empty, we get the
appropriate message. Luckily sizeof works for mappings as well – it gives us the
number of keys in a particular mapping, so we can fix that quite easily:
int do_consult() {
string key, value;
if (sizeof (previous_responses) == 0) {
tell_object (this_player(), "No-one was told anything. Now curl up "
"and die.\n");
}
else {
foreach (key,value in previous_responses) {
tell_object (this_player(), key + " was told '" + value + "'\n");
}
}
return 1;
}
Now we get a much better experience when we try to consult an empty hate ball:
> consult ball
No-one was told anything. Now curl up and die.
You consult the magic hate ball.
Thanks for the update, magic hate ball!
143
Epitaph Online
http://drakkos.co.uk
Conclusion
It's unlikely you're going to need to do much with mappings at this stage of your
creator career – they are usually a part of more complicated functionality. It's
important though that you understand what they are and how they are
manipulated – you'll likely encounter them quite a lot as you read through
example code. They are quite unspeakably useful – so useful in fact that when
working in an environment where you don't have easy access to them, you miss
them with a primal yearning. Luckily that's not a problem for us on Epitaph, for we
are well supported as far as our mapping requirements are concerned, thanks to
the support built into the driver and mudlib.
144
Epitaph Online
http://drakkos.co.uk
Debugging
Introduction
Nobody writes flawless code. That is as true of beginners as it is of people who
have been coding for decades. Developing software is a constant battle against
bugs, logic errors and the endless grind of maintenance. The skills that go along
with fixing broken code are tremendously important to a creator, as is the
mindset that allows you to be the kind of person who will sit down for hours to
track down a particular bug. It makes you a very strange person mind, but it's
that mindset that separates someone who will eventually be a self-sufficient
creator and someone who will always need someone there to fix their problems.
You should very much aim to be the former, because there will be vanishingly few
people who will be willing to do the latter.
The Two Different Kinds of Errors
The errors you will be most familiar with are syntax errors – those horrible bits of
red text that flash up when you update a piece of code that won't work. Those
are the most common errors you encounter, and while it may seem unlikely at the
moment, they're also the ones that will stop being a problem for you to solve on
your own. After a while, you'll start to appreciate what the errors are telling you
and how to fix them.
The first mistake novices make when dealing with syntax errors is to not read the
error. They see the flash of red, decide 'my code is broken', and then never make
the mental connection between one and the other. The error messages in FluffOS
are a lot better than they are in many languages, and they always contain within
them the information you need to resolve the error. We'll come back to that later.
Getting an object loaded is only part of the struggle – the next part is making sure
it actually works. When your object doesn't do what it's supposed to, it's called a
logic error. It's not a syntax problem (if it was, you wouldn't be able to load the
object at all), it's a problem with what you have told the code to do. Those kind of
errors never go away, but there are ways you can track them down, and we'll talk
about them later in this chapter.
The problem with a logic error is that it may not be obvious – it may only present
itself once in every ten times you perform a task, or maybe only if you have a
certain item in your inventory, or perhaps only at six am on a Tuesday. The only
way you can have confidence that your object is working the way it is supposed to
is to test it. We'll talk more about that in a bit.
Fixing your Syntax Errors
Seriously, read the error – it contains all the information you need to fix it. In
145
Epitaph Online
http://drakkos.co.uk
some odd cases, the error message might not be entirely transparent, but each of
the words it gives you is there for a reason. Let's look at a broken version of
Beefy:
#include <npc.h>
inherit STD_MONSTER;
void setup() {
set_name ("beefy");
set_short ("Captain Beefy");
add_property ("determinate", "");
add_property ("unique", 1);
add_adjective ("captain");
add_alias ("captain");
set_gender (1);
set_long ("This is Captain Beefy, a former officer of Her Majesty's "
"Secret Service. Once he was a pretty impressive guy, with a large "
"string of accomplishments to his name. Now, he's just another "
"survivor in the grim darkness of the LPC apocalypse.\n");
basic_setup ("human", "survivor", MONSTER_LEVEL_NORMAL);
setup_nationality(MUDLIB_NATIONALITIES + "british", "scotland");
load_chat(10, ({
2, "' Please don't hurt me.",
1, "@cower",
1, "' Here come the drums!",
1, ": stares into space.",
1, "' Oh, hello there $lcname$.",
}));
load_a_chat(10, ({
1, "' Oh please, not again!",
1, "' I don't get medical insurance!",
1, "@weep",
}));
set_challenge_profiles (([
PROFILE_SHARP_WEAPONS : CHALLENGE_INCREDIBLY_LOW,
PROFILE_PIERCE_WEAPONS : CHALLENGE_INCREDIBLY_LOW,
PROFILE_BLUNT_WEAPONS : CHALLENGE_INCREDIBLY_LOW,
PROFILE_UNARMED : CHALLENGE_INCREDIBLY_LOW,
]));
set_greet_response ("' Oh, I hope you are not here to kill me.
"hello.");
I mean, "
set_unknown_response ("' I don't know anything about that.");
add_response ("murder", "' Please don't kill me!");
add_response ("kill", "' No, please! God, please don't kill me!");
dress_me();
request_item ("plain bra", 100
init_equip();
}
146
Epitaph Online
http://drakkos.co.uk
Trying to update this NPC gives us the following set of syntax errors:
> update /d/support/deadville/debugging/broken_beefy.c
/d/support/deadville/debugging/broken_beefy.c line 39:
'PROFILE_SHARP_WEAPONS' before CHALLENGE_INCREDIBL
/d/support/deadville/debugging/broken_beefy.c line 39:
'CHALLENGE_INCREDIBLY_LOW' before the end of line
/d/support/deadville/debugging/broken_beefy.c line 40:
'PROFILE_PIERCE_WEAPONS' before CHALLENGE_INCREDIBL
/d/support/deadville/debugging/broken_beefy.c line 41:
'PROFILE_BLUNT_WEAPONS' before CHALLENGE_INCREDIBL
/d/support/deadville/debugging/broken_beefy.c line 42:
'PROFILE_UNARMED' before CHALLENGE_INCREDIBL
/d/support/deadville/debugging/broken_beefy.c line 56:
();
Undefined variable
Undefined variable
Undefined variable
Undefined variable
Undefined variable
syntax error before
You can check the file at /d/support/deadville/debugging/broken_beefy.c to
confirm the line numbers link to the lines of code I provide.
Let's look at the first error, and analyze it:
/d/support/deadville/debugging/broken_beefy.c line 39: Undefined variable
'PROFILE_SHARP_WEAPONS' before CHALLENGE_INCREDIBL
The error says 'undefined variable', which seems pretty self-explanatory – it thinks
we're trying to use a variable that we haven't defined. It thinks that variable is
called PROFILE_SHARP_WEAPONS, and it tells us line 39 which is the one marked in
bold below:
set_challenge_profiles (([
PROFILE_SHARP_WEAPONS : CHALLENGE_INCREDIBLY_LOW,
PROFILE_PIERCE_WEAPONS : CHALLENGE_INCREDIBLY_LOW,
PROFILE_BLUNT_WEAPONS : CHALLENGE_INCREDIBLY_LOW,
PROFILE_UNARMED : CHALLENGE_INCREDIBLY_LOW,
]));
Now, we know that PROFILE_SHARP_WEAPONS is a define in #challenges.h,
because that's what we learned in that chapter. However, it is complaining that it
doesn't know that token. It is actually telling us what the problem is – it doesn't
recognise PROFILE_SHARP_WEAPONS. There are two likely causes here – we
mistyped the token we need to use, or the #define is missing. We know it's not
the former (because we know what the token should be), so it must be the latter.
Sure enough, if you look at the top of our beefy file, there is no include for
<challenges.h>
If we add that to the top and update, you'll see we get a pair of new errors:
/d/support/deadville/debugging/broken_beefy.c line 40: Undefined variable
'CHALLENGE_INCREDIBLY_LOW' before the end of line
/d/support/deadville/debugging/broken_beefy.c line 57: syntax error before
();
We have just seen that first little rascal of an error in our last fix, and we just
#included the right file. So it's not that it can't find the define, it's that we've
mistyped it. Reading that include file reveals that the defines we need look like
147
Epitaph Online
http://drakkos.co.uk
this:
#define
#define
CHALLENGE_EXTREMELY_EASY
CHALLENGE_INCREDIBLY_EASY
0
1
So, we don't use CHALLENGE_INCREDIBLY_LOW – we use
CHALLENGE_INCREDIBLY_EASY. Change that, and we end up with a single error to
fix:
/d/support/deadville/debugging/broken_beefy.c line 57: syntax error before
();
'Syntax error' doesn't sound helpful, but what it's saying is 'somewhere around
line 57, your code is not well formed'. It can't be more precise than that, and
sometimes the actual error won't be on that line at all (it can only tell us when it
realises there is an error, not where that error actually is). Line 57 is the bold line
though:
request_item ("plain bra", 100
init_equip();
The error in this case is not actually on line 57 – instead it's on the line above. It's
missing a closing bracket and brace.
Common syntax errors include missing semi colons, missing brackets, missing
braces, or extra braces. Basically, the error tells you 'read that code a bit better –
you've forgotten something'.
The actual syntax errors will get easier to spot as time goes by, but the important
thing is to read the information. Everything you need to know is there!
Logic Errors
Logic errors are the tricky ones – they're the errors you get when your code
doesn't do what you think it does. They can be hugely obvious (you clone an
NPC, it says 'DOES NOT COMPUTE' and then your monitor bursts into flames), or
tremendously subtle (if I eat this NPC when I am sitting on a chair, then ten
seconds later I will get a runtime error). The only way you can really find them is
to exhaustively test your object, actually trying to make it break.
There are all sorts of formal ways in which testing is done, but I won't go over
them here because they are very dull, and I have written two and a half million
variations of the explanations behind them over my lifetime. I can't do it again. I.
Just. Can't. You can Google white box testing and black box testing if you want
to know more.
A good test is one that exposes a flaw in your code. Make no mistake – the
players are going to find all the problems in your code as time goes by. If you're
lucky, the errors won't be abuseable and the players will be generous enough to
report the problem for you to fix. That doesn't excuse you your duty to do first
pass checking to make sure it all works properly though.
148
Epitaph Online
http://drakkos.co.uk
First of all, make sure that your core object functionality works the way it's
supposed to – try it out a few times. Look at our magic hate ball – that's an
example of functionality that needs to be properly tested. Try it out yourself a
few times. Try it out with a test character. Try watching your test character use
it. Make your test character watch you use it. Make sure it resets properly. Think
of all the possible ways in which a player can legitimately interact with your code,
and then test all of them.
Then, think of all the ways in which a player can illegitimately interact with your
code. Try using invalid syntax, try referencing to objects other than the ones that
are expected. Put yourself in the role of the smarmy bastard who is trying to
prove how stupid you are for not anticipating that the magic hate-ball doesn't
work properly with a video recorder. Become that guy (or gal).
Then, when you've done that, get someone else to try to break your code. You
are the worst person to test your own code beyond a certain point, because
subconsciously you already know where the code is a little shaky, and unless you
are tremendously disciplined you will find yourself entirely unconsciously
directing your testing away from the weak areas. Ideally, you will find someone
who hates you to test your code, but who could hate you with your cheeky little
smile and beautiful eyes? Nobody, that's who.
Only once you're pretty sure that the code works the way it's supposed to work
should you then turn your work over to our playtesters – their job isn't to uncover
all the obvious bugs in your code, it's their role to explore your code from a
(mostly) player perspective. You should make sure that the code is as robust as
you can make it before it reaches them.
I found an error!
Good! Remember, all code has errors in it (the industry average is about 15 to 50
errors per 1000 lines of code. Yikes!) . It's better you know what those errors are
because that way you can fix them. That brings us to the next of your tasks as a
software developer trying to ensure robust code – finding out why your code
misbehaves.
Much like finding errors, sometimes this is easy – maybe it was a typo that didn't
result in a syntax error, such as:
set_challenge_profiles (([
"warior" : CHALLENGE_INCREDIBLY_EASY,
]));
When you consider Beefy or check out his skills, you'll see they aren't what they
should be – that leads you instantly to the challenge profiles, and you can make
some reasonable guesses as to what the problem might be and fix it.
Sometimes though, the cause is far more subtle, and you're going to need to
spend some time working it out. I'm afraid there's no shortcut there – you're
either the person who can bring yourself to spend four hours tracking down an
obscure bug, or you're not.
149
Epitaph Online
http://drakkos.co.uk
In all cases, the process for dealing with logic errors you can't find is five-fold.
Reproduce your Errors
The worst errors are the ones that are intermittent. There is, deep in the mudlib,
an error that sometimes shows up where the parser will cut off the first two letters
of a string. 'look chair' will return the error 'cannot find 'air', no match'. The
reason that bug still exists is – no-one has found a way to reliably reproduce the
damn thing. If you can't reliably reproduce your error, no amount of code-fu is
going to save you. You can make 'best guesses' and add some general 'band-aid'
solution that stops the error occurring, but at best that is an ugly hack. If your
program has an off by one error that you cannot reliably reproduce, you can fix it
by checking to see when a value is off by one and correcting it. The problem is
you haven't fixed the error, just the symptom.
Spend time working out what causes the error – here is where an effective testing
strategy can be a life-saver. You need to log what you've tried, and use that
information to rule out possible problems. Don't just stare at the code, start
drilling into it.
Use Debugging Messages. A Lot.
To find the cause of an error, you need to understand what Is happening inside
your code. Because we have no real formal debugging software for LPC, we need
to do it the Old School way – stuff your code full of informs that tell you what is
happening within. There is a lovely function that will help you with this – it's
called tell_creator:
tell_creator ("drakkos", "What up, homes!\n");
You can profitably use this within your code to see the value of each of the
variables to make sure they are what you're expecting. Tell_creator is actually a
very powerful function, and lets us slot all sorts of things into its eager parameter
slots:
tell_creator ("drakkos", "What up, homes: %s!\n", "bing");
Within tell_creator, you can use a number of arguments which get slotted, in
order, into the parts of the string you identify with control codes. %s indicates
that ' a string goes in here', so when the tell_creator triggers I will see:
What up homes: bing!
You can use %d for whole numbers, %f for real numbers, and the tremendously
versatile %O for everything else (including objects). Fill your code full of these
messages to make sure that what is happening is what you actually thing is
happening.
150
Epitaph Online
http://drakkos.co.uk
Simplify, Simplify
Much of the code in a particular function is window dressing, as far as solving an
error goes. It's parameter handling, and consistency checking, and other such
miscellaneous house-keeping. It's all absolutely vital, but not when we're trying to
locate a bug. In fact, all it does is complicate our search.
Simplifying our function can make the search much easier - comment out any of
the code that doesn't relate to the error so your search is restricted to only
relevant code. Then, as you eliminate particular lines as being innocent, you can
comment them out and replace them with place-holder values to further reduce
complexity.
Try Things
Once you've got your code down to the statements that are important, try
changing things - if you have variables being set, set them with different values to
see what happens. If you have counter loops, try changing the condition. Make a
careful note of what effect each change has, and analyse those changes with
reference to the flow of logic in your program to see how each change is affecting
the execution.
Understand!
This is the important one - once you've found a bug, understand what is causing
it. Don't give in to the temptation of simply fixing it or recasting the code so that
it doesn't happen without learning from it. Partly this is simply good practise - you
learn more from your mistakes than you ever do from your successes, but only if
you take the effort to ensure that you understand why your errors occurred.
However, it's also very important from a pragmatic point of view. If you have a
loop that is being iterated over one more time than it should, and you can't see
why, then you can fix it by changing the continuation part of the loop so that it
simply iterates one less time.
All fine and dandy, and you've fixed your immediate problem - but fixes like this
have a tendency of fixing only a symptom and leaving a larger problem
unaddressed. The value of your variable wasn't what you were expecting - if you
are making a reference to that variable elsewhere, or basing a calculation on it
then you're going to have problems there as well.
Conclusion
It's not fun, and it's not entertaining. It can be terribly frustrating and hateful, but
it's a part of development – writing code isn't enough, you have to write code that
works. Nobody does this, of course – but you get better at it over time as you
start to learn what is easy to fix, what is hard to fix, and why one should be better
than the other. Having the mindset to be able to focus for hours on solving a
151
Epitaph Online
http://drakkos.co.uk
single bug is an important part of being a coder – it may seem like a terrible effort
to reward ratio, but it's only when working your way through the complex flow of
logic in a complex program that you can really exercise all the analytical skills that
are needed to be able to developer the higher tier of LPC objects that can be
created.
152
Epitaph Online
http://drakkos.co.uk
So Long
Introduction
You've done very well to get this far – programming is not an easy task regardless
of what anyone tells you. Perhaps the most frustrating thing that goes with
learning how to do it is how easily some people 'get it'. It's unfortunate that not
everyone finds it as easy as other people, but the willingness to persevere can
trump that initial natural understanding. Having the personality to continue when
it gets hard is what separates a programmer from a talented amateur.
You may very well fall into the category of talented amateur yourself, in which
case this should be a warning for you too – everyone struggles at one point or
another to get something working, and if it's been easy all the time then you need
to be willing to dig deep and find the will to go on when things start to become
difficult.
Anyway, in this brief chapter we shall wrap up our initial foray into LPC and talk
about what happens next.
The Past
Deadville isn't exactly what you'd call a finished area – there are bits that are not
described, and other bits that have no interesting functionality. That's okay
because it's not for the eyes of players – it's a project so that you can see the
process by which an area can be built. There are bits of Deadville that are quite
sophisticated, and if you've managed to keep up with the code then you can make
use of variations of what we've talked about to great effect.
Learning how to develop on Epitaph is a two-pronged problem. To begin with, you
need to know how programming code works, and this is in itself an entire
textbook of knowledge. This is a transferable skill – the coding structures that
you've learned as part of this introduction to LPC are transferable to most
programming languages. Individuals interested in seeing how transferable their
skills actually are invited to explore my Java textbooks at http://www.monkeys-atkeyboards.com
Combined with the understanding of generic coding structures, you need to learn
the specific code that is unique to Epitaph – how to create rooms, NPCs and items.
Then you need to learn how to link them up, and make them do interesting
things.
A good creator is not just a coder – a good creator has an unusual blend of skills.
A good creator can architect projects, write well, and produce robust code. A
beginner can only be expected to master one of these to start with, but you need
to be able to do all three before you can think of yourself as a well-rounded
153
Epitaph Online
http://drakkos.co.uk
creator. It's not an easy task.
So, let's recap what you've learned so far, and provide some context... there's
been a lot of content, and so you may be forgiven for having not really realized
how much you've actually done.
In terms of generic programming structures, you've learned:









Variables and variable scope
If and Else-if statements
Switch statements
Functions and function scope
For and Foreach loops
While loops
Arrays
Mappings
Header files
A substantial subset of these are present in most modern programming
languages, and understanding how these work in LPC is the first step to
understanding how they work in other environments. The syntax may be different,
but the concepts are identical.
In terms of what you've learned about Epitaph development, we can add to that:










Inside and outside rooms
Day and night items, chats and long descriptions
How to use the linker
NPCs
Searching items
How to use the task mapper
Virtual objects
Modifying exits
Faction shops
Object matching with match_objects_for_existence
That's quite a list of accomplishments, and a well deserved pat on the back is
deserved for you for having learned all of these. I won't say mastered, because
there's always more to learn about all of these things, and only diligent
exploration of the MUD will reveal the more obscure features of all the things
we've discussed.
154
Epitaph Online
http://drakkos.co.uk
The Present
So, what happens now you've learned all of this? Well, you've undoubtedly been
assigned a newbie project by your domain administration, and your focus should
be on developing that to as high a standard as you can. There are also small
projects available for those who want to apply their new found skills to a small,
open project with the intention of it being slotted into an existing area of the
game to add richness. Just contact me (Drakkos, if you hadn't realized) and we
can chat about that.
But more than simply your current projects, you should be exploring the Epitaph
codebase. Certainly within your own domain, think of things you enjoyed while
playing and hunt out the code that handled it. Read the code, and try to
understand how it works. All coding is, to a greater or lesser degree, plagiarism –
you can gain huge amounts of understanding by just reading what has come
before.
The problem with this of course is that you don't necessarily know if the code that
you find is 'good code'. Epitaph has the benefit of being a very new MUD, and we
don't have decades of legacy code lying around serving as traps for the unwary.
Nonetheless, our rapid pace of development means that even in our lifetime there
is code that is already 'out of date'. If you wonder whether code is worth using as
a template, then feel free to contact me for guidance.
As far as exploring code outside your domain goes, you should definitely acquaint
yourself with the code in /mudlib/ and /items/. For other domains, ask the
domain lord if they mind you scrutinizing the code for inspiration. Hardly anyone
will mind, but it's polite to ask. Talk to other creators, especially those who may
be as new as you. Part of what makes being a creator so much fun is the social
environment in which you function, and you should take full advantage of that.
You should also feel free to talk to members of the Support domain, particularly
the leader of the domain – it's only by talking to people like you that we know
what things you want to know about! Asking for information on a particular topic
is as helpful to us as it (hopefully) is for you!
The Future
There will come a point, hopefully not too far in the future, when you feel you're
ready to learn new things. We've only scratched the surface of the kind of things
that get written on Epitaph, and there is exciting territory to come. From here, you
can move on to Being A Better Creator, Working With Others, and Intermediate
LPC.
Introductory LPC is the major section in this substantial handbook. There are four
of these sections - two of them focus on the softer-side of Epitaph creators, and
the last is a more advanced guide to LPC development. You should familiarise
yourself with all of them - there's a lot discussed across these texts.
155
Epitaph Online
http://drakkos.co.uk
It would be a good idea to at least finish your first domain project before you
worry about expanding into this territory – getting a feel for the complexity of
development and the things that you wish you knew how to do will make the
lessons in later versions of the texts more valuable.
Conclusion
That's us for now! You're done with Introductory LPC and you can emerge from
the room in which you keep your computer... the bright sunlight may hurt at first,
but that'll go away with time. We'll see you again soon though, oh yes. We'll see
you soon...
156
Epitaph Online
http://drakkos.co.uk
Reader Exercises
Introduction
Now that we’ve reached the end of your introductory voyage through LPC, let’s
give you some homework to see how well you’ve learned your lessons. Full
answers to (almost) all of these exercises are available, but please attempt them
yourself first – you learn more by trying than by reading what other people have
done for you.
Exercises
Fill in the Blanks
We haven’t done a lot of descriptive work for Deadville, focusing instead on the
code we needed to build it. You’ll learn a lot however from going over each of the
rooms and filling in the blanks. Ensure each room has the following:



A day and night long description
Day and night add_items
Day and night room chats
Importantly, make sure every noun in your long description and in your add_items
is included. This is something people will look for in your rooms - believe me.
There is no worked example for this, but if you go through this process and
would like to volunteer it as an example for other people, then send it to me and
we can make it so.
Connect to your Workroom
Having gone to the trouble of making this village, you should be able to admire it
whenever you like – it should connect into your workroom. However, you’re also
at some point hopefully going to progress onto Zombieville. Create a new room in
your directory and call it access_point. Link this room to your workroom, and link
Deadville to it too. Add an exit function to the Deadville exit that makes sure only
creators can enter your development.
Playing Dress Up
Most of our NPCs are dressed by the NPC clother. You should improve this
situation by making sure they are appropriately dressed with their own unique
ensemble. Each NPC should have a shirt, a pair of trousers, underwear, socks, and
a pair of shoes or boots. Make sure this is true of all of your NPCs, and that you
157
Epitaph Online
http://drakkos.co.uk
pick sensible instance of each as to befit their status. If no sensible item exists,
make one.
The Very Model of A Modern Major General
Captain Beefy has no unique weapon, which is somewhat out of theme for a man
of his military background. Using the same kind of system as you did for his ring
and his boots, create a beefy_sword. It will be saved as a .wep file, and you should
look at existing examples of virtual weapons (in /items/weapons/) to ensure you
set all the right values.
Once you’ve done this, give him a suitable scabbard (.sca), and make sure both of
these are provided to him when he is setup. His sword should be cloned inside the
scabbard, and he should be wearing the scabbard along with the rest of his outfit.
Responsiveness
While our NPCs have several responses built into them, it would be nice if they
were more responsive generally. Add in code to each of them that makes them
respond positively to being greeted, both as a say and as a soul.
Additionally, make them respond to your name with an appropriate amount of
fear and cowering awe.
Make them all respond to the names of each other, with a little comment about
who they are (those NPCs that already have such responses can be left alone). For
example, if I say ‘beefy’ they could say ‘Oh, Beefy – he was our first NPC!’.
Finally, when I ask where certain NPCs are, such as ‘Where is Beefy?’, they should
tell me where I am likely to find them. You don't have to track them down in
code, something like 'Stabby Joe is usually in his shop' will suffice.
Leashing
The code we have for loading NPCs has the impact of ‘leashing’ them to a specific
room – if Beefy is found elsewhere in the village when after_reset is called, he’ll be
magically transported somewhere else. Change this so that he (and all other
relevant NPCs) get moved into the room only if they have no environment. To
make sure that they cannot then wander outside Deadville, put a creator check on
your exit from the village (in street_01).
Climbing the Mountain
One of the things that changing the move messages with modify_exit allows you
to do is create a sense of context to moving. Change the path leading to the
village square so that it simulates climbing up a mountain trail. For example:
You climb the winding trail to the northeast.
158
Epitaph Online
http://drakkos.co.uk
Similarly, when heading back down the trail to street_o1, have that described to
the player also.
Calm Yourself
Modify our crazy Stabby so that he cools down over time after having been riled to
anger.
Additionally, make it so that he gives a set of tiered warnings to players based on
how angry he actually is.
Send Suggestions
Do you have ideas for exercises that would be cool, or are you trying to do
something new in Deadville and just can’t make it work? Send them to me,
Drakkos at [email protected], and we can look at incorporating
them into this section
159
Epitaph Online
http://drakkos.co.uk
Section Two
Being a Better
Creator
160
Epitaph Online
http://drakkos.co.uk
Welcome to the Learning Zone
Introduction
In this material we are going to look at the ancillary skills that go with being a
creator – not the technical skills particularly. Instead we're going to look at the
other skills you're going to need to master. These are the ‘softer' skills in that
they don't require Code-Fu, but they do need a considerable understanding of
how everything fits together and the impact one decision is likely to have on
another.
They also require a comprehensive understanding of how to put together games –
make no mistake, although Epitaph is a volunteer text environment, you are still a
games developer. Hardly any games have the depth, complexity, and sheer size of
Epitaph. There may be no flashy graphics, but it doesn't mean you don't need a
considerably broad set of skills to add to and improve the gaming experience.
The Plan for This Course
It is expected that you will progress onto this material once you have completed
Introductory LPC. You either have your own version of Deadville in your /w/ drive
(which is recommended) or at least understand all of the code that has gone into
the ‘here's one I made earlier' versions available in /d/support/deadville. The
more adventurous among you may have written your own variation of Deadville,
which would be even better.
In this course of material, we're going to plan out a second village – not in the
half-hearted way that we planned out Deadville (which was, after all, purely based
on a map). We're going to consider all of the necessary things that our new village
will have, and how they support the wider MUD context. We didn't even begin to
consider that for Deadville because it just wasn't important – Deadville was a
demonstration of syntax, not a playable area.
Our second village is going to be called Zombieville. It's going to have quests, an
infrastructure, and all sorts of associated goodness. It's our job in this material to
work out exactly of what that goodness is going to consist, and more importantly
how that goodness can contribute to an overall plan for the area.
What we are not going to do though is code any of it. None of it. Not a single line
of code, not a skeleton of a room, nothing. Nada. Zip. That's for the section on
Intermediate LPC. Breaking the coding from the design is a good strategy because
it means we have to think about what we're doing before we start coding, and
that's something that hardly any of us do often enough.
First of all, let's talk a little bit about what we're actually trying to achieve with a
161
Epitaph Online
http://drakkos.co.uk
new area for Epitaph. As an opening gambit, let's talk about what defines a good
feature of a game, and then look at the implications that has for designing an
interesting area for the MUD.
What's In A Game?
What is it that makes one game more appealing than another game? What makes
Game A better than Game B?
Obviously the answer to this will change from person to person – different people
enjoy different elements to different degrees. A good game will provide
something that appeals to everyone, at all levels of the game. Or, more
realistically in a world of constraints, a good game will provide something that
appeals to specific genres of gamers. Within Epitaph though, we have many
genres of gamers – we'll talk about that in a later section of this material.
Essentially what all game design boils down to is the provision of interesting
game decisions. All games, at their fundamental level, are about providing an
individual with a set of choices between different possible outcomes. Those
choices become interesting, from a psychological perspective, when the outcome
of those decisions is sufficiently clouded in uncertainty that, at the time of
choosing, there isn't an obviously correct choice.
Imagine the choice between the following options:


Die
Stay alive
Providing a player with those options is not providing them with the chance to
choose between interesting courses of action. There's an obvious right answer
and an obvious wrong answer. On the other hand, let's consider a second pair of
decisions:


Leap the chasm in the hope you reach the other side
Go back down the track and find a new way to go
Now the decision becomes more interesting – it involves balancing the likely
difficulty of the jump against the capability of your character, as well as against
the likely rewards to come from safely making that jump. On the other hand, if
you take the safe option you risk not finding another path to where you want to
go. The outcome of your decision is not known at the time of making your choice.
It may be obvious in hindsight, but it wasn't obvious at the time.
An element of this uncertainty comes from the black box of the outcome – in
traditional board games this is often incorporated as the roll of the dice. Even in
entirely deterministic games (such as chess) the same basic feature is present. In
chess, there is no randomness, but you still don't know until your opponent has
made their move (and usually, a good few moves after that) as to whether you
made the right decision when playing your pieces.
162
Epitaph Online
http://drakkos.co.uk
However, randomness alone does not make a decision interesting, it just makes
the outcome uncertain. To progress to an interesting decision it has to be
possible for people to reduce the uncertainty implicit in their options by logical
deduction. You should never be able to entirely remove uncertainty (which is why
randomness helps), but you should be able to reduce it to a level that you can
make an informed, albeit uncertain, choice between competing alternatives. This
can be quite subtle – for example, imagine a game of Russian Roulette coded with
two schemes:


When you pull the trigger, a dice is rolled to determine whether you died. If
you roll a 1 in 6, then you die.
When you pull the trigger, the chance of your death is dependant on how
many times the trigger has previously been pulled. You chance of killing
yourself thus increases every time the gun doesn't fire a shot.
In the first case, it's the equivalent of the barrel being spun after every pull of the
trigger – in this scheme you are just trusting to the odds. Randomness by itself is
neither fun nor interesting, and it's certainly not challenging. The only decision
here is whether the 1 in 6 chance of death is worth whatever reward is presented.
In the second case, it's the equivalent of the barrel being spun once at the start
and then successive pulls of the trigger advancing the chamber onwards. In the
second scheme, by paying close attention to how other players interact with the
game, you can decrease the size of the window of uncertainty and thus make
strategic decisions about when to play and when to walk away from the table.
If randomness reduces how interesting a decision is, one thing that removes it
entirely is arbitrariness. Instant death as an unexpected and unknowable
consequence of making a decision is an easy way to completely undermine the
process of informed decision making. For example, standing before a door:


Go through
Stay where you are
If the door leads to certain death, without any hint, suggestion, or possible way of
knowing in advance, then it's a bad game decision. We are going to return to the
idea of insta-death later – it's a sufficiently common transgression that we should
spend some time talking about why it's bad from a game development
perspective.
No matter how different the games are, eventually it resolves down to the choices
you make and the space of time you have to decide upon those choices. In a first
person shooter, your choice may be to fire your gun, switch your weapon, leap out
of range, or run behind a wall – the fact that there is only a few instants for you to
make those decisions doesn't mean that there are no decisions being made. On
the other hand, decisions in a game of chess can take minutes to evaluate, but it's
still the same core process.
What we are trying to do then as game developers is to provide opportunities for
people to make interesting decisions. We will have cause to return to this topic
163
Epitaph Online
http://drakkos.co.uk
throughout this material as it is the basis of good game design.
Design Philosophy of Areas
There are several ‘types' of area that can be developed in an online environment
such as ours. Almost every ‘real' area is actually a blend of these, but will
emphasise two or three types over the rest. The following is not an exhaustive list,
but covers the common foci that are present:






Killing zone
Wealth zone
Questing zone
Immersion zone
Infrastructure zone
Exploration zone
A killing zone is self-explanatory – it consists of, ideally, interesting NPCs who
exist only to be slaughtered for their high experience yield. Killing zones need not
cater purely to higher level players - there is scope for killing zones to support
almost any level range. However, there is more ‘mileage' to be gained out of
medium and higher level killing zones – progress through the levels often slows
as an individual progresses, and as such a killing zone is viable for much longer
for higher level players.
Wealth zones are almost identical to killing zones, except that rather than being
killed for experience the NPCs are killed for the cash they provide. It's a good idea
to keep killing zones and wealth zones as separated as possible so that a choice
has to be made between pursuing wealth or experience. If one zone provides
both, then it obsoletes those zones that provide only one or the other – it's not an
interesting game decision to kill in an area that rewards wealth and experience as
opposed to an area that rewards just wealth or just XP.
Questing zones exist to provide opportunities for problem solving. Quests on
Epitaph are far more idiosyncratic than in most online games, and the type of
player who enjoys solving puzzles is often greatly attracted to our conception of
quest design. Players expect that a new area will introduce new quests into the
game, and meeting that expectation is beneficial to everyone. Obviously our
system is not perfect and badly designed and developed quests enhance the
experience for no-one. We will spend quite some time discussing quest design.
An immersion zone caters primarily to role-players and social gamers. It's an area
to interact with, but not to gain any tangible in-game benefit. It may be
characterised by a particular kind of ambiance, opportunities for social
interaction, the availability of a range of equipment suitable for RP purposes, or
sustainable opportunities for NPC interaction. They may also stitch into larger and
more encompassing systems, such as our player housing system or player-run
newspapers.
Infrastructure zones exist to provide the tools and equipment that people need to
164
Epitaph Online
http://drakkos.co.uk
function in the game. They consist of things like the guild areas, smithies, weapon
shops, armour shops, and places within which people can buy spell components
and other necessities.
Finally, an exploration zone is one that rewards players for paying attention.
Areas replete with hidden items, secret areas and rewards for observation fall into
this category.
Every good area will have elements of each of these – even designated ‘killing'
areas come with immersive room descriptions and occasionally even quests.
Larger areas, like cities, will be defined by the blend of their sub-areas – Dunglen
for example has its quest zones (such as the university), its killing zones (the
instances, mainly), its infrastructure zones (the Winchester and the various faction
facilities), it's immersion zones (the safe zone) and its exploration areas (the rest
of the city). Blended together, Dunglen has a particular feel based on the relative
emphasis of its various constituent parts.
The first step then in planning out your development is to decide on what kind of
features you want to emphasise. This will be decided usually on the basis of
what's needed in the larger context of your development – if it's a street or district
within a city, what features are currently not present that should be? If it's an area
within a larger geographic zone, what does it need to provide in contrast to what's
already available?
You only rarely have complete autonomy over this decision – there is always a
process of give and take between what you'd like to develop and what needs to be
developed. Discussions with your domain administration are the first step to
gaining an appreciation for how the different developments within a domain are
linked together.
Zombieville
So, with that in mind, let's decide on a focus for Zombieville. Our focus here is
simplified by the fact that we don't need to fit our development into a larger
domain plan – we are free to choose which focus best fits our particular needs.
Specifically, our needs are that the village be sufficiently involved to serve as a
driver for the new syntax of LPC that we will encounter in Intermediate LPC. We
have no player-base to satisfy here, after all.
Killing zones do not give a lot of scope for this, although there is certainly much
to be said for designing interesting and challenging NPCs. We'll leave that off as a
focus though, and return to it in a later text since making interesting NPCs
involves not so much new syntax as an introduction to the basics of a new
concept – artificial intelligence (we will get to that, just not now). Those who are
interested in learning more of this topic should come speak to me for some
suggested reading material.
Likewise Zombieville is not going to be a wealth zone – we're not going to learn
much by filling it full of NPCs laden with expensive items, or by providing
165
Epitaph Online
http://drakkos.co.uk
scavenge zones full of valuable equipment. We also have no need to provide an
infrastructure for anything else, so we won't have that as a focus either. We'll still
talk about infrastructure in the course of this material, but it won't be something
we adopt for Zombieville.
Primarily, Zombieville is going to be a questing zone – we're going to have a
number of quests in this area so as to explore some deeper issues of game
development and NPC interaction. We also want to have the area be immersive
and responsive to player exploration so as to provide us an opportunity for
incorporating some deeper concepts of LPC such as handlers and inherits. Thus,
we pick three focii for our area: quests, immersion, and exploration.
Having decided upon a focus, we can start to consider what we're going to include
in the area to meet the needs of our development. That's where the real planning
begins. It's a lot of fun to let your imagination run wild before coming face to face
with the crushing reality of actually having to implement it all!
Conclusion
We're easing ourselves in gently here. Deciding on a focus for our area means that
we can ensure we include the right kind of things to meet our goal. What that
focus is going to be is usually a function of all the other areas in the domain (and
indeed, the MUD as a whole). All of these areas appeal to different people in
different ways, and a good game environment has examples of all of them in
proportions that fit the makeup of the playerbase.
Good games are engines for generating branching points between interesting
choices. Bad games are just engines for generating choices. In order for a choice
to be interesting, each of the outcomes must have an element of uncertainty, and
that uncertainty should be manageable (but not eliminated) by player strategising.
Bad games introduce randomness rather than challenge, and arbitrariness instead
of strategy. Over the course of this document, it's my hope that together we can
explore how to do Good rather than Bad in the code that we develop.
166
Epitaph Online
http://drakkos.co.uk
Player Demographics
Introduction
There are many, varied opportunities for gameplay to be found within Epitaph,
and as such our playerbase has a blend of individuals who seek enjoyment in
different spheres of the game. Before we can hope to provide opportunities for
interesting decisions, we have to first know what kind of decisions our players
find interesting.
While there currently exists no mud-wide survey of playing styles, the work of
Richard Bartle can help clarify the generic subgroups that are usually found within
the demographics of an online MUD or MMORPG community.
Bartle's Definitions
Bartle defined four key sub-groupings of activities - while everyone is a blend of
preferences for these, individuals tend to favour one over the others, and will seek
out those activities on a more sustained basis. These sub-groupings are:




Achievement
Exploration
Socialisation
Imposition
Achievers are interested in setting and reaching in-game goals. These goals may
be influenced by the game environment, or may be entirely personal. Although
not an inextricable connection, achievers are often highly motivated by
opportunities to accrue reputation within their gaming environment. Achievers
like to be known as people who have achieved things.
Explorers are interested in defining the boundaries of the game world, be these
topologically related (mapping, for example), or how the game world models its
own physics and reality. Gaining an understanding of the environment in which
they function is what most defines an explorer.
Socialisers play the game for its social context - the game itself is largely an
engine for generating interaction opportunities between themselves and other
players. Talker channels, internal bulletin boards and in game social environments
are the stamping ground of a socialiser.
Finally, there are the imposers, who play the game for the thrill of direct player to
player conflict. These are the player killers who get a thrill from beating another
human being in the way that they do not from beating a computer algorithm. Any
opportunities that increase the conflict between individuals is what works best for
167
Epitaph Online
http://drakkos.co.uk
an imposer. More colloquially, imposers are best described by the label 'killers' the game world in general is a vehicle for attaining the necessary skills needed to
be able to survive in the world of player killing.
I must stress again that very few players are the caricatures presented here, and
everyone combines aspects of these to a particular degree - but over the long
term, a particular player will tend to focus on one (or at most two) of these over
the others. Additionally, players who focus on a particular feature of the game
tend to view unrelated developments through a somewhat nuanced eye. See the
Richard Bartle paper for a full discussion of this topic.
Your Zone's Players
Understanding the focus of your area makes it easier to understand the kind of
players who are going to be most attracted to it:
Type of Zone
Killing zone
Wealth zone
Questing zone
Immersion Zone
Exploration zone
Player Type
Achiever, Imposer
Achiever, Imposer
Achiever, Explorer
Socialisers
Explorers
It may seem like socialisers are ill served by our zone categories, but that's
misleading - socialisers mostly gain their in-game fulfilment from other people
rather than from what we provide as creators. We provide the environment in
which their own interactions can set the parameters of the game.
Likewise with imposers - although they will often frequent killing and wealth
generation areas, it's usually as a means to an end - to gain the necessary power
and wealth to be able to pursue and prevail in conflict with other imposers.
Knowing what kinds of players are going to be drawn to our area gives us the
necessary information to decide what features our area needs in order to best
service their expectations. Every decision you make in putting together your
design can either enhance the experience for your player groupings, or detract
from it.
Catering For Achievers
Achievers like to have goals that they can set for themselves, as well as
opportunities for developing a reputation within the game. Your task then
becomes to provide mechanisms to support those goals. Achievers are looking to
progress through the levels to become a Person of Power, and also a person of
Repute. Systems that facilitate this goal are highly popular.
Providing a mechanism by which points can be accrued is a useful way of
168
Epitaph Online
http://drakkos.co.uk
facilitating achievers, especially if those points are available for inspection by
anyone else. Epitaph uses a range of 'Top Twenty Tables' which are available to
anyone who may be interested. On these tables, players can compare their
standing against the standing of others in a range of categories, such as skill
levels, rating, achievement points, quest points, and so on. Essentially anything
that adds a permanent, or semi-permanent mention of an individual is likely to
appeal to an achiever. The 'first achieved by' tag on an achievement for example is
a way of gaining a measure of MUD immortality.
Additionally, since achievers often look to excel within specific sphere of the
game, and since our game is, like most games of this type, based on invested
experience, achievers are drawn to areas which offer significant opportunity for
strengthening individual power. NPCs are an obvious example of this - both high
XP and high wealth yields will draw achievers to a zone.
More than this though, a degree of challenge is an important motivating factor achievers set goals for themselves, and often feel less satisfied when those goals
are easy to achieve partially as a result of the achievement being available to more
individuals. If an achiever is to stand out, their actions must separate them from
less capable players. There is a substantial difference to providing ten easy NPCs
that offer a small XP yield to providing one difficult NPC that has a large XP yield.
Killing a boss NPC for example is a mark of achievement that stands out.
As a corollary to this though, achievers are often highly focused on the
cost/benefit ratio of their actions. While more powerful NPCs may give a welcome
degree of challenge, they will often be ignored in favour of the more lucrative
opportunities that come from killing many weaker NPCs in the same time scale. In
the end, it's all about what gives the most benefit to the player.
Opportunities for advancing seldom-developed skills can draw in achievers
looking to maximise their potential, especially if those skills are operationally
useful. While individuals may not flock to an area that offers a chance to increase
their lute playing, having a TM chance for an important skill like perception can be
a draw.
Overall, achievers are largely about how things improve their characters or their
personal reputation, rather than the satisfaction of doing the thing itself. As such,
achievers tend to prefer high-yield, straight-forward quests to intricate, involved
quests. For an achiever, choosing to do a quest is often a decision reached after
weighing the potential reward against the possible XP/money per hour of another
activity.
Achievers essentially are pitting themselves against the game - their competition
against other players is indirect. It's based on accumulated wealth, or number of
quest points, or level in a skill, or teaching bonuses. Thus, in order to appease an
achiever, these are the things that your development should provide ways to
improve.
169
Epitaph Online
http://drakkos.co.uk
Catering for Explorers
Explorers are the cartographers and researchers. They are motivated by the
chance to learn obscure information about their game environment - you are
almost guaranteed to know of a few of these off of the top of your head. Explorers
may be methodical and highly analytical, or they may be taken to satisfying
spontaneous curiosity. Their basic features remain the same.
Explorers like to categorise and demystify the black box systems that work in the
background of Epitaph. They are responsible for teasing out the hidden
information that becomes apparent only through careful experimentation. They
are the ones who will be working out which weapons or armours are best in which
situations, and what relationship exists between complementary internal systems.
They are also responsible for the creation of the many superbly detailed maps
that the rest of us have available. In short, they are the scientists of a gaming
environment.
As can be imagined, explorers like to know things that are not easily known. They
like to be able to bridge the gap between action and outcome, and detail the
larger context of how things fit together. Unfortunately, such things - assuming
they are not changed often - are fleeting achievements. Once mapped, an area
remains mapped. Once known, a weapon's damage profile remains known.
Changing things around reintroduces mystery, but greatly reduces the satisfaction
explorers get from making the effort in the first place, and has a knock-on effect
with regards to creating opportunities for players to make informed decisions - if
the goal-posts keep changing, where do you kick the ball?
Although secrets do not remain secret for long, we can still accommodate
explorers in our developments. Large areas appeal to the compulsive mappers,
but mapping is a One Time Thing. Secret areas and hidden interaction options are
more effective - while mapping is a task of methodically exploring an area, finding
secrets is a more intellectual pursuit involving delving into the provided clues and
untangling them in such a way that a worthwhile result is obtained. Hidden
treasures like rumours, items that are scavenged only be careful observation of
the environment and other such flourishes tend to appeal to those of the explorer
persuasion.
Alternatively, a development that includes a mysterious but ultimately knowable
system can provide a great deal of lasting interest. An interesting crime system or
gossip system can provide an incentive for explorers to extract as much
information as they can from experimentation. However, such systems must come
with appropriate rewards - it's not simply knowing something that inspires an
explorer, it's knowing something worth knowing.
Defining something as being worth knowing is a tricky task. Ideally it will have
some tangible benefit in the game - knowing the information makes your own
gaming experience more pleasant. However, there's only so often you can provide
such a thing, and certainly within a local development the cost of learning a
system is usually far greater than the reward that comes from obtaining a little bit
170
Epitaph Online
http://drakkos.co.uk
of situational knowledge.
One way around this is to provide information that helps explorers to find other
pieces of information. Hinting at quests as part of the information teased out of a
system can be a reward in itself, especially if knowledge of the quest is otherwise
unavailable. Of course, the existence of quest sites makes quest information very
difficult to obscure for long, but the design of the quest itself can help there. We'll
talk more on that later.
One mechanism for drawing an explorer to your area is in the provision of tools to
help measure other things, which is always surprisingly popular. I wrote a
thaumometer on Discworld for a player once, purely because he asked for one and
I didn't see any reason why not. That turned out to be such a useful tool for
mapping the impact of background magic on spell-casting that the explorers
spent many happy weeks documenting and experimenting. An hour or so of idle
development turned into a huge amount of enjoyment for the game's explorers,
purely because it hit the sweet spot of what they wanted.
Catering for Socialisers
Socialisers are a difficult group to explicitly cater for, because so much of their
enjoyment derives from interaction with other players. While you can't provide all
that much, you can incorporate some elements to enrich interaction
opportunities.
One method for this is to introduce multi-player activities such as games or
quests that require cooperation. Porting text versions of popular board or card
games can be an excellent way to get people to socialise – board games, poker,
and the various multiplayer games available to players are all perfect examples of
catering for socialisers.
Roleplaying is one particular expression of socialiser tendencies, and providing
tools to support that is an important part of keeping your socialisers happy.
Providing options for people to tailor their gaming experience in such a way that
the changes are cosmetic rather than functional, such as with custom clothing, is
a draw for many players. Failing that, creating a shop with new and unique items
or roleplaying aids can be a boon to those seeking to increase their sense of
personal immersion. For socialisers, it's often more a case of 'being' than 'doing'.
More than this though, what you are looking to provide is things for people to
actually interact about. Examples of this are the clan system, the
interdependencies of the crafting system, and even the simple fact that someone
can help you up when you're lying prone. While working within these systems
provide real game world benefits, they are primarily interaction aids. They don't
make it possible to do anything that couldn't already be done - instead, they
facilitate an interaction in such a way that the infrastructure deals with the
busywork leaving people free to deal with the people part of the system.
There is a danger in this though that by simplifying the busywork you will actually
171
Epitaph Online
http://drakkos.co.uk
remove a lot of the satisfactions socialisers get from actually interacting. Getting
people to work together towards a common goal is one of the motivating factors
socialisers have for socialising, whether that goal be an interesting conversation,
or a putting together a successful clan. When too much of that interaction is
automated, then the challenge is gone and so too is the incentive to socialise.
Catering for Imposers
Imposers, like socialisers, play the game to interact with other people. However,
that interaction is primarily conflict rather than cooperation. What many imposers
look for out of a development are the same things that achievers seek - more
personal power within the game. However, the motivation is different – imposers
seek power to remain competitive against other imposers.
High XP yield is a significant draw for imposers - whether this be in the form of
NPCs or low complexity quests. Skill opportunities, especially for Killer Viable
skills, are also a draw.
However, one thing that can appeal specifically to imposers is a framework for
interesting player versus player conflict, or for ways to widen the pool of
individuals who can be part of the fun. Instances where the environment is
designed to be a challenge for player-killers offers one coded mechanism for this,
as does our opt-in system of PK – anyone can PK for a while if it seems like fun.
Within a lone development, it is harder to specifically cater to killers. As with
socialisers, the primary lever by which we can manipulate their game experience
is the set of Other People. Sadly, no matter how much we wish otherwise, Other
People are out-with our control.
Conclusion
This has been a fairly brief discussion of the kind of game elements that can
support drawing the right kind of people to the right kinds of developments.
Bartle's work is far more detailed than I have outlined here, but it is more difficult
to relate to our specific gaming environment since certain assumptions are made
throughout (for example, that imposers can prey on non-imposers, which isn't the
case for us).
Knowing our audience is the first step in knowing of what our development
should consist, and we at least now know what direction our planning should be
heading. With that knowledge, we can move on to the next step of deciding what
specific elements our development is going to have!
172
Epitaph Online
http://drakkos.co.uk
Urban Planning
Introduction
Our last two chapters have discussed the motivation for an area, and the kind of
people who are likely to be interested in visiting it. In this chapter we are going to
layout out the specifics of what Zombieville is going to be in a way that lets us
know, long before we lay down a single line of code, how everything relates to
everything else.
It's important to note at this point that having a plan is not the same thing as
having no choice when it comes time to develop – your plan is contingent on real
world constraints such as the time you have available, your coding confidence,
and the overall requirements of your domain. Plans must be flexible, but you can
get an awful lot of benefit out of thinking through exactly what it is you are
hoping to achieve.
An Interesting Area
What makes an interesting area? There are many answers to this, but that one that
has always resonated most with me is a definition given by an old playtester I
knew over at Discworld MUD, a fellow by the name of Griffin@Discworld. It was a
very useful benchmark and one that I used throughout the planning of the city of
Genua on that MUD. His full posting is available in the appendix of this, and
although hugely out of date (the Discworld cities of AM and KLK have been
remodeled since he first posted it) in essence it breaks down into three measures:



Count the number of rooms in a street or town, discarding inside rooms.
Count the number of rooms in that street or town that have a special
feature.
Divide the number of features by the total number of rooms to get your
feature density.
This is an inherently ambiguous metric, because some of these things are difficult
to count properly (to which street does a crossroads belong, for example), and
features are hard to define and difficult to quantify – is a room with a hugely
complex gaming system as interesting as a room with commonly available
scavengeables, for example? It also ignores things like the quality of the writing,
the richness of the immersion, and the overall thematic correctness of the
development.
At a basic level, features are the things that you go to an area for – they could be
quests, shops, interesting NPCs, or anything else that would serve as a draw for
players. While people may come for interesting writing or mapping, they'll only do
that once.
173
Epitaph Online
http://drakkos.co.uk
When putting together your area, it's an excellent idea to keep the feature density
in mind, and aim for something reasonably high. If your development has
anything lower than a feature density of 0.5, then you should consider either
reducing its size or increasing the number of features. Large, empty areas are of
dubious benefit to the MUD in general, except when the sheer size of an area is
part of its theme – mazes, for example.
The measure is imperfect, but it has a fairly close match with how ‘interesting' an
area feels – the higher the density, the more interesting an area it is likely to be.
The more sustainable the feature density, the longer that area is going to hold its
interest factor.
The Refined Feature Density Model
For the purposes of this document, we are going to look at a refined model for
calculating feature density. The refined model is identical in structure to that of
Griffin's, but has some modifications to make it a better guide for creator
development, although this comes at a cost of increased subjectivity and
increased time to calculate. Such are the trade-offs we make in life.
We are acknowledging a difference of feature values to different groups of
players, so it is illogical for us to use a single measure as a mark of interest to all
players. Instead, we will use weighted measurements in a table to work out
roughly how appealing our area will be to particular demographics.
In the Refined Model, we do not mark a feature as a single point – a hugely
entertaining quest isn't the same thing as a shop selling a random collection of
useless items, although in the Traditional Model this would be the case. This is
where our increased subjectivity comes into play – we assign each feature a value
from one to ten indicating how entertaining we believe the feature to be. We're
not always going to be right, but as budding game developers we should be able
to make that kind of rough calculation of interest – it's part of how we decide
what to code. An average feature, such as a shop with unique stock, should be a
five on the scale. A five is a ‘take it or leave it' feature. Anything less than a five is
something that will fail to draw people to an area. Less than a three is likely to
actively put people off. Five is ‘meh, I'll take a look', whereas less is moving
towards ‘Oh, that's really of no interest to me'.
Additionally, we rate a feature as to how interesting it is likely to be for our
particular player groups – killers will not find quests as appealing as achievers,
who are likewise going to find shops less interesting than socialisers. A true
metric is going to reflect the difference of taste. As a further refinement of the
Traditional Model, I would also recommend separating out sustainable features
from those that are more temporary – a quest for example is a draw for a player
until it is completed, and then irrelevant afterwards, whereas a shop holds its
feature value over the long term.
Features in the Refined System are broader too – they include not only things to
do, but things that happen. They include crime handlers and other immersion
174
Epitaph Online
http://drakkos.co.uk
possibilities. Essentially you list everything about your development that you think
is likely to be noticed by players, and assign that a value between one and ten for
each category of player.
As a worked example of this, I am going to use our safe zone – the area between
the chain fences that defines the first area of Epitaph. It's quite a hefty area, so
rather than list every single thing individually, we'll focus on groupings. We have
various features – the Winchester itself, of course, which comes complete with a
faction. We have a church which, while not especially interesting in itself does
have a reasonable number of things to kill for newbies. We've got a playpark that
contains some practice objects, and many flats lining the road each with plenty to
scavenge and find. Within the Winchester, we have two quests (simple as they
may be), a faction shop, and various other things. We don't need to be exhaustive
here, we just need to be prepared to make guesstimates as to how interesting
different groups will find the area.
To start with, let's just rate everything as a five on a chart:
Feature
The Winchester
The Church
The Gym
The Playpark
The Houses
Explorers
5
5
5
5
5
Achievers
5
5
5
5
5
Socialisers
5
5
5
5
5
Imposers
5
5
5
5
5
This in itself is a simplified worked example, because we're ignoring core code
shared between all rooms in the game, such as the scavenging system and the
rumour mill, both of which are germane to this area – it's difficult though to
separate them out from the larger context of the city itself. Additionally, we're not
paying attention to the sustainability of these features – a more complex analysis
including sustainability is left as an exercise for the reader.
The Winchester is pretty heavily packed – it has a faction, which offers missions
(XP and reputation, of worth to achievers and imposers), a faction trainer (for
advancing skills – achievers and imposers again). It has two quests (Explorers and
Achievers, and since they are simple quests they are of value to imposers also). It
contains a faction shop (socialisers), and a couple of secrets that aren't
immediately apparent (explorers). It's also a major point of social interaction,
since it represents the 'default' area of the MUD – as such, it is high traffic and
thus of value to socialisers. In short, it appeals across the board. So let's bump it
up in all categories:
Feature
The Winchester
Explorers
8
Achievers Socialisers
8
8
Imposers
8
We won't go any higher than eight here, because that's already pretty high and it's
not like it is perfect or couldn't be improved with reasonable effort.
The church contains some simple NPCs on which newbies can cut their teeth, as
well as being the location of a couple of the missions handed out by the faction.
175
Epitaph Online
http://drakkos.co.uk
Let's give it a little bump for achievers and explorers:
Feature
The Church
Explorers
6
Achievers Socialisers
6
5
Imposers
5
The gym contains a few scavengeables, rumours that can be uncovered, and
practice objects – the latter are free skill increases, and thus of value especially to
achievers and imposers.
Feature
The Gym
Explorers
6
Achievers Socialisers
7
5
Imposers
7
The playpark also offers free skill increases, but with little else other than the
atmosphere it tries to create. Mark it up for achievers and imposers:
Feature
The Playpark
Explorers
5
Achievers Socialisers
7
5
Imposers
7
The houses that line the street are chock full of valuables, raw resources and such
– but beyond the actual value of these resources in terms of how they advance
and sustain individuals, they have limited interest to anyone but explorers and
socialisers. Socialisers will appreciate the fact that there are many options for
character customization available by scavenging clothes, and explorers will
appreciate the fact that the scavenge profiles represent a mechanism by which
information about the searching system can be elicited. Achievers and imposers
may like the relatively easy access to food and water, which is a staple for their
other pursuits.
Feature
The Houses
Explorers
7
Achievers Socialisers
6
7
Imposers
7
Our next step then is to collate all of this and calculate the feature density of the
safe zone for each of these groups. We won't calculate this against street rooms,
because we have the situation complicated by, for example, the sheer number of
houses:
Feature
The Winchester
The Church
The Gym
The Playpark
The Houses
Average
Explorers
8
6
6
5
7
6.4
Achievers
8
6
7
7
6
6.8
Socialisers
8
5
5
5
7
6
Imposers
8
5
7
7
7
6.8
Our area then has acceptable feature densities for all four main categories of
players – that's important for the first area that players end up discovering. The
actual feature density of the region is much higher because here we are counting
'houses' as a single feature when they each count as a feature on their own (albeit
176
Epitaph Online
http://drakkos.co.uk
one that is not especially varied). It's not my intention here though to fully
calculate the feature density of the area, just to show you a worked example of
how you can use it to assess the profile of your work.
Reasonable people can disagree on the values I have assigned to things here –
they're all a subjective judgement call and I make no claims that my numbers are
necessarily more accurate than anyone else's. It's not the answer that matters, it's
the process that you go through because that illuminates a lot of your thinking.
Feature Planning
There are two ways to begin the planning – one is to concentrate on the features
first and then work out the area to fit around them, or to plan out the area and
work out the features to fill it. We're going to go with the ‘features first' plan,
because it helps us decide in advance how big our area can realistically be to keep
a reasonable density.
To recap on our design goals, we are catering to two sets of players – explorers
and socialisers. That means that we have several choices of the ‘flavour' of zone
we are going to develop:



Questing zone
Immersion Zone
Exploration Zone
Let's plan out a first rough feature-set that is likely to appeal to these particular
groups. No details, just a vague idea of what the scope of our development will
be.





One obvious quest
Two secret quests
Three hidden areas
An abandoned shop containing unique clothes
A library that with careful research will yield hints about the quests and
some info about other players.
Where do we get this ideas from? Just by thinking about what fits into the theme
of the area we're developing. We're not actually building Zombieville to an in-game
zone, which means that we don't actually have the benefit of thematic constraints
and inspirations. We'll talk about the importance of the theme in the next chapter,
because it will help us outline what form our village will actually take.
We don't need to know what we're actually going to do here, we're just speccing
out what we'll need to do in order to provide a draw to the right kind of people.
Even without knowing the specifics, we can start putting together our feature
density chart. A quest is more interesting, generally, than any average feature – so
it starts off a bit higher. It also appeals across the board:
177
Epitaph Online
http://drakkos.co.uk
Feature
Obvious quest
Explorers
7
Achievers
7
Socialisers
7
Killers
7
A secret quest appeals more to explorers and achievers than anyone else, but the
amount it appeals to an achiever will be based on how much of a yield the quest
will have. Killers are looking to maximize their own skills, and anything that
requires intricacy is likely to be less profitable on that score than other available
opportunities:
Feature
Secret Quest 1
Secret Quest 2
Explorers
9
9
Achievers
8
8
Socialisers
7
7
Killers
5
5
Hidden areas appeal to explorers, and do not especially appeal to others.
Assuming they have no other draw than to be hidden, I'd rate them as follows:
Feature
Hidden Area 1
Hidden Area 2
Hidden Area 3
Explorers
7
7
7
Achievers
3
3
3
Socialisers
3
3
3
Killers
3
3
3
The shop will appeal to socialisers over any other group:
Feature
Clothes Shop
Explorers
5
Achievers
3
Socialisers
7
Killers
3
And the library will appeal to explorers who want to know about secrets, and
socialisers who want to know about people:
Feature
Library
Explorers
8
Achievers
3
Socialisers
8
Killers
3
This is without any real planning – we can adjust the values up and down as we
get a firmer idea for what course the development is taking.
Now, the only two groups we're actually interested in are the explorers and the
socialisers. Summing up the feature densities gives us the following:
Feature
Obvious Quest
Secret Quest 1
Secret Quest 2
Hidden Area 1
Hidden Area 2
Hidden Area 3
Clothes Shop
Library
Total
Explorers
7
9
9
7
7
7
5
8
59
Socialisers
7
7
7
3
3
3
7
8
45
178
Epitaph Online
http://drakkos.co.uk
This gives us a measure for what size our area is going to have to be in order to
appeal to our chosen demographics – anything more than nine rooms is going to
drop the Feature Density below five for socialisers, and keeping the density above
five is a generally good rule to which we should adhere. Thus, we're going to give
ourselves a budget of nine corridor rooms to link all of these up.
Conclusion
The refined feature density model proposed in this chapter is experimental, and
at best only a rough measure for guiding your development. It is also not enough
to guarantee people will spend time in your area – especially if you are, like we are
here, catering to demographics that are not especially well represented amongst
the Epitaph playerbase. All the refined feature density model allows for is a
structured framework for thinking about what your area will need to provide. Use
it if you think it'll be helpful, ignore it if you don't.
A good game keeps your players happy, and spending this time at the start of the
development before you start writing any code helps ensure that your plans are
aligned with what is likely to appeal to the right kinds of players.
Of course, we are also restricted (and supported) by the thematic constraints of
the Epitaph generally, and in the next chapter we'll look at how thematic
awareness can inform our developments.
179
Epitaph Online
http://drakkos.co.uk
Thematic Considerations
Introduction
Epitaph is not based on any existing intellectual property, although it is of course
hugely influenced by many of the significant works in the post-apocalyptic genre.
The lack of existing canon that we need to service gives us tremendous freedom
when deciding how our game is to look and feel, but it does complicate things
when our own conception of the game world is evolving and incomplete. When
there is a blank canvas for a game, then who is to say what is appropriate and
what is not?
Luckily, we are not entirely in that situation because the apocalypse is well
explored literary territory, and we have a good conception of our own real world
and can extrapolate a potential game world based on simple starting principles.
We don't need to have the entire story when we sit down to develop – in fact, it's
good that we don't because we can let our game canon be driven by our needs,
rather than our game being developed around some inflexible plot.
Starting Principles
So, what are these starting principles upon which our game is based? Well, how
about these to start with:






The world is set in the near future, although it is not specified when.
The game is set an indeterminate period after the zombie infection first hit
– the timeframe is never provided, but it was long enough for those
remaining survivors to have looted most of the city clean.
The game is set in an approximation of our own world
We are seeking to build a game of survival horror in which zombies are a
major feature.
We are looking to explore dark territory, driven by the social context in
which survivors survive.
Our world is set in a non-mystical universe, and the zombie infection is not
magical in origin.
These starting principles have a huge impact on the world that we develop – it
opens us up to explore the darker recesses of human civilization, and use the
apocalypse as a mechanism to discuss larger issues of the decay of modern preinfection society. Because it's a close relative of our world, albeit a world in which
zombies are possible, we understand the nature of the world physics, the
relationship of people to spaces, and the 'feel' of the cities in which people will be
adventuring. People will be adventuring in cities that look much like real world
180
Epitaph Online
http://drakkos.co.uk
cities, although the high-street brands that people encounter will be satirized
rather than used directly by name.
The theme of survival horror then drives a lot of the rest of our design – there is a
difference between a survival horror game, and an action game that happens to
involve zombies. Survival horror implies that survival is an end in itself –
ammunition will be scarce, food will be scarce, and it should be actually
dangerous for people to project their presence outside of the safe zone.
The fact that we have defined the nature of the infection to be non-mystical also
influences the theme greatly – there's no magic in our world (as there might be in
a world that was more Cthulhu themed). There's no religious intervention (as
might be the case if it was an 'end-times' theme). There's no psychic power (as
there might be if we were based on Stephen King's 'Cell'). There is nothing to say
that we couldn't have those things in the future if it seemed appropriate – it's our
theme, after all. But failing changes to the basic principles upon which we all
operate, those are restrictions under which we labour.
The theme constrains us, but it also channels us – there is a reason we don't have
dinosaurs wandering around, or Jedi coming by to give the zombies a good
beating – Epitaph's world is a near neighbor of our world, and in our world
dinosaurs are long extinct and Jedi do not exist. To be a coherent game world,
everyone needs to understand the importance of extrapolating from these fixed
starting points.
The Opportunities
The fact that we have the entire real world to draw on means that we are never at
a shortage for things we can include – the real world is full of great things we can
use as inspiration. Fantasy MUDs don't get to have chainsaws, or pneumatic
hammers that can be used as weapons. We can have stuff like remote control
cars, flashlights, video cameras, all of the rich taphestry of modern life. And
modern life being full of dark, depressing things – we also have a huge amount to
draw on for when we want to discuss the duality of human nature – nobility and
savagery in equal measures.
Many of the areas I have written for Dunglen have been 'influenced' by locations in
my own life – that's easy for me, because Dunglen is set in Scotland and that is
where I have lived my whole life. There are bits of many locations I know well
represented in the city, as well as other locations drawn from across the country.
None of them are direct copies, but they served as a useful starting point for
visualization.
Fantasy worlds, while they offer the opportunity to be truly fantastic, are often
difficult to write because they are so unfamiliar. None of us has ever seen a
floating city while we view it from the back of a dragon. All of us have seen a
Starbucks. While it's not easy to be poetic about your average coffee chain outlet,
it is possible to describe it while also providing social commentary on the nature
of such outlets – their self-concious imagery and colour schemes and the social
message they attempt to project while selling hot drinks at giant, unjustifiable
181
Epitaph Online
http://drakkos.co.uk
markups. We have a templating system on Epitaph whereby we can place down
copies of certain buildings – the fact that we do that is in itself a commentary on
the increasing homogenization of the high-street and the power of branding.
When deciding on a location or a feature to be included in the game, think about
what your real life city provides, and how it could become interesting in the
context of post-apocalyptic city. Perhaps it is interesting because of the kind of
things that may be found there. Perhaps it's interesting because you have some
neat quests in mind. Perhaps it's the obvious location for placing a faction. All of
these are excellent reasons to consider a location for inclusion. If you need a
system that we don't have in order to justify it, don't let that stop you – we have a
pretty rapid pace of development that lets us rapidly introduce new features.
Once you've got an idea for what your location is going to be, consider how you
make it your own. Copying an existing location isn't going to be especially
satisfying, but it can be a useful starting point. Move things around, merge the
interior with the interior of a similar building you know – make it something
specifically Epitaph. You'll find that the real world has a lot to offer for inspiration
when building a modern, post-apocalyptic world like ours.
Profanity and Racism
Whilst being tremendously foul-mouthed myself (I'm Scottish, we use swearing as
a kind of punctuation), I am not a fan of unrestrained profanity in the game. I
think it looks cheap on the whole, and worse than that it almost always looks
artificial. There's a fine art to good swearing, and the requirements of a game
mean that it's difficult to get that kind of natural poetry worked into game
dialogue.
However, I also appreciate that given our setting, swearing is going to
occasionally be a neccessary part of game dialogue. When the word 'fuck' is
genuinely called for, it's usually the only word that will do. I appreciate there are
a lot of people who believe profanity is symptomatic of a limited vocabulary, but I
am not one of them - I think the sheer emotional and cathartic qualities of a
curseword is often difficult, if not impossible, to express just by being more
eloquent.
Within the world of Epitaph, swearing is permitted - but only when you are putting
words into the mouths of NPCs. Thus, it is okay in dialogue, and okay in graffiti.
It's not okay in long descriptions or add_items. You can have:
The rough sailor says: Fuck me, zombies!
But you can't have in a long description:
This is a pretty big knife.
no mistake.
You could cut up some fucking bitches with this and
This might seem like a strange distinction to make, but again it is about theme.
182
Epitaph Online
http://drakkos.co.uk
We as the builders of a world are the dispassionate creators of a reality - we're not
affected by it, and thus we can afford to be more articulate in how we attempt to
present that reality. Our NPCs, and by extension their interactions with the world,
are being affected by our reality on a day to day basis. When attacked by a
zombie, no-one is going to say 'Oh my, this is a sad state of affairs and no
mistake!'. They are going to say 'Argh, I am being eaten by a fucking zombie!' or
something similar.
Similarly with racism - we live in a world where racism is rampant, and while we
can gloss over that by simply never mentioning it in the game context, that
doesn't ring true. Violent racist imagery, graphic racist epithets and
characterisations of racial minorities are omnipresent in day to day life. I'd hope
it goes without saying, but genuine racism has no place on Epitaph. On the other
hand, if an NPC should be racist, then there is nothing in place to stop you.
Remember though - you are putting words in the mouths of NPCs. The text that
makes up the dispassionate lens through which our players view the world must
never be either profane or racist.
I appreciate that it is much easier in both cases to simply say 'Well, in this game
world we don't have either of those', but it always strikes me as ringing hollow.
Those who have such delicate sensibilities that they cannot bear to see real world
ugliness reflected in a game with a theme such as ours should perhaps look
elsewhere for their entertainment. That doesn't mean that you should overuse the
devices - just be aware that they are available when it's neccessary to enhance
characterisation or thematic consistency.
The trucker who says 'Dang it all to heck' forcefully breaks my immersion - people
just don't talk like that, on the whole. The television show that promotes a
diverse panel of multiracial integration always strikes me as being horribly,
cynically manipulative. We all know that's not the world we live in - the world of
Epitaph should be a reflection of the real world as far as it can.
Humour
We're obviously not a comedy game, but that doesn't mean we need to be
dreadfully po-faced all of the time. One of the great things about the human
species is that we can laugh even in the face of the most horrendous acts.
Humour then is not something you should shy away from, but it should be
thematic humour.
Black humour is a suitable humour category for us, and it covers those subjects
that are considered 'taboo' or off-limits for regular, every day comedy. Death,
sexual violence, racism, and war are all common subjects for black humour. The
idea is not to diminish the subject matter, but instead to create an atmosphere
whereby the audience (our players) are simultaneously made to feel amused and
also a little discomfited. In a world of unremitting darkness, people will find the
humour in practically anything, even if only as a mechanism for releasing some of
the internal pressure on their minds.
Related to this, but subtly different, is gallows humour - it differs primarily in the
183
Epitaph Online
http://drakkos.co.uk
fact that the source of the humour is the same as the target of the
unpleasantness. Quips by those about to be executed are a common example of
gallows humour - a man about to be shot by a firing squad, upon being offered a
cigarette, says 'No thanks, I'm trying to quit'. Benjamin Franklin, when saying 'We
must all hang together, or else we will be hanged separately', was employing
gallows humour in the face of an almost certainly devastating British counteroffensive upon the signing of the declaration of independence. In the Discworld
novels, Vimes muses 'We who think we are about to die, will laugh at anything'.
In House of Leaves, there is a beautiful expression of gallows humour:
Zampanò, I've come to recognize now, was a very funny man. But his humor was
that wry, desiccated kind soldiers whisper, all their jokes subsurface, their
laughter amounting to little more than a tic in the corner of the mouth, told as
they wait together in their outpost, slowly realizing that help's not going to reach
them in time and come nightfall, no matter what they've done or what they try to
say, slaughter will overrun them all.
As is often said - you have to laugh, or else you'll cry. Take advantage of that
basic trait of humanity if you feel that you need to lighten the atmosphere a little
bit.
Player Input
It's often difficult to think up good ideas for what you'd like to include. It's all very
well to say ‘I'm going to write a quest', but you're going to need a formal plan for
that quest before too long. Sometimes the books don't give inspiration and you
just can't think what to do. In such cases, the playerbase can be a useful source of
ideas.
It's sad but true – the vast majority of ideas submitted by the playerbase are
unusable. They are either so exhaustively detailed that no creator wants to code
them (half the fun is to plan the plan, as Mrs Lovett has said), so unbalanced that
they would break the game entirely, or so vague as to be completely worthless.
However, every now and again, there is an idea that is just perfect. Someone will
make an idea report such as 'Hey, we should be able to set fire to people with
these moltov cocktails', and a creator will see it and think 'Yes, that does sound
cool'. Sometimes that's all it takes – the idea gets to the right person who needs
it at the right time, and it flicks the right switches.
We have several locations where player ideas get recorded. Your first port of call
should be to our ideas interface, which is available both on the webpages and in
the game through the 'idea' command. This is a good starting place because it is
partially community managed – players vote for ideas they like, and vote down
ideas they don't. Ideas that are highly rated are not necessarily useable for
various reasons, but they at least have popular support and that is worth
something.
Sometimes, when we find an idea that we like and want to show our support for,
184
Epitaph Online
http://drakkos.co.uk
we put it on our 'rainy day file'. You'll find that on the wiki – it's essentially a list
of ideas people have had, and that a fellow creator has said 'Yes please', but didn't
have the time (or perhaps the expertise) to code it. Usually these will be drawn
from the ideas system, but they are especially worthwhile because at least one
creator has seen them and decided that it was worth the time to record.
Playtesters are usually a source of ideas that are more considered than that of
normal players, and reading the Playtesters wiki and the ptforum board can be a
source of inspiration. The player boards too, although a more mixed bag, are
worth keeping an eye on to see what people are enjoying or complaining about.
We encourage player feedback for the occasional diamond in the rough – but you
have to work to find those ideas. However, even reading bad reports can trigger
off that little connection in your mind that leads you down a path of inspiration.
Whenever you are lost for ideas, just spend time reading the ideas of other
people. Make a note of ideas that appeal to you for your own rainy day file. Not
every day is going to be a day in which you come up with great ideas, and other
days will be full of more ideas than you can hope to code. Keep track of them as
you go along so that you have a ‘fallback' list.
Back to Zombieville
So, now that we've spoken a little about thematic awareness, let's return to our
plan for Zombieville. We have the luxury here of deciding on a theme of our own
because we are not limited by a domain plan. We could set it in England, or
America, or any number of other places. Let's not be adventurous though, let's set
it in an area in which a lot of the theme is already defined. Certainly for our first
development, having constraints will aid us produce an area that fits into the rest
of the MUD.
We're going to set this village in Scotland – like most of the United Kingdom,
Scotland has been ravaged by the infection and is full of the undead. The
remoteness of the country and the relatively low population density have had an
impact, but they have not protected its major population centres from the ravages
of the plague. Scotland is dotted with little towns and villages, and as such we
can have a development of practically any size – from a few scattered farmhouses
up to relatively large rural locations consisting of a dozen or so streets and
hundreds of houses.
We're not going to be as adventurous as all of that – our village is going to be
quite small (nine rooms, as we decided in the last chapter), but knowing where
the village is set gives us some clues as to where our thoughts should coalesce.
The Scottish landscape is hilly, and the transport infrastructure is very shaky in
places – while some villages are well served by motorways or trains, there are
plenty for whom car travel is the only reliable way to get to 'civilization'. That has
an impact on the kind of village we are likely to develop – is it remote? Is it quite
central? Is it in the highlands? Or the lowlands?
Reading up on Scotland and looking up webpages of some of Scotland's villages
185
Epitaph Online
http://drakkos.co.uk
can be a worthwhile exercise for those who have never been here, but it doesn't
really matter if it's realistic. It just has to feel realistic.
Because we have such a limited budget of rooms, it's probably a good idea to go
for something remote – we can't afford a train station in our budget, and nine
rooms can perhaps accommodate a couple of small streets, or perhaps one street
with a village square. That's not a lot to work with – we could increase our budget
by planning for more features, but that's not a great idea (we'll talk about feature
creep later in this section of our book). It's best if we work to the budget we have.
So, we have a small, remote village. Let's think about what such a village might
have. A small local shop is one possibility. It would almost certainly have a pub,
which in Scotland as in elsewhere is very much the heart of a small community.
Being in the countryside, it's also likely to have a couple of farmhouses dotted
around. It would have little cottages too, but that's going to be true of anywhere.
Let's relate this basic idea to our feature list, and see if we can get it to mesh up:
Feature
Obvious Quest
Secret Quest 1
Secret Quest 2
Hidden Area 1
Hidden Area 2
Hidden Area 3
Clothes Shop
Library
Total
Explorers
7
9
9
7
7
7
5
8
59
Socialisers
7
7
7
3
3
3
7
8
45
While a clothes shop is not the most immediately obvious option for a small
village, it's not impossible – after all crafts shops are a common feature in small
communities the world over. An abandoned crafts shop, full of tasteful tartan
knitwear, could be just the thing to fill that role in our feature plan.
A library doesn't seem like something that would be a likely feature in a small
Scottish village, unless we widened the scope a little – what if if it was a library in
a village school? A village school is certainly possible, and would be likely to have
a small library associated with it. Perhaps it has been barricaded up against the
undead, but eventually had to succumb as all fortifications must when the
besieged are not being resupplied.
So, that is our area – a small village, complete with village school and a tartan
knitwear shop, perhaps one that contains useful crafting materials for players. It's
by no means the only way we could go with this development. We could just as
easily have decided on a small castle, or a salmon fishery, or pretty much
anything. This is just one way we could choose to go, and since it supports my
goals of providing an effective teaching scenario for a later section of this
handbook, it's the one we're going for.
186
Epitaph Online
http://drakkos.co.uk
Conclusion
When working within a world with a well defined theme, you have to know what
that theme is. More importantly, you have to know what you can do and what you
cannot. However, when the theme is being defined by the people building the
game, it's more important that the development is consistent with basic first
principles because often the game canon simply won't exist until it has sufficient
need to be written.
Those constraints are not problems you need to work around, they are the very
meat and potatoes of making your area seem like something that fits into both
the books and the larger game world of the MUD.
The best investment you can make in yourself as a Epitaph creator is to read
books that are appropriate to the theme – zombie books, apocalypse books, even
books on simple human nature. It may be a bit much to ask you to love them...
there are actually some Epitaph creators who are not especially fans of zombies in
particular... but a love of the Epitaph setting will help you make the jump from
abstract plans to more concrete, thematically consistent locations – and that's
very exciting.
187
Epitaph Online
http://drakkos.co.uk
The Best Laid Schemes
Introduction
Each of our chapters so far has taken us one step closer to defining exactly what
we're going to have in our area. At this point, we have the demographics to which
we are going to appeal, the features that we are going to provide to ensure that
the right kind of players are supported, and a theme for our development that is
both original and consistent with the Epitaph setting.
In this chapter we're going to finalise these plans and decide on exactly what form
our development will take. We'll decide what each of the quests are going to be,
and what hidden areas will be present. We'll also sketch out the map of the region
according to the constraints we have set ourselves (nine corridor rooms).
Making the Map
A good first step to help bridge the gap between vague plans and specific plans is
to develop a map of how things will link to other things. In Introductory LPC, we
looked at putting a skeleton area together based on such a map, and that's always
a worthwhile thing to do.
Trying to put together an interesting layouts of areas is part and parcel of a
development - street rooms are not likely to be 'interesting' in the technical sense
we have previously defined, but they can very easily be boring.
We have nine rooms, which isn't a lot, but it's enough for a small village square
and five further connecting rooms. How we choose to link those up is entirely our
choice. We could be boring and make it all along a single line.
5 - 6
| X |
1 - 2 - 3 - 4 - 7 - 8 - 9
Yawn. Although having to change direction when travelling is not an interesting
decision, it's a decision nonetheless. All you're doing to get from the end of this
village to the other end is travelling in one direction. A drinking bird toy could
handle that for you.
We could make the layout a little more inspiring by mixing it up. Almost every
village square is a 'north, south, east, and west' affair. You could easily rotate that
by 45 degrees to make one that is all diagonals:
188
Epitaph Online
http://drakkos.co.uk
6
/ \
5 + 7
\ /
4
It's a very small change, but one that instantly changes the topology of the village
from the traditional 'Ho hum, another village square' to something a little more
interesting. Likewise with the path, there's no reason why it has to go in a straight
line. We can make it meander a bit. Also, we could have a branch off of the main
path leading elsewhere rather than having a second path leading off of the village
square. All of these are more interesting choices than having a simple, linear
progression.
6
/ \
5 + 7
\ /
4
/
3
|
2
/ \
1
8
\
9
We have exactly the same number of rooms - all that has changed is that we have
laid them out a bit more exotically. An interesting layout will lead to easier
descriptions - for the first map, it's all going to be variations on the theme of 'The
path leads east and west', whereas with our second map we have many more
interesting possibilities.
We'll be using Map Two as the basis for Zombieville when it comes time to code it.
Incorporating Features
The easiest thing to do to begin with is work out what our secret rooms are going
to be. We have a number of choices:




We
We
We
We
can
can
can
can
make a key thematic feature of our area hidden
make the library itself visible, but rooms within it secret
dot hidden areas around the village
have a blend of these.
We need to decide which of these is likely to be a more interesting scenario.
If we make the school hidden, we are instantly making it more difficult for people
to enjoy the area since a key feature is already missing. It'll be of appeal to
189
Epitaph Online
http://drakkos.co.uk
explorers, but everyone else is going to be disadvantaged. People won't
necessarily even know there is a school unless we hint at it elsewhere in the area perhaps in room chats or rumours placed throughout the development.
If we make the school visible, but rooms within it secret, then people will find our
key feature, and the attention of individuals is always higher when they have come
to a location that screams 'This is not mere scenery'. People are likely to
investigate an obvious library in a way that they won't investigate a street room.
Having the school visible hints that there may be further mysteries within.
We can dot hidden rooms around the village, and have the school open
throughout. This will have the effect of making the village itself more interesting
(but only to explorers), but the school less interesting. People in general are going
to be coming to our village to investigate the tempting confines of the school,
because its very presence is a distraction from the rest of the village.
Our final option is to have a combination - we don't need to put all of our features
in a single subarea of the development. We could have some secret rooms in the
school, and some elsewhere in the village.
Personally, I favour having the school being a trove of hidden secrets since that's
where people are going to expect them to be. Partially we want to provide a
challenge and a reward for people who pay close attention, but on the other hand
we are writing for an audience and our audience is reduced if people don't get to
explore our creations. As an extra bonus though, we'll make the way into the
school itself a secret, albeit one that is fairly well signposted.
The school itself can be more than one room - when we talk about 'corridor
rooms' we mean rooms that link features to other features. A feature can be a
small zone all of its own, but bear in mind that beyond a certain point you should
do a feature density analysis of the subzone itself to ensure you haven't made it
boring by virtue of sheer size.
Let's have three rooms for our school - an opening hallway, the classroom, and
the second floor. Careful exploration of the school will reveal that each of these
rooms has a secret to tell.
Where is the school going to be, in our development? Well, we've created a
branching path off of the main road, and we're going to make it a secret path.
There should be a reward for discovering a hidden path, so let's put the school at
the end of it.
The shop is a simple one to place - it'll go in the village, and it doesn't really
matter where.
We'll worry about where the quests will go once we have placed the physical
features.
190
Epitaph Online
http://drakkos.co.uk
The School
A rural school in a Scottish village doesn't require a lot of explanation, but we do
need to spend a bit of time considering how it is going to have been affected by
the world situation. I previously suggested that perhaps it had been barricaded,
and that seems like a sensible approach. We can construct something of a
narrative within the library. Perhaps the infection struck the village suddenly, and
the teacher (or teachers) thought it best to make use of the classroom supplies to
barricade the children until help arrived – except, help never did arrive and the
food contained within the school was not plentiful enough to support a long
siege.
How did the teachers respond then? Did they simply wait it out, rationing out the
food to the children and trying to turn a deaf ear to their entreaties and suffering?
Maybe when it became obvious no help was coming, they attempted to ease the
suffering of their charges before their starvation became too much to bear.
Perhaps there was a darker tale – upon seeing the situation getting worse rather
than better, perhaps the stronger adults turned upon the children. Canabalism is
an easy way to get fresh meat, after all – and how much of a fight could some kids
actually put up? Perhaps the inside of the school is a slaughterhouse of small,
butchered corpses.
You can go as dark you like, really – our theme is mature, and so too can can be
our stories. The story will unfold on the basis of the clues you leave in
descriptions – it's unlikely anyone living will remain, after all. On the other hand,
you might encounter an undead headmaster/mistress…
Instances
The possibility of a boss encounter within the school raises the possibility of the
school being an actual instance, which is something Epitaph supports. For those
who have never encountered an instance, it's basically a copy of a set of rooms
that is shared by a group of players. Every group gets their own copy of this
instance, so while there may be four groups in an instance, they never see each
other.
There are all sorts of reasons why this is useful – the main one is that it allows for
quite directed story-telling in a way that we can't do in the world. If we require an
NPC to be there to tell a story, we can't guarantee that in the real game world –
someone might come along and kill her. We can't guarantee that things that get
done in the game world get done in the right order by the right people. We can
ensure that with instances.
They're also good fun for groups, especially if they come with a significant
challenge such as an interesting boss NPC. Let's pencil that in as a what we want
to do – an instanced School complete with a headmistress boss NPC.
191
Epitaph Online
http://drakkos.co.uk
Instances can be as interesting locations as regular, common or garden areas – if
we want to have an instance with quests, that is entirely within our grasp. On the
other hand, we may prefer to have the instance separate and the quests actually
lead to the entrance to the instance. We have complete freedom here.
Because we need to come down on one side or the other for this (and no side is
overwhelmingly better), I am going to say that the school will be a normal area,
but solving the quests will give access to an instance containing the headmistress.
The Shop
We mustn't neglect the shop - we could just slap one down and fill it full of
clothes that are already available elsewhere, but that barely counts as a feature.
Instead, we want this to be a source of things people would actually want. As with
anything else we develop, we have to be sure to keep our thematic constraints in
mind.
What kind of clothes are likely to be sold in a small, rural village? Well, real life
experience shows that experts can be found in the most unexpected places, so in
terms of the realistic ability of the tailors in question, that's not a limiting factor.
What we do need to consider though is clientele. Who would come to this village
to buy things? Again, this doesn't have to be hugely limiting - people will travel a
long way for a genuine genius, but it's best if such geniuses are rare. Most
villages don't have a Leonardo, but they might well have someone who is a dab
hand at knitting.
We'll leverage the usual scavenging code to support this, but we'll make sure we
knock up some unique items to make it worth the while of someone searching.
Since it'll be a craft shop, it'll have a pile of tartan knitwear, along with some basic
craft components for those who are looking to expand their crafting skills.
Perhaps even a schematic or too – now that kind of thing is what makes a feature
actually interesting to people.
It All Adds Up
Every decision we make about the theme opens up a new opportunity for making
it all fit together. So far we have, for our village, the following defining
characteristics:




A rural school containing a small library
An instance made available by completing the quests
A tartan knitwear shop containing clothes, crafting supplies, and some
schematics
An undead headmistress with Awesome Boss Powers.
All of that has been spawned from coming up with a simple list of features and
considering how our theme influences those features. Considering what the
implications of our design decisions are likely to be also influences further
192
Epitaph Online
http://drakkos.co.uk
development.
One thing that you may have noticed from our plan is that our village is
tremendously sparse - one shop, and all the other features are concentrated in the
school. This isn't necessarily wrong, but it does suggest if we want to get the best
out of our development we should direct any other features that occur into the
village. As discussed earlier, a village pub, or a church, or a small groceries store
would go a long way towards making it seem less of an irrelevance. We can worry
about that later though, when the core of our design is in place. We add features
whe we need to and when we have a gap in our timetable, because otherwise we
never stop adding to our plan.
Conclusion
This chapter is a worked example of turning vague plans into solid plans, and it is
by no means the only direction this development could have taken. Interested
readers can consider working up their own plans based on the thematic
constraints we identified in the last chapter - you may be surprised how different
your results can be with only a little imagination.
193
Epitaph Online
http://drakkos.co.uk
Quest Design
Introduction
In this chapter we're going to look at some of the design decisions that go into
creating a quest for Epitaph. Unlike games such as World of Warcraft where quests
are mass produced and number in the thousands, Epitaph adopts a more handcrafted approach to the quests we put into the game.
Quests are usually the most challenging thing that a new creator will attempt to
write – they are non-trivial to plan, and non-trivial to execute. Nowhere is the need
for planning more apparent than in the design of even a fairly simple quest.
Before we get to that though, let's talk a little bit about different kinds of quest
design philosophies.
Why Quests?
Why do we actually have quests on Epitaph? Partially it's a legacy from the old text
adventures that inspired multiuser dungeons in the first place – such games were
all about progressing through a story, and in order to progress you had to solve
puzzles. There were rarely any ‘hack and slash' elements to these adventures, it
was all about figuring your way through the game quests. A text adventure with
no quests is merely an interactive story.
Within these adventure games, each quest that you solved took you a step closer
to the conclusion of the cstory, and if you didn't solve the quest your progress
through the game was halted. Quests thus had a narrative payoff associated with
them – they advanced the personal story of the player character.
Quests are also about providing alternate models of advancement through the
game. Quests give substantial experience rewards, and occasionally also reward
money or skill levels. Certainly quests that require only brainpower to solve
permit new players to gain skill levels in a way they would otherwise find
somewhat difficult. Quests therefore carry with them advancement possibilities.
Since our quests are puzzles, and since a player needs to be observant to find
them and to solve them (assuming they are not making use of a quest list site),
they also have an inherent value in terms of providing an additional challenge for
players. In the same way that a crossword or a sudoko puzzle is enjoyable in and
of itself, a good quest should give enjoyment by challenging in a new and
interesting way.
Finally, there is a sense of satisfaction that comes from having completed lots of
quests, especially when at least some of those quests can be construed as a mark
of honour. The social status of achievers in particular can be enhanced by having
194
Epitaph Online
http://drakkos.co.uk
a large number of quests to their name.
Hand-Crafted Craziness
Epitaph's approach is very much a case of ‘quality over quantity'. The quality of
quests will vary from place to place, coder to coder, and indeed in the perceptions
of player to player – but the fact is that each of our quests are more or less
unique. This puts a tremendous amount of pressure on a creator because the ‘off
the shelf' quest designs so prevalent in large MMORPGs are usually those that we
try to avoid.
If we think about most of the standard quests in such games, they tend to break
down into variations on a handful of themes:




Killing quests
Drop quests
Escort quests
Courier quests
A killing quest is one in which the objective is to kill a certain number of a certain
type of NPC – ‘kill ten green pigs', for example. A drop quest is one in which you
have to collect a certain number of items – ‘bring me four left-handed
screwdrivers', Sometimes these get combined into quest that involves you
collecting items that have a percentage chance of dropping from certain NPCs –
‘bring me six wolf teeth'. A feature of many of these hybrid quests is that they are
extremely jarring from an immersion perspective. If you are asked to collect ten
yeti spleens, and there's only a 30% chance of a spleen actually dropping, you
have to wonder how all these other yetis can walk around without one.
Escort quests involve you protecting an NPC as it moves from one place to
another, usually as it is attacked at several points by multiple attackers.
Courier quests involve you taking an item from one person to another. Potentially
this has a middle stage of having to first collect the item. ‘Go pick up the ruby
nipple from the temple of Zog, and then take it to my friend Stabby Joe over in
Deadville'.
All of these can be automated in the code-base, meaning that the task of adding a
new quest is as simple as deciding on some descriptive text to go with it, a
reward, and a target. Such quests can offer a narrative payoff through the
descriptive text, are often a source of wealth and experience, and often provide
useable items as a reward. What they do not offer though is a sense of inherent
enjoyment – they are means to an end. Killing ten boars because you have a quest
is exactly the same thing as killing ten boars when you don't have a quest – the
quests themselves just provide a context for rewarding such actions in a
consistent way.
We do have these quests on Epitaph, but not as quests – instead, they are handled
as randomly generated faction missions – while other games may pass them off as
substantial content, we acknowledge that it is possible to build these kind of
195
Epitaph Online
http://drakkos.co.uk
tasks from templates. Each faction has a shared bank of these, while some
factions may have entirely unique missions designed for their own particular
thematic considerations. Our quests are puzzles – our missions are what covers
everything else.
That's not to say that games employing the ‘quantity over quality' mechanism
have no interesting or satisfying quests – indeed, there are often several of these
in even the most ardent hack and slash MMORPG. They are very much the
exception though, and stand out precisely because they are so unusual.
By making our quests hand-crafted, we have a vast wealth of possibilities in terms
of what a quest actually looks like. We are limited only by our imaginations, and
that is tremendously exciting from the perspective of a developer. There's more
satisfaction in hand-coding an interesting quest than there is in writing up a
configuration file to be slotted into a handler along with a thousand virtually
identical files. And likewise, there's more satisfaction to be had from the players
that enjoy the quest you wrote.
People don't talk about identikit quests in the way they talk about hand-coded
quests. I have never had a conversation in which someone has said ‘Oh yeah,
remember that quest where you had to kill twenty plagued ocelots? Yeah, that was
awesome'. Quests that stick in the mind are the ones that go above and beyond
the call to provide something unique and interesting as to their structure. The
reward for such quests is partially simply completing it.
The Anatomy of a Epitaph Quest
So, we have identified that we are going to have three quests in our library – our
next step forward is to decide what those quests are actually going to be. We
won't do that in this chapter, but we will first talk about the features of quest
design that are particular to Epitaph. Knowing what the conventions are makes it
easier to guide your thinking. These aren't inviolable rules, but if you want to
bend (or break) them you need to get approval from your domain leader.
So, in no particular order, the ten commandments of Epitaph Quests! Except, as
previously mentioned, they're more guidelines than actual commandments:
Quests Do Not Reward ‘Must-Have' Items
The rewards that come from quests should be proportionate to the effort that was
invested. Very rarely does that effort reach the level that would warrant a musthave item. If an item would take ten hours of advancing faction rep to attain, it's
not appropriate that it be a reward for a quest that can be solved in five minutes.
In practice, it's impossible to judge that amount of invested effort into a quest,
especially since any quest you write will eventually end up recorded on a quest
site. Most quests can be resolved down into a set of linear steps (but again, more
on this later), and the quest that you thought would probably take three hours to
puzzle out may be possible to solve in five minutes with the help of a solution.
196
Epitaph Online
http://drakkos.co.uk
‘Must have' items throw the balance between risk and reward out of whack, and
tend to disproportionately reward those who invest the least amount of effort. As
such, as a rule we do not reward must have items. Instead, we reward either
flavour items, or items with only moderate usefulness.
Quests Do Not Reward Stat Increases
Most of the balance of our skill-stat model comes from the fact that we know how
many stat points individuals have to invest. If extra points are then made available
just as a consequence of completing a quest, then it becomes less important for
people to consider where their points will be invested, and also possible to
‘power-game' the system in ways we have not intended. Where we make stat
increases available (as we do with some knacks), careful adjustment of the context
is important.
A stat point is a tremendously valuable artifact, and no amount of invested effort
can really justify it as a reward for a quest.
Quests Do Not Reward Skill Increases
Skill increases should come as part of action – it's perfectly okay (and indeed,
encouraged) to have quests that involve skill checks. The TM opportunities of a
quest are a good way to make it possible to give a few bonus points of a skill to a
player. Our taskmaster is what regulates such rewards, and it makes sure that
levels awarded are a function of how experienced a player already is. The five
points of combat.melee.blunt that you decided to give out in a quest may be
worth only 1000 XP to complete newbie. It is worth much more to an older player,
especially one with skills higher than the game currently supports in terms of
available challenges.
Quests do not significantly restrict access to other parts of the game
Participation in the game shouldn't usually be based on whether or not you have
completed a particular quest. It's okay to make a few rooms accessible only as
part of a quest (as we are planning to do for our rural school) but it's less okay to
restrict larger areas – the larger the area, the less okay it is. When you restrict an
area based on a quest, it should be restricting access to 'extra content' rather than
core content.
The reason for this is pretty simple – we don't want quests to be something that
people have to do, but rather something people want to do. When it's mandatory,
it becomes a chore rather than anything else. It forces people to play the game in
the way we have chosen for them, rather than in the way they have chosen for
themselves.
Quests shouldn't require disproportionately large levels of useless skills
197
Epitaph Online
http://drakkos.co.uk
There's little more frustrating than a quest requiring a hundred levels of
other.stupid and that skill being of use nowhere except for that particular quest.
This is actually a two-pronged problem – one is that some skills are useless (and
we need to address that with game features that make use of the skills), the other
is that your quest is asking too much from a player.
Asking too much in the way of skill levels is a tremendously easy way to make
sure that the cost of doing the quest dramatically exceeds the reward. If it's a
‘flavour' skill, then set the absolute limit at something that can be achieved with
an investment of a few hundred thousand XP. You don't want to discourage
people from doing your quest purely because the net result will be to actually cost
them experience.
In both cases though, keep in mind the ceiling that factions can teach, and that
the cost (in terms of invested time) dramatically increases beyond the faction
maximum.
Quests do not have lethal consequences
Quests are about fun and experimentation, not lethal consequences. Players
shouldn't be killed as a result of trying to work out your puzzle unless it's
adequately signposted. Having a big button that says ‘If you press this you will
die' is an example of something being signposted – the fact is people won't
necessarily believe it, but you can't help some people. On the other hand, having
to choose between three identical-looking chemical beakers, two of which will kill
you instantly, is not a good design choice.
This is not to say that your quests cannot involve risk – that risk just has to be
proportional, and it has to be opt-in. Players have to know that what they're doing
has potentially deadly consequences. Challenging a high level NPC to a fight is
something that has an obviously potentially deadly consequence, and players can
opt-out of that fight when they feel it is going too badly. Dropping someone into a
pit of fire with no escape because they pressed the wrong unmarked button on a
dashboard is unfair and frustrating for the player.
Quests should be easy to find, for a given value of easy
By this I don't mean that a quest NPC should prod everyone who passes and say
‘Hey, I've got a quest for you'. Warcraft signposts quest NPCs so that you don't
look for quests, you get told exactly where they are. The opposite extreme is
quests that are obliquely hinted to in an add_item three or four references down.
It should be possible for an averagely observant player to get a hint that a quest is
somewhere nearby. We have a 'questsense' command that serves to bridge the
two extremes – those who want to discover quests organically are never prompted
of their existence. Those who want a bit of help, or want to see if there actually is
a quest associated with some in game object, can use the command for hints.
198
Epitaph Online
http://drakkos.co.uk
This is in your best interests too – quests that are too hard to find are solved honestly by
the smallest subset of the player-base. The rest of the people who solve it were directed
to it by a quest list of some form. It takes a lot of time and effort to code a quest, and
you want people to be able to gain enjoyment from that effort.
Quests should use obvious syntax, for a given value of obvious
It's really hard to predict what commands people are going to use to solve your
quest, and even harder for you to make sure that everyone is going to be happy
with the syntax. The most frustrating quests on Epitaph are games of ‘guess the
syntax' in which the only puzzle is what arcane, unintuitive command will unlock
the tasty candy of the reward. We've all done it, it's one of the consequences of
working within a code-base that lets you do pretty much whatever you would like.
It's difficult to know in advance what is going to be good syntax, but try to make
it something obvious. If you want someone to pull a lever, then make the syntax
‘pull a lever', don't make it ‘tug on lever'. Use simple, clear syntax whenever you
can. In the Discworld lib, from which we derive, that was complicated by the
awkward interaction between real commands and soul commands – you don't
have that problem here, because all soul commands are prefixed with a /. Use the
commands that make the most sense, and be open to criticism when people say
that they are oblique.
Quests should not involve hard-to-get or rare items
Once upon a time on Discworld MUD, one of the hardest items to get in the game
was a simple oil-can. It was available for a few pence from a shop on the Plaza of
Broken Moons, but only one was available per reboot. If you were the first person
to the shop after a reboot you could buy it and sell it for a hugely distorted profit,
or use it to complete the simple quest to which it was linked.
When the only thing stopping you from completing a quest is getting hold of
some hard-to-obtain item the quest becomes a waiting game rather than
something you are actively trying to solve. It eventually becomes something you'll
try to solve if and when the stars and planets align in such a way as to provide you
with the necessary luck to actually obtain the item upon which success is
contingent. That's presumably not what your intention was in writing the quest.
This is especially problematic if your item is available in another area of the MUD,
because it's often the case that areas get moved around, temporarily (or
permanently) shut down, or remodeled. On Discworld, people sometimes
reference a ‘green gem' – this was an item that was available at one point, but
stopped being available as the area in which it was found was closed down. A few
of them lingered around in private hands, usually in the vaults of players who kept
them ‘just in case'. This wouldn't be such a big deal if it wasn't for the fact the
gem was required for a quest with stupidly overpowered rewards.
Making quest items rare distorts their value, and creates a small economy around
their procurement. Obtaining the item becomes a matter for speculators rather
199
Epitaph Online
http://drakkos.co.uk
than questers, and that is hardly ever a good situation.
Quest should have logical behaviour and an obvious goal
If trying to guess the syntax of something you want to do is frustrating, imagine if
you were trying to guess the syntax when you weren't actually sure what it was
you were trying to do. All quests should have an obvious goal that leads the
player (with thought) to the solution. A quest in which you have to flick a piece of
cheese through an open window needs you to explain why this is something the
player should try to engineer. You can provide mention of the cheese in the room,
and the open window, but there is no logical reason why the presence of both
implies the cheese should be launched airborne.
On the other hand, a quest in which you attempt to ping a ball of cheese to a
terrified, starving mouse in a mouse-hole has an obvious goal – there's a starving
mouse, and you have some cheese. It won't come out of the mouse hole, so you
must provide the cheese to the mouse.
Additionally, the behaviour of your quest should make sense – not only should
you know what it is you are supposed to do, you should be able to rationally plan
out how to reach the goal.
The canonical example of a quest in which this was not the case is the Babel Fish
Puzzle from the Infocom text adventure of Hitchhiker's Guide to the Galaxy. The
Babel Fish is a fish that translates any language when it is placed in an individual's
ear, and the game has a puzzle whereby the player must engineer such a
desirable state of affairs. The solution breaks down as follows:
Pressing a button dispenses a Babel Fish, but it shoots out at such speed that it
flies across the room and into a hole. The player must put a dressing gown on a
hook above the hole. This causes the fish to drop down a drain which must be
blocked by a towel. When the fish drops onto the towel, it is cleaned away by a
small cleaning robot that instantly darts into the room, cleans, and disappears via
a small panel. This panel is thus blocked with a satchel so that when the robot
darts in, it cannot escape. Instead, it throws the fish into the air to the attention of
a second cleaning robot responsible for the upper half of the room. This second
robot is dealt with by placing some junk mail on the satchel so that when it is sent
flying into the air the robot is sufficiently busy so as to miss the fish.
There is no way a player can plan this out in advance, and to be fair this actually
breaks down into several sub-puzzles each with a fairly well defined goal:




Stop
Stop
Stop
Stop
the
the
the
the
fish flying into the hole.
fish falling down the drain.
robot escaping with the fish.
second robot grabbing the fish when it is thrown in the air.
Solving each of these leads to the next in the chain, but the problem is that the
last step of the puzzle violates the principle of having an obvious goal, and also
having logical behaviour. This is made especially unforgivable in that you can
continue with the game without solving this problem, but it makes the game
200
Epitaph Online
http://drakkos.co.uk
impossible to complete – and you don't find this out until you are almost at the
end of the game.
When planning out the stages that people must go through in your own quest,
make sure that they progress naturally from one to the other, and that the
behaviour to bring about a goal from a starting state is something that can be
discovered by more than trial and error.
Conclusion
Quests are going to be the most intricate things you code as a new creator, and
also one of the things that are easiest to do wrong. In our next chapter we will
continue to talk about the principles of good quest design, because we're not
even close to being done with this topic. All we have really done here is discuss
the general principles upon which we try to build Epitaph quests... we haven't
talked about actually designing them yet.
201
Epitaph Online
http://drakkos.co.uk
Let's talk about Quests, Baby
Introduction
Now that we've spent a little time discussing the anatomy of a Epitaph quest, let's
bring it back around to focus on our own development and the specifics of how
our own quests are going to work. As usual, there's thinking that needs to be
done before we get to that point – so far we've only really spoken about
constraints.
We're planning to have three quests – one secret quest to reward explorers, and
two more obvious quests that should be easier to locate for everyone. We've also
decided that our quests will take place in our rural school. All of that has emerged
naturally from our thematic considerations. Now we need to make actual quests
emerge!
The Intention of a Quest
First of all, let's consider why we're putting these quests in our development at all.
We've spoken about this in general terms in the last chapter, but let's look at in
terms of what's specific to our own area. In short, what is it that we want our
players to get out of the quest?
Do we want them to get:




A challenge?
A tangible reward?
Access to a feature?
Nothing beyond the fun of the quest itself?
The design of our quests will vary according to what they are supposed to be
generating for the player. Most quests are simple ‘for the fun of it' – you find
them, you do them, you get some XP and then move on. That's absolutely fine,
but in certain circumstances there is much to be said for quests that give
something beyond that. That's the first thing we need to consider, because the
quest we write will be different based on what's supposed to be the intention.
Additionally, when developing a suite of quests we need to consider the
connections between them. These connections can be as simple as quests being
part of a chain (quests can be set to have pre-requisites if you want to do this) or
a more complex set of dependencies (it's possible that doing a quest one way
makes another quest impossible to do). We can set up whatever connections we
like between the quests, or no connection at all if we see fit. Our next
202
Epitaph Online
http://drakkos.co.uk
consideration then is whether our quests are going to be linked to each other, or
linked to other quests elsewhere.
Pre-requisites for quests can be more than simply requiring that other quests have
been completed – items required, skills needed, necessary moral codes – these
are all things you should consider in advance. Ideally you'll have some formalized
way of representing this so that your domain administration and other creators
can see, at a glance, how everything fits together.
Linking up quests make a development seem more integrated, but it also
frustrates those players who hit a stumbling block – failing to solve one quest
locks them out of attempting the next. This is not necessarily a bad thing, but it's
something to bear in mind when planning things out.
Ideally quests that are linked will have a sensible progression. Quest A may
involve a door with an obvious lock, but no key, whereas Quest B rewards the
player with a key, but no door in which to use it. There is a natural link here that
leads from one quest to the other. If there is no obvious implied link, there should
be a direct hint provided when solving the pre-requisite quest.
Imagine as an example for this a pair of quests. Quest B involves finding a secret
passage, and Quest A involves fixing the mechanism that opens the secret
passage. If the secret passage only becomes available after Quest A is completed,
then Quest B should hint at that. If it does not, it is very unlikely the player will
thoroughly re-explore an already explored area. The mechanism in Quest A
should thus be an obvious triggering mechanism for a secret passage, or fixing it
should give a hint that something may have changed in the room. Something as
simple as echoing a message to the room would suffice for this.
The combination of pre-requisites and an understanding of why each quest is in
place allows you to decide on what the exact steps of each quest should be.
The Zombieville Quests
We already know the rough theme for our quests – each allows you access to a
different part of the rural school. Now we need to work out exactly how that's
going to work. We also want one of these quests to be harder to find than the
others so as to reward explorers. An easy way to do that is to have one quest as a
pre-requisite of the others.
Our task is to explore the school, which instantly suggests the kind of activities
that would be appropriate for the quest – repairing staircases, breaching
barricades, entering security codes, climbing wrecked furniture– essentially the
whole gamut of activities that involve people exploring an abandoned civic
building. We haven't written a single line of descriptions yet, so we are entirely
free to decide on whatever we need to be in place so we can write our rooms
around these requirements.
If we want one quest to be linked to another one, we also need to bear this in
203
Epitaph Online
http://drakkos.co.uk
mind – in what ways can a quest be revealed by the completion of another set of
tasks? Unfortunately there isn't a quick way to come up with ideas like this, you
just need to trust to your imagination. As such, it's necessary for me to simply
leap over the thinking part of that to a firm plan for what we'll do. This is the
equivalent of me saying ‘Here's one I made earlier' – the process you need to go
through to generate ideas yourself is to sit and think, talk over ideas with people,
and imagine what you'd find fun if you were presented with the tower as a player.
That takes time, and it's not something that can be sped up. Think back to what
you're actually hoping to accomplish with your quests, and consider what can be
put in place to support that.
Let's pretend you've already done that, and that we're ready to outline the result
of our thinking. Here's what we're going to do – the school is reached via a crack
in the wall which leads to the library. Upon entering this room for the first time,
the player is confronted with a horrible mess with books strewn everywhere. The
bookshelves will be in a fairly sad state of repairs, but marked with category
headings. The first quest is for the player to organize the library once more by
picking up books, reading the title, and putting them into the right category. This
is a quest that is obvious – and it doesn't need to fit into any larger master plan.
Why they should stop and do this in the middle of a zombie apocalypse? That's
not our problem, man – nobody is holding a gun to their head.
We've got hidden areas we want to unlock in this school, and so we have an
option of making access to the other rooms based on the completion of some
kind of quest. Or at the very least, we want the completion of one quest to give
us the necessary information we need to complete the next. Perhaps access to
the school's corridor is blocked by an incredibly well barricaded door, but careful
examination of the room might reveal a piece of the wall that is structurally weak
(a partition wall, perhaps) that you could more easily break down. While not
strictly speaking a secret quest, it's one that'll need people to pay particularly
close attention to find. We could base it on a certain level of
mental.perception.observation, and require some kind of tool for the player to
actually break through the weak area – a hammer, for example. It's not
conceptually perfect, but it'll do.
Upon reaching the main corridor of the school, the last quest becomes to gain
access to what will become our instance – that could be the headmistress's office,
or maybe the cellar. Or the attic. Any of those would be entirely appropriate. In
terms of obstacles, we have plenty of options – a sturdy locked door, barricades,
perhaps even rubble from a roof that never got the needed repairs that had been
scheduled. We can mould this to the mental image we have of the school, and
choose obstacles that match our conception of the state of the structure.
You could have it so that to reach the headmistress's office, you need to clear the
rubble that is blocking the staircase to the higher reaches. This too is an obvious
quest because an obstacle implies a resolution, which implies a quest. An
alternative is to have the rubble as a red herring and the actual quest as
something slightly different. A nice way to do this is to provide an add item saying
something like ‘Seriously, this rubble is immovable. Don't even try. Honestly',
because people will try anyway and you at least warned them. If we are especially
cruel, we can add some commands that allow people to attempt to use various
204
Epitaph Online
http://drakkos.co.uk
tools on the rubble to try and clear it – but that provides nothing except the cold,
hollow laughter of the damned.
Whether this is what you want will depend on your own vindictive nature. I, for
example, am full of hate – and so the rubble will be a distraction and the real
quest will be to activate the electronic keypad that blocks access to the cellar.
The code to unlock the door will be found in the library itself, provided the library
has been put in order. However, because we don't want the player to have to sort
the library every time they want to attempt this quest, we will provide the secret
code to them as an output of the quest. How that code works doesn't matter right
now, we can decide on that later.
This will lead to a the service corridor that leads to the cellar and the boiler – in
the boiler room is our headmistress. We will return to her at a later date.
Next, we need to work out the precise steps that are going to be taken to actually
solve the quests, and what systems will need to be in place to support them.
Before we get to talking about that that though, we need to discuss the difference
between linear quests and dynamic quests. This is an important topic because it
directly relates to how ‘listable' your quest is, and how likely it is that someone
can solve it by reading a solution.
Conclusion
In this chapter we've introduced a new diagramming format for showing quest
relationships, and also spoken about the intentions behind quests. We've also
refined our plan even further and identified what our quests are about, roughly.
Our next step is to outline the steps that must be taken to actually complete the
quests. We'll do this without any reference to the code we need – we are actually
going to code all of this, just not right now. At the moment, our only constraint is
on our imagination.
205
Epitaph Online
http://drakkos.co.uk
Dynamic Quest Design
Introduction
The existence of quest lists has been a problem for MUDs for a considerable
period of time. At their core, quest lists are a way for an individual to quickly
siphon rewards with no expended effort – armed with a quest list a new character
can easily reach levels of competence that they simply did not earn. Some MUDs
resolve this problem by not giving actual in-game rewards for quests, but the
usual default attitude (and the one we have on Epitaph) is that we accept that we
cannot defeat quest lists. In fact, we have our own quest solution system
available from the website.
For a long time, using quest lists was considered tantamount to 'cheating', and
many MUDs had and have harsh penalties against those who were discovered
using them. Providing our own quest solution system simply means that everyone
can prosper if they choose, rather than those who have access to hidden
solutions.
However, just because we have finally accepted we can't beat the problem, it
doesn't mean we can't use it as an opportunity to develop better quests. That's
what the topic of this chapter is.
As a gesture of full disclosure, I will point out that a chunk of this chapter is taken
verbatim from the article Unlisting The Listable which I wrote while over at
Discworld. It's also available on the Discworld webpage and also published on the
articles section of the website Top MUD Sites.
Dynamic and Linear Quests
Linear quests are those that can be solved by issuing a set of commands in the
right order, where none of those commands changes from individual to individual,
completion to completion. Most of Epitaph's quests fall into this category, and
thus most of them are candidates for inclusion in a quest list.
Dynamic quests are those in which some elements of the quest are randomized or
otherwise changed based on the player attempting the quest. At best, such quests
can be listed only in terms of the instructions as to how to solve it – the solution
is still something that is unique to a particular player. The exact level of
dynamism will vary from quest to quest. A quest where an NPC says 'Guess the
number', and the number is randomly assigned from player to play – strictly
speaking, that's a dynamic quest, but it'svery much at the shallow end of the
dynamism pool.
At the other extreme is a quest like the Murder Mystery in Genua on Discworld (a
quest I wrote), where absolutely every part of it is dynamically generated when the
quest is started. The only thing a quest list can provide for this is the instructions
206
Epitaph Online
http://drakkos.co.uk
as to how to interact with the quest. Solving the quest, regardless of how much
help you get, is still the work of a good few hours of sleuthing.
Let's talk about the Murder Mystery and see an example of the architecture
needed for an entirely dynamic quest:
The quest is set in a hotel — a murder has been committed — murder most foul!
It's your job to find out who the murderer is. The hotel is a suite of eight or so
rooms, each of which has numerous hiding places. The murderer is one of seven
NPCs. The murder victim is the eighth NPC. NPCs are randomly set up so that one
is the victim and one is the murderer. Instantly, this adds a level of dynamic
configuration to the quest that makes it more difficult to list.
The murderer is furnished with a murder weapon (randomly), and each NPC is
setup with a series of occupations — each occupation is associated with a given
murder weapon. Captain Beefy, head of the city guard, perhaps trained as a
butcher before becoming a soldier — he'd be familiar with swords (from his
soldiering), and meat cleavers (from his butcher days). Again, all of these
occupations are set up randomly.
This immediately narrows down the suspects to those who are familiar with the
murder weapon — examining the corpse reveals the weapon used, as well as a
number of other clues (generated randomly). These clues are scattered about the
hotel rooms, in various hiding places. Each NPC has a randomly generated
whereabouts list — the murderer will be the only one who was in all the rooms
containing clues after the murder was committed. As an extra constraint, the
murderer will have been seen by another NPC every hour after the murder.
The quest works by questioning NPCs — asking them where they were, who they
saw, and who they know. Through thorough questioning, you can build up a
profile of the movements of each NPC. From this and the list of murder weapons,
you can work out which NPC was in which rooms after the murder, and then you
accuse them — if you were right, you get the reward. If you were wrong, you get
nothing.
This quest took weeks to code and test — but it stands as one that it is simply
pointless to add to a quest list — there's too much randomness in how the initial
setups are generated. There are billions of possible permutations — the only bits
that can be listed are the instructions, and those are the easy part. The hard part
is solving the puzzle.
Which are Best?
There's not a lot of dissent on this score – quests that give rewards should require
an investment of effort. There is no effort in following instructions from a quest
list, and so linear quests disproportionately reward quest listers. It may take a
normal player an hour to solve a quest, and a quest list user can type in the
instructions, get the reward, and then move on to the next. This completely
inverts the principle of invested effort influencing reward.
207
Epitaph Online
http://drakkos.co.uk
Every level of dynamics that you put into a quest adds a level of work that an
individual has to do in order to complete the quest. As such, whenever possible,
you should be thinking of ways in which your quests can be randomly set up.
HOWEVER!
It is important that a quest system offers something for everyone, and the kind of
quests that involve people grinding out calculations, poring over spreadsheets
and ticking off possibilities on a grid are usually those that emphasise logical
skills over other skills – they have to be, as a nature of their dynamic construction.
Some people don't enjoy sudoko – some people don't enjoy crosswords. Those
people are not well served by quests that are heavily dynamic.
Quests that have linear steps are not necessarily 'easier', and do not necessarily
exercise the other skills that people may have, but the offer possibilities for more
creative exercises because there is no need for the experience to be resolved
down to easily represented and manipulated data points.
Once, I felt very much that a MUD should have all dynamic quests all the time.
Since then, my thinking has matured a bit. There's no need to be militant about
this – a good game can, and probably should, have quests of both types.
The question becomes again though – how do you ensure that people get
rewarded appropriately for their effort? For this, we have simply made the
generation of quest levels based on a rating in several categories – how random is
it, how much player skill is required (that's – skill on your behalf). How much
character skill is required (your character's levels), how much travelling, how much
time, etc, etc. These then get thrown into a big blender and out comes a number
that represents the quest level – easily listable quests thus simply end up being
lower level quests than those that need you to spend half an hour with a
spreadsheet.
So, the policy on Epitaph is schitzophrenic – I do prefer dynamic quests on the
whole, but I also appreciate my preferences are not universal and that others will
have different perceptions. The most important thing is that your quests are
interesting.
Some Guiding Principles For Dynamic Quest Design
The process of making a dynamic quest is the process of creating an implied
contract with your players – that their effort is going to be fairly rewarded. As
such, there are some design principles that go along with putting such quests in
the game.
Principle One: Be Transparent
If something can be listed, it will be listed. Pre-empt this by making all of that
information freely available within the game. You are not deterring anyone by
208
Epitaph Online
http://drakkos.co.uk
hiding it - secret information is valuable, and it only penalises those willing to play
the game fairly. Obviously this contradicts our discussions about the things that
motivate explorers, but everything in game design is a trade-off, especially when
the thing in question has broader appeal – everyone does quests, in general they
can't cater purely for explorers.
Be up-front about the syntax — make it obvious, and make it clear. Stick it in a
help-file, or on a web-page. Let everyone know how to find it.
Principle Two: Identify areas where there is room for randomness
Consider the standard 'courier' quest (the kind that we do via missions): You are a
brave hero, yadda yadda yadda, great risk, peril, danger, take my item X, give it to
NPC Y.
The bits around the edges may be different from quest to quest, but the structure
is the same. There is great scope for randomness — generate item X from a list of
possibilities, and likewise generate item Y from a list of possibilities. The same
quest engine can generate dozens of different quests: 'Take this rock to Maggie
May', 'Take this pencil to the Consumer of Souls', 'Take this sword to Mrs.
Miggins'.
This is such a simple example that we don't even consider it a quest – it's a
faction mission instead. All that someone needs to do to make their very own
faction use it is to define some enemies (used to determine killing targets), some
random macguffins (items that you have to take from place to place), some
desired items and so on. The framework of these is so simple that our mission
generator can handle everything else.
We have a quest that involves decrypting a coded message on behalf of a
university lecturer. Obviously this message could be chosen from a list, but we
did something slightly different — the message is only available as a piece of
substitution ciphered text. The principle of deciphering such a message is fairly
standard, and a description is available on many web-sites — but it's still a
complex thought exercise to put that theory into practise.
An alternate mechanism for this kind of quest would have been to give the text
coded, but provide a 'decipher' command that took a character's skill and gave
them the answer. The 'story' of the quest is the same, but the first way is dynamic
and the second way is linear. For the former, only the instructions can be listed.
For the latter, everything up to and including the commands you need to type in
which order can be put on a list.
Principle Three: Provide in-game means of finding quest hints
Players often turn to quest lists in frustration — not being able to find quests, or
simply not knowing what to do. Documenting all the bits of the quests that are
possible to list helps alleviate this, but so does providing an in-game quest hint
system. Does your area have bars and taverns? That's an excellent place to drop
209
Epitaph Online
http://drakkos.co.uk
the occasional hint. 'I hear there have been some murders up at the old hotel', or
'I saw a shady looking guy hanging around in the bar the other night'. You can
liberally include red herrings and misinformation, as well as some useless trivia or
funny observations. It doesn't have to be All Business. Make it funny, make it cute,
and people will like it regardless. Guess what we have on Epitaph to help you
with that? That's right, a rumour system!
Simply giving people an indication of where quests can be found removes a lot of
the motivation for those 'casual' quest list users — those who didn't know where
else to look and so turned to the Dark Side in their despair. If we make quests
easy to find, and provide hints as to where people should be looking, then those
who want to solve the quests themselves are able to do so without being entirely
disadvantaged. We offer both 'OOC' mechanisms (the questsense command and
the quests page on the website), and 'IC' mechanisms (rumours seeded through
the game).
Principle Four: Make it Interesting
Obvious, I know... but hear me out.
Remember why people do quests in the first place — sometimes it's for the
reward, and making a quest unlistable means the reward can be proportionate.
Often though, it's just for the thrill of solving a puzzle. Unlistable quests are a
quantum leap more difficult to code, but they offer the potential to be so much
more interesting. Their very difficulty inspires us to be more creative.
The more interesting you make the quest, in terms of the tasks involved and the
storyline, the more people will be willing to do it.
Principle Five: Amply reward effort
If it takes two hours to solve a quest, it should give a substantial reward that
reflects the time taken. It doesn't have to be equal to what would have been
gained farming NPCs for XP, but it should be enough to justify the expenditure of
time. If people feel that they are getting enough of a reward for their hard-earned
quest, there is less of an incentive to attempt to buck the system.
The investment in coding effort means that you want to get maximum utility out
of your quest — a fair reward is also a good draw of interest. If you make it
worthwhile, then your development time will not have been in vain.
For the most part, the rewards a quest gives our are managed centrally, but you
should consider how much you are asking of your players, and whether or not
that is commensurate with the reward they will get in the end.
Dynamic Quests – The Book Sorting Quest
So, how does all of this help us design our Zombieville quests? Well, for one thing
210
Epitaph Online
http://drakkos.co.uk
it gives us another design principle to bear in mind – make it all as random as
possible.
Let's go over each of our quests in turn and look at how we could make them
dynamic. The first is the sorting exercise in the library.
The traditional, linear version of this would be to have a set of books that fit into
a set of bookshelves. The first book is 'Stuff for people' and fits into the bookcase
'Things about stuff'... if it's always that, then it can be solved entirely from a quest
list.
A better solution is then to generate books either randomly, or from a large list.
There are opportunities here to make the quest more 'fun' or interesting too in the
choices of book you make. You can lighten the atmosphere by including books
that are in the vein of the old standard of 'Amusing Book Title' by 'Amusing
Author', such as 'A Stitch In Time, by Justin Case', or 'A Boy's Guide to Archery, by
Beau N. Arrow'.
On the other hand, if you're looking to intensify a dark atmosphere, you can have
books that play to that them – famous books about the dreadfulness of human
nature, or even fake books that do the same. 'She Won't Be Coming Back, A
Mother's Story of Loss and Remorse'. It's a bit of a downer, but the ornamentation
you put around a thing like this is important for the atmosphere you build.
It's little touches like this that make our game so special, and worth exploring. If
we have a long list of books, and a set of randomly selected book cases, then
we're a long way towards producing real dynamism in our quest design.
To imagine this in practice, let's say we have four bookshelves – 'biographies',
'fiction', 'geography' and science'. Those four bookshelves then set up the
possible set of books to be found in the library. The player picks up a book, and
reads the cover. The book is randomly chosen from the list of valid options:

'The Principle of Entropy – Thermodynamics and the Decline of the
Inevitable Universe'
The topic of that is science, and so the book is slotted into the science bookshelf.
Repeat this for a certain number of books, and the library is organized.
The bigger the list of books, the less chance there is that the books will be listed
on a site. You don't even have to have every one of them being a joke or a deep
philosophical statement, you can have the book titles completely randomly
generated if you so choose. Indeed, we're going to go with a variation of these
when we move on to actually writing the quest.
So, let's formalize what this quest will involve:




Every reset, the library subsides and the books spill out.
At this point, the bookshelves in the room are randomized.
The random bookshelves are used to generate random book titles.
When a player picks up a book, they have to put the book in the appropriate
shelf.
211
Epitaph Online


http://drakkos.co.uk
When they've done this ten times, the quest is completed.
Upon sorting the last book, a small slip of paper falls out of it, containing a
randomly generated code which just happens to be the code to the
basement.
While we don't need to come up with all the quest details now, we should
probably think of a quest title and quest story in advance. For this one, how about
something like:
Visiting Librarian, in which you obeyed the categorical imperative
Your quest story should ideally be a little bit funny, or a little bit dark. At its best,
it would be both – dark humour is pretty great for a game like ours. It's not either
of those things here, because I am an Old Man with no measurable sense of
humour.
Dynamic Quests – The Partition Quest
It's a little harder to figure out the dynamics of the partition wall quest because it
seems like something you either do or do not. That's absolutely fine – as I said
above, a blend of linear and dynamic quests is better than all one or nothing.
Having said that, there are ways and means to make the quest more dynamic, and
we'll do these because it'll be more instructional when we come to actually code it
in Intermediate LPC.
First of all, we could link the location of the wall to random bookcases, so you
need to look in the right place before you see that part of the wall is a partition.
Rather than 'look wall' you would 'look wall behind science bookshelf'. In this way
at least you have to do some work yourself.
The second option is to base the tool required to break through on some kind of
random algorithm – sometimes it'll look as if a hammer is needed, sometimes a
crowbar, maybe other times it'll need a knife of some kind. This should remain
constant as soon as a player has discovered the hole, because it's unfair to make
players go fetch a crowbar and have the room reset in the interval so as they need
a shovel.
So, our formal steps for the quest are as follows:





Randomly generate a location for the partition wall
If the player passes an observation task check, show the wall when they
look in the right place.
When a player looks at the wall, give them the clues as to how they need to
break through it.
The player uses the hints given as to tool needed to break through the wall.
Upon doing so, the quest is granted and the player is moved into the
corridor of the school.
For a quest story, how about this?
212
Epitaph Online
http://drakkos.co.uk
Mister Gorbachev, in which you tore down that wall
I know, it's terrible. I'll get my coat...
Dynamic Quests – The Secret Room Quest
The randomness in the cellar is already in place – if the secret code generated
from Visiting Librarian is random, then every player will need to complete that
quest in order to find the code. Let's add a little bit of extra challenge though –
we'll hide the keypad behind a painting of an old school headmaster. We can add
extra randomness in by having more paintings in the room, and changing which
painting the panel is behind for each player. You could even work this as a simple
puzzle in the vein of the zebra problem if you were feeling adventurous. We'll be
slightly less adventurous and having the paintings be of people with particular
facial features and algorithmically generated names. We can generate them
entirely randomly so as to end up with, for example:



The huge portrait of a man with a big nose and blue eyes.
The small portrait of a woman with small lips and red teeth
The medium portrait of a bear with big teeth and green eyes
Ideally this would link back into the library, so that a player making use of the
library would be able to find the clues necessary to identify exactly which painting
needs to be moved to reveal the panel.
Our formal steps then:




Randomly generate a set of paintings.
Allow the player to research the secret panel in the library and find clues
about the paintings.
When the player moves aside the right painting, they find a panel.
Entering the secret code into the panel opens up the secret door to the
hidden staircase.
And our quest story:
Art Investigator, in which you discovered what was beneath the surface.
Look, stop staring at me. I'm doing the best I can.
Conclusion
After three chapters, we now have an overview of exactly what our quests are
going to involve. You may be thinking 'I don't know how I'd even begin coding any
of this', but that's okay. You will when we get to actually starting to write up our
new and improved village.
We've spent a lot of time talking about quests specifically because they're one of
213
Epitaph Online
http://drakkos.co.uk
the easiest things to get wrong, and it's often hard to relate the often vague
guidance you are given on how to code a quest to our own specific requirements.
Sadly, most of what quest design involves is just sitting and thinking, and there's
no shortcut for that. However, there are plenty of places to look for inspiration –
your domain administration, your fellow creators, relevant books, the playerbase,
and as an absolute last resort when you have no other alternative, you can even
talk to me!
214
Epitaph Online
http://drakkos.co.uk
The Ten Commandments of Room
Descriptions
Introduction
We shouldn't get bogged down in the complexities of planning features – it's part
of being a creator, but so is the work of actually providing interesting, clear and
entertaining descriptions for everything that we write. Indeed, certainly in our first
few developments this is usually what we spend the majority of our time doing. It
may seem like something that is self-evident with regards to how it should be
done, but over the years we have built up numerous conventions as to how
descriptions should be written.
In this chapter we're going to talk about what needs to go into a good description
– it should be noted that everyone has their own views on what Good Writing is
supposed to look like. Your domain administration will be able to give you
guidance on what is considered acceptable within your domain. On top of these
though, there are some guidelines that are Universal. These are the ones we're
going to discuss.
The Ten Commandments
The Ten Commandments of Room Descriptions was a simple set of guidelines I
wrote as to the things you should and should not do when writing descriptions.
They are incorporated here from the original article.
I. Thou Shalt Not Use 'You' In Room Descriptions
All direct references to the player should be removed from the description. This is
especially true of descriptions that begin with 'You are standing in...', or similar.
Room descriptions should make sense from all perspectives and all situations...
what if I'm not standing there? What if I'm sitting, lying down, or hopping on one
leg? What if I'm looking into the room from an adjacent room, or scrying into the
room from miles away? In all these cases, the room description will no longer be
accurate:
> look down
You are falling at great speed towards jagged rocks below.
> /scream in fear
You scream in fear.
> shout Help! Help! I'm falling to my death and need someone to tell my wife I
love her!
215
Epitaph Online
http://drakkos.co.uk
<several uneventful seconds pass>
> /think
You think carefully.
> look
You are standing at the top of a cliff. It's perfectly safe, provided you
don't take a wrong step.
> say Oh yeah!
You exclaim: Oh yeah!
It is jarring when this happens, and you should consider this an Inviolable Rule as
to your own descriptions. Never make any assumptions on how the player is
viewing the description – keep it general.
II. Thou Shalt Not Assume a Course of Action from The Viewer
A room description's purpose in life is to describe - hence the word, ‘description'.
What a room description is not there for is to dictate the action of the viewer, even
for the holy purposes of narrative causality.
You decide to try the door handle, but the door is locked.
You decide to follow the path to the north.
Let the player choose what they want to do, based on your descriptions... you're
not here to play the game for them. Likewise, don't tell people how they think, or
how they feel - they know better than you do. This is something that you see
particularly in games where the courses of action through the game are more
restrictive than ours - the MUD is not a choose your own adventure book.
This is a dark, scary trail through the forest. To the north is a horrible
looking path, and you decide to follow it to...
> don't follow path
What?
... to follow it into the depths of the forest where the horrible spiders roam
with...
> stop following path
What?
...roam with their sharp, pointy teeth and horrible eyes glaring...
> say I don't want to follow the path, I'm scared of spiders!
You exclaim: I don't want to follow the trail, I'm scared of spiders!
...horrible eyes glaring, and yet you feel unfraid, as if the spiders hold no
fear for you.
> /whimper in fear
You whimper in fear.
Having the game assuming a course of action from you is tremendously annoying
216
Epitaph Online
http://drakkos.co.uk
if you are personally invested in your character. It can seriously impact on a
player's sense of immersion.
III. Thou Shalt Not Write Static Descriptions of Dynamic Objects
By dynamic objects, I mean those objects in a description that are likely to change
their state or appearance during the course of the game.
'The door to the north is closed', for example, when the door may be opened by a
player or NPC.
Or...
'The chairs are empty', when someone could sit in them.
This is a lovely pre-apocalypse pub, with an empty stool at the bar.
Drakkos Wyrmstalker arrives from the south.
Drakkos Wyrmstalker sits on the stool.
> look
This is a lovely pre-apocalypse pub, with an empty stool at the bar.
Drakkos Wyrmstalker is sitting on the stool.
Drakkos Wyrmstalker says: Hey sailor. Buy me a drink?
Static descriptions should refer to static objects. If you must make a reference to
an item that is likely to be dynamic (like a crowd of people), then make it vague
rather than specific:
> look crowd
The crowd mills and jostles around you, caressing your body with the Brownian
motion of its constituent members.
Likewise, when describing an object that is mobile, it is better to do this as a
room chat. The bird is flying in the sky' in a long description makes the whole
room look static, like an oil painting. If the bird flies across the sky occasionally in
a room chat, this is less true.
IV. Thou Shalt Not Use Relative Directions
Unless you're going to get very clever with code, you cannot assume a direction of
entry from a player. When entering a room with two entrances, in general you
don't know which one they entered by. Likewise, in a room with only one
entrance, you don't know if they arrived via that entrance, or if they portalled in,
logged in there, or were dropped off there by mutant bats. For this reason, you
shouldn't assume that a relative direction is going to be appropriate.
The forest stretches ahead of you to the north.
What if I just arrived from the north? Wouldn't it be stretching behind me?
217
Epitaph Online
http://drakkos.co.uk
Likewise with 'left' and 'right'... these will change dependent on what direction I
have just arrived from.
This is the kitchen of a pretty house. The larder is to your left. The sitting
room is to your right, the pantry to the south and the hallway is behind you
to the north.
> south
It's the pantry.
> north
This is the kitchen of a pretty house. The larder is to your left. The sitting
room is to your right, the pantry to the south and the hallway is behind you
to the north.
> ponder
You ponder.
> north
It's the hallway.
> south
This is the kitchen of a pretty house. The larder is to your left. The sitting
room is to your right, the pantry to the south and the hallway is behind you
to the north.
> shout Help! Help! I'm trapped in the house that Escher built!
Avoid words that assume you arrived from a particular location. It won't bother all
players, but the ones it will it will bother a lot.
V. Thou Shalt Write (Mostly) Proper English
Although writing a description is not the same as writing a thesis, there are
certain stylistic elements of formal writing that you should keep in mind when
writing. One of these is that you should not write numbers as Arabic digits...
instead, write them out fully. 'There are 2 large stone blocks here' should instead
be 'There are two large stone blocks here'.
This rule extends at least up to ten, and usually up to twenty. Beyond that, using
the digits is acceptable, although using a general plural such as 'lots', or 'many' is
perhaps a better approach. After all, who is going to count the exact number
upon a casual glance at something?
Also with regards to writing properly: proper sentences have verbs, and so should
all the sentences in your description. 'A large clearing' is not a sentence while
'This is a large clearing' is a sentence.
However, don't worry too much about being too formal... in many cases, writing
completely formal text will detract from a quality description. Use formal writing
where appropriate, and whatever sounds good for the rest.
Finally, the standard of the MUD is for the British spelling of words. Color, center,
and emphasize, etc, are all wrong. Colour, centre and emphasise are correct. All
your descriptions should conform to this standard. If in doubt, grab hold of a
218
Epitaph Online
http://drakkos.co.uk
British English Spellchecker and run it over your descriptions.
VI. Thou Shalt Make Thy Text Easy On the Eye
A MUD such as Epitaph is, I'm sure you've noticed, a text-based medium. As such,
the presentation of the text in a clean and attractive manner is of paramount
importance. Although the practice is falling out of vogue as variable width
computer fonts become the norm, it is one of the standards of the MUD to
double-space between sentences. The practice of double-spacing stems from the
days of typewriters when each letter took up the same amount of space on a
page. Many MUD and telnet clients still use fixed-width fonts to present MUD
output, and using only a single space between sentences makes the whole
description cramped and difficult to read..
Related to this, you should avoid the use of colour in descriptions and shorts.
Colour is a powerful method for emphasising particular words or sentences, but
using it carelessly makes everything garish and detracts from the rest of the text.
Additionally, it detracts from the consistency of the MUD in general. Why does
your 'red sweater' have a coloured short description, when someone else's 'green
sweater' doesn't?
Remember too that a number of users cannot see colours on their clients, and
even those that can may find your choice of colour clashes with their client's
background or their own defined colour schemes.
Finally, using colour also means people have to worry about stripping colour
codes from your short descriptions when they reference them in code... an
additional CPU and design overhead for something that is usually undesirable in
the first place.
VII. Thou Shalt Describe Every Noun
If you mention a noun in your room, you should also make sure you have an
add_item for it. This goes equally for nouns within the descriptions of add_items.
There are few things more instantly indicative of a lack of attention in a MUD than
descriptions that mention lots of things in the long description, but never provide
items you can look at for them:
This is a nice stairwell. There are some lovely stairs leading upwards. There
are zombies on the stairs.
> look stairs
Cannot find "stairs", no match.
> look zombies
Cannot find "zombies", no match.
> /sigh
You sigh
Of course, you don't have to be too fanatic about this... describing the dew drop
219
Epitaph Online
http://drakkos.co.uk
on the leaf of the stalk of the plant in the garden is unnecessary unless you really
feel you want to. Most people will never look beyond a certain depth of add_item,
but it's nice to reward those that make the effort by ensuring they have something
to see if they're following your descriptions. Having a little joke at the end of a
long chain of nouns is a nice touch too... it's the attention to detail that really
makes a MUD special.
VIII. Thou Shalt Not Describe NPCs In The Description
If you have an NPC in your room for whatever reason, it should be a separate
object and not a part of the room description. So rather than writing the wiry old
shopkeeper with an add_item, he should be a separately coded NPC. If you don't
do this, you get all sorts of inconsistencies such failing to find a match when you
try to kill them, or being told they are not a living target when spells are cast on
them. It's also inconsistent with how the rest of the MUD deals with NPCs... where
there is a living creature, you should be able to interact with it as such in the
same way as you can elsewhere.
This is a shop. There is a shopkeeper standing here.
> look shopkeeper.
What a clean old man!
> /leer shopkeeper
You leer shopkeeper.
> /ponder
You ponder.
> kill shopkeeper.
Cannot find "shopkeeper", no match.
> /boggle
You boggle at the concept.
You may not want it to be possible for players to kill your precious NPCs, but
that's irrelevant – there are other ways to do it than to make the NPC part of the
description. It is inconsistent for there to be a mention of an NPC and no way to
interact with it.
IX. Thou Shalt Not Put Command Hints In The Description
If you are describing a room with special commands within, it's very tempting to
hint at these commands in the room description. This is Very Ugly, however, and
should be avoided:
This is a nice shop. You can 'buy' and 'sell' stuff here
It's much, much better to put these command hints in a help-file. Most standard
shops will already have a help-file, so you won't even need to do that. However, if
your room or shop does something unusual, it's always better to give the syntax
in a help-file rather than in the long description. Partially this is to make the help
system a homogenous, consistent thing. It's also to remove Out Of Character
220
Epitaph Online
http://drakkos.co.uk
game information from the MUD.
Signs are also a valid way of providing information relating to how a particular
room or shop works... but signs should always be in theme, prompting as to the
use of a room, but never directly quoting the syntax. Signs are most effectively
used as a means of conveying game information relevant to a particular room,
such as exchange rates, cost for services, and so on:
Bad:
Welcome To Bing's Bank!
We Give You More Bang For Your Buck!
You can 'apply' for an account here.
You can 'deposit' money here.
The transaction fee is 10%.
Shop around! We guarantee you won't find a better rate anywhere else in this
bank
Better:
Welcome To Bing's Bank!
We Give You More Bang For Your Buck!
Apply For An Account Today!
We charge a low, low 10% on all deposits.
Shop around! We guarantee you won't find a better rate anywhere else in this
bank.
Just think how the signs in your local store, bank or supermarket look. That's the
way all signs should look on Epitaph. Leave the actual commands and instructions
for the help-file.
X. Thou Shalt Have Fun With Your Descriptions
This isn't just for your benefit. It's far, far more interesting and enjoyable to read
descriptions written by someone who is enjoying what they do, rather than
descriptions that have been mechanically churned out according to some rote
formula. However, don't go nuts with this. It's all too easy to just give in and be
silly or surreal in the hopes it will make your descriptions funny and enjoyable.
What it mostly does, however, is make you look like a loon. In all cases, try and
keep your descriptions in theme, but fun!
This is a large marketplace. Giraffes are bouncing on the stalls and pink
elephants in tutus are waltzing gently around the villagers as they shop.
> look giraffe
221
Epitaph Online
http://drakkos.co.uk
There isn't a giraffe here... you're going crazy!
> look elephant
Why would there be an elephant here? It's a marketplace.
> /roll eyes
You roll your eyes.
Remember your theme here – there is nothing at all wrong with dark comedy –
gallows humour is a regular feature of horror and apocalypse works. But we're
not a game with a 'silly' theme – we're trying to construct a particular kind of
atmosphere, and you must be mindful of that.
Conclusion
So, those are the commandments for writing room descriptions. In the next
chapter we'll look at it more in terms of how to put descriptions together. Pay
attention to these commandments and you'll avoid making most of the mistakes
that mark out a new creator from a more experienced creator, and that's good –
part of the reason you are reading these documents is to learn from everyone
else's mistakes!
222
Epitaph Online
http://drakkos.co.uk
The Art of Writing Room Descriptions
Introduction
Room descriptions are not the simplest things to write from a cold start, and are
often a stumbling block for new creators. It takes a certain amount of mental
exercise until you are able to write hundreds of descriptions without feeling
overwhelmed at the task. Having a semi-formal process for writing is the way to
gain sufficient confidence to be ready to describe anything you need, and in the
quantities your require.
In the last chapter we discussed the commandments, but in this chapter we're
going to take a more positivist approach to writing descriptions – how to get
started, and how to be sure that they're good enough.
Your Mental Picture
The first step in writing descriptions is to picture what you're writing about. If
you're describing a room, imagine what it looks like – get a feel in your dusty
mind for the most distinctive features. A good room description is around fifty to
eighty words in length. That may sound like a lot, but it's not really - in fact, once
you get a taste for writing them you'll find it's quite restrictive.
The paragraph above is seventy-four words in length - you'll soon use up the word
allowance before you've really described everything you want to. What's useful
then is a structured way to write the description so that you can channel your
descriptive juices more effectively.
To begin with, consider what you can see in the far distance – this helps set the
constraints of your 'observable view'. When out in the Wide Open, you'll have a far
greater vista than you would in a cramped city, and you should reflect that in your
description. Setting the context is a good way to begin a description – where you
have great visibility, your description should begin by explaining that. Likewise
when within tight conditions, the description should reflect that too.
The trees here blot out the sky with their densely packed branches. All
around, the forest is dark and forbidding... little light penetrates the
shadows within.
Next you can focus on the main features of your immediate location – what is it
that distinguishes this room particularly from any other room in the MUD? Close
your eyes and imagine standing in the room – what instantly strikes you that you
should mention? You don't have to describe absolutely everything in the long
description, just those elements that are immediately noticeable. Are the walls a
particular colour? What about the floor? Are there windows? You should frame
such details in a way to describe the constraints of the room – the bits that relate
223
Epitaph Online
http://drakkos.co.uk
to where this room begins and ends.
The ground is strewn with organic matter shed from the trees above, forming a
carpet of leaves and wet, moldy bark.
Next, make mention of any items of particular note in the room. Does it have
bookcases? Does it have a fountain? Is the ground strewn with rocks? Imagine
yourself as a burglar casing the joint – which would be the things most likely to
draw your eye in terms of the contents of the location?
Several large rocks have been set in a circle on the ground here, and there
are several vaguely humanoid-looking wooden puppets hanging from the nearby
branches.
Finally, relate the room to the rest of the area. The exits line of a description gives
you the available exits, but you can expand on these to provide actual, immersive
detail. Don't write 'the exits are north and south', because that duplicates already
available information. Instead, you could do something like the following:
The road north leads into a clearing, while the path south plunges into the
heart of the forest.
Bringing these together gives you the full room description:
The trees here blot out the sky with their densely packed branches. All
around, the forest is dark and forbidding... little light penetrates the
shadows within. The ground is strewn with organic matter shed from the trees
above, forming a carpet of leaves and wet, moldy bark. Several large rocks
have been set in a circle on the ground here, and there are several vaguely
humanoid-looking wooden puppets hanging from the nearby branches. The road
north leads into a clearing, while the path south plunges into the heart of
the forest.
While the structure is important, what is also important is your choice of words.
You should pick words that are evocative of the impression you are trying to
convey. Be careful though not to exhaust your vocabulary! The word choice in our
example description here reflects that this is a scary forest, and not a cheerful
Disneyesque one. Mention is made of the darkness, and the forbidding
atmosphere. The trees are portrayed as being active in blotting out the sky, rather
than ‘The sky can't be seen because of the branches'. The path into the heart of
the forest doesn't lead into it, it plunges like a dagger.
You have to be careful with this kind of thing, because if you do it too much your
description just sounds ludicriously over the top. Evocative phrases are to be
peppered through your description like a delicious, but spicy, herb. Consider what
happens when we turn the evocation up to eleven:
The trees loom all around, their vicious branches ripping at the sky like
wooden talons. The forest all around emits an aura of death and despair,
promising only horror for those who dare explore its interior. The ground is a
wet blanket of rotting leaves and decaying bark. Jagged rocks have been set in
an ominous circle on the ground, and wooden puppets have been strung from the
branches like grotesque parodies of humanity. The road leads north into a
clearing, with the path south leads into the darkened horrors of the interior.
224
Epitaph Online
http://drakkos.co.uk
Individually there's nothing especially wrong with any of the description, but when
taken together it screams ‘I am trying very hard to scare you!', and the entire
effect is lost. This leads onto a not insubstantial secondary problem... when it
comes to writing the next description, how are you going to give the same
impression without repeating yourself?
A common mistake that new creators make is to go overboard with adjectives. Not
everything you see is interesting enough to justify an adjective:
The beautiful street is sparklingly clean. Busy people go by carrying
interesting-looking bags of shopping. Cheerful dogs bark with happy woofing
sounds'
It might look okay, but as with our ‘over the top' description it just doesn't ring
true - it's somewhat artificial in its insistence that everything in the room is worth
actually describing. You can't make banal interesting, so don't try - don't force
adjectives just because your high-school English teacher told you that they were
important. Keep them in reserve for when they're actually warranted.
Finally, if you have a strong mental image you'll be able to appreciate that while
you know what things are, people coming to your description without that context
will be looking at a scene they can't neccessarily parse. 'People wander into the
nearby bank' is probably fair enough, but how do the players know it's a bank?
‘People wander into the ornate building' is a little bit better, with the clue that the
building is a bank reserved for its add item.
You can get additional flavour in a description by making use of active verbs over
passive – 'the shop dummy has a hat on it head' may be true, but is boring. 'the
shop dummy has a hat perched on its head' is better, but you can continue to
refine this until it says something genuinely interesting, such as 'the shop dummy
has a hat perched jauntily on its head'.
It's hard to keep this up, description after description, item after item, but you
soon get into the swing of it. The key is to have somewhere interesting to
describe. Anystreet in Anytown isn't going to be easy to describe because it's not
exciting. That strikes at the core of the main difficulty of room development on
Epitaph – we don't live in an amazing fantasy world, we live in a world that is filled
with mundane locations and the same branding colours and logos spread
everywhere. That's a problem, but it's not an insurmountable one.
If you find it hard to write your descriptions, spend a bit of time thinking about
whether you can put some kind of feature throughout if. Maybe it has a river
flowing through it, or it is nestled into a fertile valley. Having some kind of
geographical feature to focus on can make everything flow a little easier.
Remember, you're not describing places that have been defined by anyone else,
you can make them as interesting internally as you like (within the context of the
theme and the constraints of the real world).
225
Epitaph Online
http://drakkos.co.uk
Hand-Crafting versus Procedural Descriptions
One of the most telling experiences I had on Discworld was when the redesign of
Ankh-Morpork went live. It's no secret that I thought it was a terrible mistake, and
that it marked a turning point for the worse in the MUD's history. But I was at least
sure that the players would hate it as much as many of the creators did.
I was disappointed that never turned out to be true. And then, when I stopped
being disappointed, I started to become depressed because of the lesson I had
learned.
There were many reasons to hate the New AM project. One of them was the fact,
in my mind, it lowered the quality of the rest of the MUD by replacing a 100%
coverage of hand-written descriptions (of varying quality, it's true) with huge
swathes of generated descriptions that consisted, in their entirety, of lines like
'Street X continues to the east and west'. I hated that. I thought it was the absolute
worst advert to give as to the quality of Discworld MUD. I thought people would
see that and think 'the lack of care and attention shown here in Ankh-Morpork
leads me to believe the rest of the MUD will be as shoddy'.
For context, at the time we were putting the finishing touches to Genua, which
had taken a long time of writing to put in place. We worked hard on ensuring that
it was all properly described, and a lot of care and attention was poured into
making it as detailed as we could. I thought at the time that was a good
investment of developer effort, and I was very proud of the quality of the writing
across the majority of the city.
New AM changed my view on how worthwhile that effort turned out to be. What
New AM told me was that, while a minority of players do read all the descriptions
and pay attention to all of the add items they can look at - for a significant
proportion of Discworld's playerbase, descriptions simply do not matter in the
slightest. Considering how long a well described room takes to make (a long
time), I have come to the conclusion that hand-rolled descriptions area mug's
game.
Now, that comment needs some qualification, because in its unadulterated form
that comment implies 'so don't bother writing any descriptions'. My view lies
between the two extremes - rooms should have descriptions, and they should
have all the add_items needed to ensure they permit exploration and the
construction of a mental picture of the area. However, it's not neccessary to have
a unique, hand-rolled description for each room and each item in each room.
Quite simply, that's too much to write and too much to read.
On the other hand, I do pay attention to what other MUDs are doing, especially
new MUDs. Many of them advertise thousands of rooms, but these rooms have no
descriptions at all - they are just expressed as locations in an ASCII map. More still
have long descriptions, but no add_items. To me, a MUD doesn't look like it's
serious about creating an impression of a world unless you can look around you
and construct your mental picture. I like to think of the distinction between
graphical MMOs and MUDs as the same distinction between books and movies.
226
Epitaph Online
http://drakkos.co.uk
The mind is the best graphics card that you will ever possess, but it does need
information in order to construct the world.
It's easy to get carried away when writing descriptions though - what's important
is that you are sure why you are writing them. To my mind, you are not looking to
exhaustively describe a room or an item or an NPC. I hate descriptions that are
largely recitations of an inventory, and while I admire the dedication that goes
into describing every whorl and sworl in an add_item about a rug, that's not what I
want. I want the data points I need to construct my mental representation of the
world. I don't want to see three hundred words describing every scratch in the
parquet floor - I want to be told 'there is a worn parquet floor' so I am in control of
filling in the blanks. In short, descriptions have a job to do - they are for setting
the scene and giving people the information they need to visualise their game
world, but not confine it.
What we've settled on at the moment for Epitaph is a medium point between 'no
descriptions' and 'exhaustive, unique descriptions for everything', and it consists
of:



Streets get a single description that describes them in the abstract
Individual buildings get hand-rolled descriptions insofar as they have things
of interest
Templated rooms have stochastically generated descriptions.
For streets, this raises the additional complication that it's difficult to write a
single description for a room that, for example, winds around a larger building.
To get around that, we layer landmarks on the description that so that you can
see where major buildings are in relation to where you currently stand. Each
landmark comes with a number of descriptions, additional add items and such
that ensure that you can see an appropriate description based on where you are
and how far away it is, and you can look at the extra details that are indicated in
the add_item for the landmark itself. Each of these landmarks are pretty detailed,
and the combination of landmarks you can see from a room define what your
description currently is, and the detail of the room, and the range of add_items.
Individual buildings get hand-rolled descriptions, but only where it makes sense.
Two halves of one large, uniform room are likely to share a room description, only
varying in terms of specific details. By this I mean, for example, a long dining
room which may be made up of three rooms, but each room is pretty much the
same. Or a long wood panelled corridor where there are no distinguishing
features at any particular part of it.
Templated rooms are those that we place many copies of - coffee house
franchises and houses are examples of these. Within the code for these, we use a
stochastic system to set up the descriptions (colour and type of walls, floors, etc)
so that they remain constant between loadings of the rooms, but the individual
parts of the description will still be hand-rolled and combined together. Thus, one
set of options may describe potentially hundreds of small variations of the same
core.
Does this approach work? I don't know, we'll need to get a lot more people
227
Epitaph Online
http://drakkos.co.uk
through the game to say - I think it does, but a large part of the reason why I think
it works is based on the theme. It is partially on Epitaph a commentary on
globalisation in addition to a simplification of our work as builders. Every
Starbucks looks the same in real life (within some general parameters), so every
Starbucks looks the same on Epitaph.
Within a unique area though, the situation is different. Alphabet Street may have
a single room description, but the Winchester has unique descriptions for each
room because they are all different, look different, and are used for different
purposes.
It's fine to include variations on a theme in multiple room descriptions with a
unique area, but shy away from direct repetition. This is especially true too of
‘special words' in a single room description – while there are always words you'll
need to repeat, the defining words in your description should be used once only.
Otherwise, it jars people out of the narrative because it brings back memories of
the first use of the word.
Sometimes though you're going to get stuck and be unable to provide a new way
of referencing something in an interesting way. In such cases, you can consider
going off on a tangent by not describing the thing directly but instead
approaching it from a different angle.
Imagine trees of such beauty and splendour that simply being in their presence
is enough to make someone fall to their knees and praise the Gods that they
were alive to experience such wonder. Imagine those trees, because they're
nowhere to be found in this forest. These trees are just like all the other
sad examples you've seen so far in this desolate place.
Don't overdo it, it's a nice surprise every now and again but soon becomes
tiresome as a narrative device. Flippant or jokey descriptions might work well the
first time they are encountered, but they become progressively more irritating as
familiarity grows. You don't want to be the creator who writes the descriptions at
which people roll their eyes.
The rule for uniqueness doesn't extend to add_items, although having a number
of different add items for the same thing is always preferable. The more variety
the better, but you get much less out of completely unique add_items than you do
out of completely unique long descriptions. For one thing, the shape and length
of a room description is a navigation aid for those who wander around with
verbose descriptions on. Even if it's not being used for navigation, more people
are going to see your long descriptions on a regular basis than will see the items
in the room.
Room Chats
Room chats can be one of the most challenging things to do properly because
they repeat so often in a room. A bad room chat will greatly reduce the overall
'polish' of your room, and having a small set of highly repetitive chats can be
worse than having no chats at all, especially in rooms where people may spend a
228
Epitaph Online
http://drakkos.co.uk
lot of idling time.
Long descriptions and add items shouldn't include activity. The long and athe dd
items are, by their very nature, frozen in time. Any activity you describe is likewise
frozen in an entirely artificial way. Any motion you want to describe should be
migrated to the room chats. Imagine the following in an add item:
Every now and again there is a soft thump as an acorn falls from a nearby
tree.
That looks horrible, and not just because of the writing. It just doesn't feel active –
it feels like you're looking at a painting of a tree dropping an acorn, rather than
experiencing something in a living, breathing game world. On the other hand, if
you occasionally see as a room chat something like:
There is a soft thump as an acorn falls from a nearby tree.
Suddenly that frozen activity becomes something active that adds a level of
richness to your location. The exception to this as a rule is when your dynamic
activity is an on-going affair. The hustle and bustle of a crowd can be conveyed in
the long, and in an add_item, and also in room chats. The static descriptions
should describe the activity in the abstract, and the room chat should describe
time-sensitive specifics. You could have for example the following description:
There is a great hustle and bustle of people going to and fro, and here and
there.
Then in your room chats, specific examples of that hustle and bustle:



An old lady bumps into you, apologises, and then wanders off.
A well dressed man stands on your toes, but walks off without noticing.
A small child wanders past and laughs at you before wandering away.
The more room chats you have, the better. Like add_items, you can profitably
share these between the relevant rooms. Having nine room chats shared in three
rooms is better than each having three unique room chats. Too few room chats
tends to give a 'groundhog day' feel to your area. A good way to make for really
dynamic areas is to have chats that are, at least in part, randomly generated. We'll
look at how to do that in the intermediate LPC documentation to follow, although
the more inventive creator should be able to work it out from the material we
covered in the chapters on introductory LPC.
References
You shouldn't rely on your own withered cortex for writing – make use of the tools
you have available. Buy yourself a good dictionary and a good thesaurus, or make
use of the many free online versions. When you're struggling for a synonym, turn
to your thesaurus. When you want to make sure that you've used a word exactly
right, then check your dictionary. Don't leave it unresolved, or guess.
229
Epitaph Online
http://drakkos.co.uk
Be careful though with synonyms derived directly from a thesaurus – they're fine
to prompt you with words you already knew but didn't immediately remember,
but synonyms are often loose associations at best. Don't turn over your own
internal editing process to an automated solution.
Quality Assurance
So, having written a description, how do you know if it's okay? The easiest way to
do this, and I am not making this up, is to read it out loud. Out loud, mind – not
just read it over.
When you read a description out loud, you instantly pick up on clumsy phrasing,
repetition, and scansion problems. A good description flows, rather than stutters.
To show this in action, read the following description out loud:
There are trees all around. The trees are brown and look like trees normally
look. Each of the trees have leaves. The leaves are green and brown and found
around. All of the trees is unhealthy and looks as if they are almost dead.
This is an exaggeratedly bad description for instructional effect, but you should
have instantly picked up on some of its flaws:



Inconsistent scansion.
Repetition of words
Incorrect form of verbs
Scansion is perhaps the most difficult of these concepts to define for those
unfamiliar with the idea, but it's to do with the natural rhythm that accompanies
verbal communication. It's a term most often applied to poetry, but applies
equally well to narrative flow. Syllables in English are either long or short, and the
combination of syllables is what defines the ‘meter' of a written piece of work. We
all instinctively have a greater or lesser feel for scansion, but when something
sounds ‘clumsy' it's usually because the rhythm doesn't fit. That's something that
just doesn't come across when you read something quietly to yourself – it only
comes out when you speak out loud.
A formal discussion of scansion is way, way outside the scope of this material –
it's just something that you should bear in mind when putting together your
descriptions.
Once you've written the description to your liking, the next step is to put it aside
for a few days and come back to it. Read it aloud once more, and then change it
so that any new issues you've identified are resolved. Sometimes you get too close
to something you've just written and you need a little perspective – that only
comes with time. If you can, get someone you know and trust to give you their
honest feedback on what you've written - an outside perspective is even better
than your own time-delayed thoughts.
230
Epitaph Online
http://drakkos.co.uk
Conclusion
Writing descriptions is a skill that comes with time and practice, but there are
certain things you can do and think about to get yourself into the habit. Not every
description is going to be a winner, and even descriptions that you like aren't
going to appeal to everyone. That's fine though, the world would be very dull if we
were all the same. All that matters is that you do the best you can to produce your
highest quality of writing. It may seem like an insurmountable task to describe an
entire village, down to every item in every room, but it soon becomes a reflex. It's
also thoroughly enjoyable, when you get into the groove.
231
Epitaph Online
http://drakkos.co.uk
Non Player Characters
Introduction
Writing descriptions for NPCs introduces a new set of difficulties – people really
aren't all that different, when it comes down to it. While the scenic panorama
visible from one mountain top will differ dramatically from the panorama available
at another, it's usual that one person looks much the same as another, within
some fairly well defined parameters. How then do you write descriptions for NPCs
to ensure that they look unique and interesting?
Additionally, NPCs are supposed to be responsive – they are supposed to say
things and react to things. That's an additional difficulty in setting them up well –
we have to write dialog. If you've never done anything like that before, it's a brand
new challenge to meet.
Finally, sometimes we don't want our NPCs to be killed because they are a part of
in-game systems. We need to consider what kind of protections we can put in
place for such NPCs, and why we might want to ensure they are protected in the
first place.
NPC Classifications
As an informal system, we tend to categorise NPCs into one of several categories:





Cannon fodder
Service
Quest
Flavour
Boss
Let's go over each of these categories in turn, and discuss what kind of creatures
belong to each.
Cannon Fodder NPCs
Cannon fodder NPCs are those that you see wandering the streets. You can think
of them as walking bags of tasty experience and loot. We tend not to spend a lot
of time worrying that they are fully interactive, because that's not the role they
fulfill in our game. It's nice if they respond to nods and waves and such, but it's
only a small subset of players who will even consider interacting with cannon
fodder, and an even smaller subset that will care that you made the effort.
As long as your cannon fodder NPCs are described with a long description, kitted
232
Epitaph Online
http://drakkos.co.uk
out with suitable equipment, and contain some load_chats, you can move on to
the next thing you have to develop. Remember, your time as a developer is
limited, and you should focus your efforts where they are most needed. In many
cases, you can get by by having them use one of the standard, pre-set
personalities to populate them with chats and responses.
Service NPCs
Service NPCs are those that exist to facilitate in-game transactions. They could be
Faction NPCs, shopkeepers, or soldiers guarding certain parts of the city. A player
killing these NPCs will impact on the provision of that service. One solution to this
is to simply not have NPCs as part of the player transaction - for example, we can
create shops without shopkeepers with no trouble. That's cheating, though – we
want our game to be richer, not more artificial. What we can do is provide
disincentives for players who attempt to kill them. That is the basic motivation
behind practically every punitive system we have in the game – to make it less
appealing for people to murder NPCs of which that other people may need to
make use. We make it so faction NPCs reward negative faction standing when
they are killed, which provides a disincentive that can be quite powerful.
In terms of writing service NPCs, descriptions and chats are important, but even
more important is the system you put in place to discourage their murder. There
are ways in which you can simply make them impossible to kill, but that's
unrealistic and lazy. We want the game to allow people to be bastards if they want
to be, but we put in place punishments for that behaviour so that there is a way of
balancing player freedom of choice with protection for game systems.
We'll talk about mechanisms to discourage gittishness later.
Quest NPCs
Quest NPCs are similar to service NPCs in that they are part of an in-game
transaction, but their presence isn't continually required. Often they disappear
when a quest has been completed, or are generated only when certain conditions
are met. As such, we can't ensure their presence at all times, or even really
necessarily tie them into a crime system. If a quest NPC is an animal, or a
criminal, or in some way beneath the notice of some appropriate faction, it is not
realistic for the faction to extend protection to them. Where possible, the systems
that protect service NPCs can be extended to quest NPCs, but not always.
Our solution to this in the past has been to make quest NPCs worth None XP, but
while that discourages indifferent slaughter it doesn't do anything to punish
someone who gets their kicks out of inconveniencing other people. Alas, we can't
have everything.
For Quest NPCs, interaction is king – they should be responsive to questions and
souls, as well as more chatty than other types of creature. Ideally they will
mention things that are obviously related to their quest. NPCs hinting at the
quests in which they are involved provide a useful mechanism for hinting that a
233
Epitaph Online
http://drakkos.co.uk
player should spend more time conversing with this NPC than they would with
another.
Flavour NPCs
Flavour NPCs have no formal standing in the game, but are unique and interesting
regardless. Most of the survivors in Dunglen fall into this category. The different
kinds of cats and dogs are all examples of flavor – we could have one kind of dog,
but we get more by having lots of different breeds.
If NPCs exist to provide flavour, they should be fully described and interactive to
ensure that flavour is... uh... delicious. The danger is that people will assume they
are part of a quest, so unless you are especially vindictive don't make them say
anything that could lead players to that conclusion. ‘Oh, where is my ring?' is an
example of a chat that implies a quest is present, and it will send players off on a
fruitless endeavour to solve it. The only result of this is that the players become
frustrated and disillusioned. The questsense command helps with that to a
degree (people can check to see if their hunches about the existence of quests are
correct), but not everyone will rely on that as a system.
Boss NPCs
Boss NPCs fill the role of ‘exciting combat opportunities', and are to be found
usually as unique NPCs in the depths of an area. Their appeal comes from the fact
they are a chance for high level players, or groups of players, to test their skills
against a dangerous, unique foe.
More than anything else, Boss NPCs should offer interesting combat experiences –
they should make full use of an appropriate range of skills and commands, and
have a range of fun and hopefully humorous attack chats. They should kick, bite,
disarm, crush, stab, knock people down, run away – the whole range of activities
needed to allow them to put up a decent fight.
Their descriptions are not quite so important because, presumably, people won't
get a lot of chance to read them. That's not to say you should ‘phone it in', but
most of your effort should be spent on making the combat interesting rather than
weaving clever functionality into the description and chats.
We have a system for allowing this through our AI behavior system – we'll see that
in a later chapter of the book.
NPC Descriptions
First of all, we need a clear picture of this NPC in our mind. While we don't usually
provide detailed histories of our non-player characters, it's worth spending a bit of
time thinking about what kind of life the NPC is likely to have had. If they are a
military type, what are the likely results of their past campaigns to have been? If
they have a specific occupation, what are the physical traits that are likely to be
234
Epitaph Online
http://drakkos.co.uk
associated with that occupation? People generally grow into the form they need
for the life they have, so let that guide your mental image.
Additionally, think about what kind of upbringing are they likely to have had.
Aristocrats will bring with them a haughty bearing and a particular way of
behaving to people. NPCs from Forn Lands will have characterizing features that
can be brought out in the description. There are all sorts of connections you can
make between the NPC's background and its observable behaviour.
However, identifying characteristics are usually highly individual, and not generic.
Scars and broken bones add flavour to a description, but if used too freely they
seem awkward and contrived. Remember that a player is often going to see many
dozens of your more generic NPCs wandering around, and it'll look weird if every
one of them has a scar above his or her eye, or if every one has the same broken
bone that has been 'clumsily reset'.
One good way to add some variety is to use the basic structure as a template, and
have the exact details generated randomly. For example, your NPC could get a
random adjective such as ‘Strong', or ‘Thoughtful', or ‘Healthy', and their long
description could change subtly depending on which of these adjectives were
chosen. Additionally, you can slightly modify skills and statistics based on the
adjective, so that characters with the adjective ‘strong' have a couple of extra
points of strength, whereas one that is ‘healthy' has an extra couple of points of
constitution. It doesn't make a huge difference when fighting them, but it does
add a little spice. From the same basic codebase you could generate the following
descriptions:
The thoughtful soldier is a tall, thin man with a scar across his left eye.
Versus:
The strong soldier is a short, heavyset woman with a nose that has obviously
been broken and reset many times.
We'll talk about how to actually achieve this goal in Intermediate LPC, but as with
having randomized room chats it's something you could already do if you were
willing to be a little bit inventive. You'll find many examples of this kind of thing
in the 'live NPCs' in the game if you want to read a little bit of example code.
Your unique NPCs have the advantage that they can be fully described without
having to resort to generality. You can make specific references to exact features,
size, shape – whatever you like. Unique NPCs are usually named, and you can be a
lot more specific in what their history is and how that has affected their
appearance.
Equipment
The clothing that an NPC is wearing shouldn't be mentioned in the long
description. If an NPC is supposed to be wearing something, give them that item
235
Epitaph Online
http://drakkos.co.uk
to wear, but don't mention it otherwise. There are three reasons for this:



The inventory system of the MUD is standard and people expect to see
items displayed in a particular way on an NPC.
If clothing is mentioned, players should be able to look at the clothing. We
have no equivalent of add_item for NPCs.
You can't guarantee the clothes will be there – they may break, or be stolen.
The choice of clothing you give your NPC is important – as with long descriptions,
it can either be something that adds to your NPC or detracts from it. If every
instance of a generic NPC has exactly the same items, it's boring and jarring to
your suspension of disbelief.
We have a clother system for randomly dressing NPCs according to their affluence,
so that not only is every NPC wearing a fairly unique ensemble, it's an ensemble
that they could realistically afford to wear, or an ensemble that they could
reasonably have been expected to scavenge from the city.
For unique NPCs, equipment should be fit for purpose. Make sure the Three Key
Zones are covered – legs, chest and feet. Anything else you provide will enhance
the NPC, but those three should always be provided. If an NPC has no shoes, it's
barefoot. If it has nothing on its legs, it's naked from the waist down, and if it has
nothing on its chest it is naked from the waist up. Those kind of things get
noticed...
In terms of weapons, bear in mind the environment in which the NPC will be
functioning. This is certainly something to consider in Dunglen, which is a
Scottish city and as such not as knee-deep in firearms as Los Angeles. While
people are likely to walk around with their weapons out (because, you know, the
world has ended), those weapons are likely to be improvised for the main part.
You can certainly throw the odd automatic rifle in there, but remember this is a
world where new ammunition is, on the whole, not being made.
One neat touch that adds a great deal of immersion to the game is if an NPC has
random, useless items that hint that the NPC you just murdered has a spouse and
children somewhere. You could very occasionally have an NPC carrying a small
present containing a wooden duck, with the label ‘Happy Fifth Birthday, Sue!'. It
may not even be noticed by most players, but it'll be a nice and unexpected laugh
for the ones paying attention.
Dialog
For NPCs where responsiveness is important, it comes down to dialog. Dialog is
really quite difficult to do properly because at its heart it is a tension between the
convenience of gameplay and the believability of the revelation. It's tremendously
common in games for NPCs to reveal every intimate detail of their life to the most
casual of inquiries, but that doesn't make for a rich dialog. It may be appropriate
for a passive medium such as a book, but it doesn't work in an active environment
like a game.
236
Epitaph Online
http://drakkos.co.uk
With dialog, we are looking to build a genuine opportunity for players to interact
with our game world. If our characters are to be believable, they need motivations.
They need back story, and they need reasons to talk to the player.
This is mostly an issue with quest and flavour NPCs rather than the others, but a
believable character is one that requires us to tease out the information we need
rather than to have it dumped entirely on us as a lengthy monologue.
Dialog in a MUD is closer to that of a film than that of a book – dialog in films is
snappy, direct and delivered as exchanges. It's hardly ever paragraphs of
unsolicited revelation.
So, our first aim in writing dialog is to build up a tree of how revelations will
unfold. Our character may be looking around for something, and that hints to the
player that a quest may be somewhere in the offing. How our NPC responds to
enquiries is how we build the dialog. This is dull:
> ask beefy about lost things
You ask Captain Beefy a question about lost things.
Captain Beefy says: Yes! I have lost my marbles! I lost them down by the Old
Creek yesterday. Could you try to find them for me? I will give you ONE
MILLION DOLLARS!
Where's the interaction here? Where's the drama? You're just typing a thing and
getting candy. People don't talk like that. It's much more interesting, from a game
design perspective, if you make the revelation a part of the interaction:
> you ask Captain Beefy a question about lost things
Captain Beefy says: Hrm? Oh, yes. I lost my marbles. Careless, really.
> you ask Captain Beefy a question about where
Captain Beefy says: Oh... I suspect it was down by the Old Creek yesterday.
> you ask Captain Beefy a question about finding
Captain Beefy says: If you can find them, I will reward you.
> you ask Captain Beefy a question about reward
Captain Beefy says: Oh, how does ONE MILLION DOLLARS sound?
If you want to build a convincing discussion, you can even build in suspicion – you
can make it so that you need to talk the NPC into giving up the goods. Why would
you, some stranger in a world gone to hell, be willing to help someone else
without any obvious gain?
When writing these dialogues, bear in mind your mental picture of the NPC again.
How are they likely to speak? It's very easy to lapse into stereotypes here, so try
not to. Are they direct and to the point? Flowery and poetic? Do they speak
clearly? Mumble? Lose their train of thought? Are they articulate? Do they
communicate mainly in grunts? All of these things help you frame the dialog
appropriately.
237
Epitaph Online
http://drakkos.co.uk
Conclusion
This chapter hasn't really been about the writing that goes into NPCs – it's more
about the thinking that lets you write them. Having a firm idea of why your NPCs
exist, what they are for, and how they are supposed to interact is a great way of
making sure that you're emphasizing the right things in the right areas.
In the next chapter we'll talk specifically about Zombieville and look at the NPCs
we're going to make available, what they are for, and what dialog opportunities
they are going to present.
238
Epitaph Online
http://drakkos.co.uk
Beasts of Zombieville
Introduction
We now return to our regularly scheduled programming – first we talk about a
Thing and then we relate that discussion to our design of Zombieville. In this
chapter we're going to look at the NPCs we are going to make available in our
development, including what they're for, what we need to provide for them, and
how our players will be able to interact with them.
We have a fairly small development, so we can't go overboard with this – we may
have cause to add more if we need to increase the feature density of the village,
but at the moment we're really looking at three:



Our undead headmistress
A survivor in the knitwear shop
Some zombies to kill
Only the first of these are required as part of the development, but having a
survivor gives us a way to provide game clues, game story, and an obvious way of
setting up a quest hub. And if we have a survivor hiding in a knitwear shop, we
require peril – and that is where our zombies come in!
Our NPC Manifesto
The headmistress is a boss NPC, which is an exciting kind of NPC to talk about –
our boss NPC system allows us to set up unique powers, unique loot, and all sorts
of neat features. We'll talk more about this later in this chapter.
Our survivor is a service NPC – he or she will be the quest hub of the area, and will
direct the player to the school with tasks such as 'Oh, I am the librarian of the
school, and I have been going mad thinking of what has happened to my library'.
As the player completes the quest, they can hand them in at the survivor and get
the next in the chain.
Finally, we have our cannon fodder NPCs – the zombies. We have lots of different
kinds of zombies in the game, but we'll just have these being the standard,
normal shamblers – perhaps with a little ornamentation, but there's no need for
us to spend a lot of time coming up with interesting combat mechanics for the –
that's what our headmistress is for.
Together, this creates the NPC complement for our village – it's not much, but
then it's a small village and isn't likely to attract much in the way of activity.
Our task for each of these is to spec out the details – although we can write the
descriptions in advance of actually writing the code, we're not going to do that.
Instead we're going to develop the framework into which each is to fit.
239
Epitaph Online
http://drakkos.co.uk
The Zombies
Let's start off with the simplest NPCs – our cannon fodder. We're going to have
three or four of these wandering around a time, and so our discussion about
randomizing some of the setup becomes relevant. Zombies already get
randomized descriptions, since their various horrible injuries are all appended
onto the description we get:
This child couldn't have been more than about three or four when it turned.
Its tiny body is grey and hardened by the lack of life fluids. Lack of muscle
development makes it a greatly reduced threat, but don't be fooled - any of
these things can be deadly. The right arm of the zombie toddler has been
completely lost leaving tendons to hang limply from the shoulder socket like
dry, decaying noodles.
More is always better in this case though – in addition to the grisly wounds, we
can randomize the first part to reflect the demographic makeup of the city. We
can have old people, young people, fat people, thin people – it won't change the
way that they work in combat or their skill levels – at least, not dramatically.
Ideally they'll even have different short descriptions to add a bit of interest. In
Dunglen as an example you'll find cadgers, mendicants and beggars – they're all
the same NPC, just with different shorts. We can make all of that Come True with
only a tiny little bit of Code-Fu.
To begin with, we'll provide a list of all the possible short descriptions:
({"spinstress", "schoolchild", "farmer"})
Next, let's work up some distinguishing features for their hair:
({"mohawk", "lank hair", " cropped hair"})
This is just the first part of her description, but already we have a mechanism for
distinguishing one zombie from another zombie. The more of these we have, the
more outcomes there are. Let's add in weight and height:
({"fat", "thin", "skeletal", "plump", "well-built"})
({"tall", "short"})
And hair colour:
({"blonde", "brunette", "red", "black"})
And then we combine them together into a standard framework, such as :
This is the shambling corpse of one of the village's $type$. $pronoun$ is a
$height$, $body_type$ person with $colour$ $hair$.
By slotting random elements of our lists into this, we get things like:

This is the shambling corpse of one of the village's spinstresses. She is a
240
Epitaph Online

http://drakkos.co.uk
tall, plump person with blonde cropped hair.
This is the shambling corpse of one of the village's farmers. He is a short,
well-built person with a blonde faux mohawk.
The more entries we add to the list, the more variation there can be. It's a great
way to get extra Value from very little expended effort. We will have to be careful
when we code it so we cal allow for 'a blonde' and 'blonde', but that's okay – we
can do that.
In terms of their outfits, those are going to be related to what kind of NPC they
are. We'll dress farmers differently from schoolchildren, and both will likewise be
dressed differently from spinstresses. They won't be dressed in the usual random
ensemble of a survivor, because they are the reason people scavenge, not
scavengers themselves.
Their load chats are going to be variations on the theme of moaning and
groaning. Not very interesting, but it would be a little strange if they were
incredibly articulate while also being zombies.
In terms of their weapons, they won't have any. They're zombies, and even if they
were carrying weapons before they were infected, they won't be carrying them any
more.
Interaction possibilities revolve around – well, eating brains. They'll be auto
aggressive and will attack any player they see. That means we don't need to
worry very much about add_response. We can save that analytical effort for when
we develop our survivor, who will need to be fully responsive.
The Survivor
Our survivor (let's make it a woman), is the only real conversationalist in the entire
village – we can spin a narrative around her in any way we like, but for the sake of
progressing let's say she's the former librarian of the school. When the infection
struck the village, she found the only place she could safely barricade was the
knitwear shop, where she now lives – she sneaks out in the evening to scavenge
for supplies, but she has been on her own for a long time and as a result has gone
a little crazy.
Her main worry in life is not the zombies, or where her next meal is coming from
– instead, it is the library she left behind, and her old friend the headmistress.
She can't get into the school herself, but she would be very grateful to any player
who could manage that feat.
Since our survivor is a quest NPC, it is hugely important that she is fully
responsive, and that she gives the necessary context for players to know what it is
she requires of them.
She'll probably be dressed mostly in tartan knitwear – the shop will have provided
her clothing requirements, but aside from what was in the flat over the shop she
won't have had much in the way of food or drink. We'll need to make sure that
241
Epitaph Online
http://drakkos.co.uk
her responses and chats explain her current situation.
We can describe this survivor without resorting to randomization – she's a unique
NPC. As such, we should also come up with a name for her so that she's not just
'a survivor'. Something like 'Agnes McKay' would be suitably Scottish for our
requirements, but anything that is thematic would do.
Her chats will centre around the school, the library, and the children that she
abandoned when it all got so bad. She might have stories to tell about what
happened – perhaps she lef the school because the headmistress had gone crazy
and began to canablise the children, or perhaps she simply hadn't gone into work
that day. Perhaps she escaped when it was obvious the infection was spreading.
It's our choice as to why she is where she is. She might say things like:




I will never forgive myself for what I let happen to those children.
Oh, the books – I can't believe I let that happen to the books.
I swore an oath as a librarian, and yet look what I let happen to my library.
Oh, my dear friend – I am praying for your soul.
If she was a direct observant of the scenario in the school, she may be somewhat
hesitant to explain what happened, especially if her friend was the one who
turned on the children. Her responses would then be halting, and ambigious.
> ask agnes a question about school
Agnes McKay says: Oh, it is so terrible, so terrible.
about it.
I try not to think
> ask agnes a question about terrible
Agnes McKay says: Those poor children, but... it was the kindest thing,
perhaps.
> ask agnes a question about kindest
Agnes McKay says: There was no food, no food at all - she, she did what she
had to do. But I couldn't.
> ask agnes a question about had to do
Agnes McKay says: They were all starving, she had to do something - something
until help came along.
> ask agnes a question about something
Agnes McKay says: Some of them were already on the verge - their passing at
least eased the suffering of the others for a little while.
Her dialog should also lead players to realize there is a quest in all of this for
them. To make it a little more believable, we can hint that her obsession about
the library is simply masking a much darker, much more painful obsession.
> ask agnes a question about library
Agnes McKay says: I can't sleep at night, thinking about the state the library
must be in!
> ask agnes a question about state
Agnes McKay says: Oh, I can't go there myself.
grateful if someone could tidy it up for me.
But I would be ever so
> ask agnes a question about tidy
242
Epitaph Online
http://drakkos.co.uk
Agnes McKay says: It's silly, I know. But it does occupy my mind so.
poor books... they didn't stand a chance.
> ask agnes a question about books
Agnes McKay whispers: They were so young.
that.
Those
It shouldn't have happened like
We won't put anything in place to stop people killing her – because our world is
not a nice world and there's no real way we can justify it.
The Headmistress
Our boss NPC opens up a whole range of options that we don't have with other
NPCs – specifically, she will have boss powers and boss loot. She won't be
especially responsive, because – well, zombies are not. But we can give her some
relatively evocative chats. Perhaps, because she is a unique zombie, she has
retained some semblance of speech – that's not consistent with the other zombies
in the game, but that's okay – boss NPCs are unique by their very nature, and it is
game canon that the infection affects different people in different ways (thus, the
variety of zombies that we have available)
The headmistress also, technically, doesn't really need to be clothed – her clothing
could easily have rotted away. However, the thought of the rotting undead
genitals of some sixty year old woman doesn't do a lot for me, so let's make sure
she is – we'll clothe her in some suitable teaching attire.
She also doesn't necessarily need to be described to the last detail – certainly the
imagination of the average player is more than up to the task of filling in the
blanks if we provide them with an intentionally fuzzy overview. Much as how the
best horror movies and books leave the details to the imagination, there is a lot to
be said about maintaining the mystery for our headmistress. It's perfectly okay to
‘talk around' a description without giving away the farm. On the other hand, if we
have suitably horrific imagery in mind, we can use that too:
The infection has not been kind to this poor woman - her body has erupted in
violent, pus-filled boils and blisters that eject greasy fluid like minature
volcanos. Her fingers are like iron claws, hooked and sharpened to impossible
points at the nails.
On the other hand, we can just hint at the horror and let our players furnish the
mental image themselves:
That the headmistress was once woman would never be apparent from her
appearance. The infection that worked through her has shed away every last
trace of humanity, leaving only a core of pure rage and oozing sores.
In the former, we describe her appearance – that's fine if you feel you can do a
good job with describing something that exists entirely in your imagination. In the
second, we describe the essence, not the specifics – we let the player decide
exactly how the headmistress appears in their own mind. I'm not saying this is the
best way to do it, just that it's an option when dealing with things that are entirely
243
Epitaph Online
http://drakkos.co.uk
alien to our life experiences.
Since we want her to be an interesting character, we can take the opportunity to
fill her with some actual chats in Real Human Language. At one end of this, we
can have her being entirely articulate:



They all had it coming!
They were delicious!
Num num num get in ma belly
But while we can exercise some bending of the rules with boss NPCs, it just
doesn't ring true. It's far more likely that she if she can vocalize at all it is only
going to be fractional and broken up with growls and groans and moans. We can
however use this base chats, and then manipulate them to interject growls and
whines randomly in the word. That way, each time she'll say something slightly
different but eventually you'll be able to work out what she's saying. That sounds
pretty neat, so let's make that one of our goals.
Boss Battles
Having decided what our boss NPC looks like, we need to consider what it is that
makes her such a challenging foe. We are not limited here to the set of abilities
that players and other NPCs have – every boss can have entirely unique powers
that do entirely unique things, and it is that flexibility that allows us to create
interesting boss encounters. Boss NPCs are not simply larger, more powerful
zombies – they are different creatures entirely that require specific strategies to
defeat.
We shouldn't stuff too many of these powers into a boss NPC, or the fight
becomes impossible to strategise – we need enough so that people need to react
to a range of situations, but also enough so that the fight is actually manageable.
Three or four powers seem like a reasonable number.
What we also need to consider is the interaction of those powers. Every boss
should have a strategy that can best it, but that strategy shouldn't be trivial. A
boss that does massive amounts of melee damage is rendered trivial by a group
that only uses ranged attacks. Likewise, a boss that is all ranged damage is
messed up by an all melee group. Ideally, you want your powers within a boss to
ensure an interesting set of circumstances.
Forcing players to change their positions mid battle, lest something terrible
happen, is a good way to mix things up. You can have boss powers that change
the position of players for you – you could have (as an offbeat example) a
magnetic zombie that every so often causes everyone with any metal at all on
their person to be moved into melee range. You could have a zombie with a
power that causes ranged ammo in someone's inventory to explode, meaning that
ranged combat becomes risky and indeed if the risk is sufficiently high people
may need to strictly ration their ammunition.
244
Epitaph Online
http://drakkos.co.uk
When designing a boss, think of the following situations first:


How does the boss deal with an all melee group
How does the boss deal with an all ranged group
You need to deal with these two situations – mixed groups are, on the whole,
what you are looking to encourage because that requires a blend of skills. The
simplest way to manage this is to have your boss with some melee special powers
and some ranged special powers. A good third power is one that requires people
to pay attention so that they can react appropriately – a boss that occasionally
gives off a blast of disgusting, poisonous gas might require everyone in melee
range at the time to disengage. Another boss might screech and run violently in a
circle striking at everyone in range. Perhaps the boss does a mental calculation
of everyone's location and offloads a series of chillingly precise bullets at where
everyone was when he checked – that would require *everyone* to switch position
until he was done.
If you can set up interactions between powers too, that creates an interesting
mechanic. Perhaps your NPC will pick up someone in melee range and throw
them at someone else, rendering them prone. If you combine that with some kind
of 'trample' command whereby the boss charges at someone prone at range, then
you create a situation whereby everyone must pay attention, and compensate for
the fight as it goes. In a fight like that, it becomes 'Quick, get everyone up on
their feet!'
We're not looking to create an actual in game fight here, just explore the concepts
– so let's not worry about being especially creative here. We'll have one melee
special power, one ranged special power, and a power that forces players to run
out of melee range or be horribly punished.
Let's have our headmistress still clutching, in her right hand, the steel ruler she
once used, decades ago, to punish naughty children. Throwing that ruler at
someone in ranged combat seems like a reasonably good boss power. We'll make
her do this only if she's not in melee combat, which creates a need for the boss to
have at least one person in melee range.
We've made mention of her pus-filled boils, so let's have another power that
causes one of them to erupt and cover everyone in melee range. The last power
will be that she goes berserk and does quadruple damage to anyone drenched in
her pus. That creates a situation whereby melee warriors need to keep an eye on
the boss or risk being killed very quickly indeed.
Her third power will be based on her steely clawed fingers, and she will use them
to snatch a weapon from someone she's not currently focusing on in melee range.
She'll then throw this weapon at someone in range.
That's an interesting suite of powers – she is a boss that has most pull in melee
combat, which means that players will have most of their luck trying to engage
her, but if you stay out of her melee range her metal ruler comes into play. Also,
if you are the only person in melee combat with her (if you are tanking her, in
other words), you're most likely going to have your weapon snatched off of you
245
Epitaph Online
http://drakkos.co.uk
which will undoubtedly cause problems.
On the other hand, if you all engage her in melee combat it's almost guaranteed
that she'll spray her pus over you and you'll take hugely increased damage while
she is berserk. The more people who are in combat, the less chance there is that
everyone will react in time.
What makes this an appropriate boss encounter is that there are counter
strategies all ready for players –if you are the solo melee tank, then you either
want the skills to be able to avoid that disarming special, or you want to be an
unarmed tank which negates the problem. Player skill is encouraged by ensuring
that there are timing imperatives in the battle – you need to get out of melee
range when she's about to erupt, but if you stay out of melee range too long one
of the people in range is going to take a beating from that ruler.
When developing an interesting boss encounter, the best thing to do is let people
try it out. Often they will come up with strategies you certainly did not envisage,
and the fight is either rendered trivial, or much more interesting, as a result of
that insight.
Conclusion
Really, our cannon fodder NPCs are not very exciting – they're there because they
have to be to justify the situation and also to add the peril we like to place our
players within. Most of our development effort will be expended, sensibly, onto
our boss NPC and our quest hub survivor.
Both of these NPCs will allow us to explore a considerable amount about how
Epitaph manages some of the complexity of developing interesting content, which
is largely the reason that we (and by 'we', I mean 'I') have taken the design
decisions we have. As with anything else in this particular book, there were a
dozen other directions we could have gone, and interested creators should
definitely spend time brainstorming what else could have been done here. Really,
we just needed to pick one, and this is ours.
246
Epitaph Online
http://drakkos.co.uk
Feature Development
Introduction
We've now spoken about our quests, our rooms, and our NPCs. The next step is to
look at the two features that mark out our development – the school, and the
shop. The library in particular is a unique feature that is not supported in any of
the MUD base inheritables, and so we're going to have to think very carefully
about how it is to be implemented.
It's also a good idea to think about the shop before we put it in place, to make
sure we can actually have it produce a range of interesting and unique
merchandise. If we can't do that, we can rethink our plan as necessary. Plans are
malleable after all, and we put all this thinking in before we start coding so that
we can completely change what we're aiming for with no cost.
A Local Shop For Local People
Let's start with the easy one first – the shop. Producing items may not be the most
inspiring of tasks (although they add a huge amount of richness to the game), but
if we are adding a new shop it's a cheat if we don't make its stock at least partly
original. Luckily, Roundworld is the best inspiration of all for items because
whatever we may be developing, we will find examples on The Interwebs.
Researching for inspiration is a big part of putting together a unique development
– you don't need to copy things exactly, but they can give you the starting point
you need, and still ensure that your items are grounded in reality.
For a shop to be worth its title as a ‘feature', it should have a reasonable amount
of unique stock – the more the better, although if there is too much it may be
sensible to spread it over multiple stores as to more accurately reflect the feature
density of your area. It's better for two stores to have a reasonable amount of
stock than for one shop to be oversupplied and another to be barren. It's also
better for there to be two reasonably stocked shops over one that has a huge
stock list that cannot be easily navigated.
I won't give specific URLs for inspiration here, I'll just recommend you fire up
Google and look around for a bit. Looking for 'tartan knitwear' outfits will fill your
browser with possibilities, and most are even safe for viewing in work. Others
aren't, and if you find any of these you should send them to me so that I can...
uh... properly protect you from them. Yes.
Your first task is to decide on exactly what the remit of the shop is – is it purely
for knitwear? Maybe it's also doing a side trade in Scottish staples like canned
haggis and shortbread. Just because we decided on a type of inventory at the
start doesn't mean we can't refine it as we go along.
247
Epitaph Online
http://drakkos.co.uk
Once you have decided on what is to be stocked, think about what is going to be
available. You should aim for a scavenge list of roughly eight to ten items, which
doesn't give you an awful lot to work with if your remit is too large. Four
categories of items will give you around two of each – that doesn't permit a wide
range of possibilities, but it would be possible to buy an ‘outfit' rather than having
to mix and match.
Let's make it easy for ourselves and have our shop selling two kinds of items –
hats and skirts. That means we need to provide four or five examples of each –
that's a sufficient range to provide interest, and yet not so ambitious that we have
little hope of hitting the target. It's a nice balance between the two. We'll also add
a couple of schematics here too, because those are always worthwhile for players
to find, even if they are just for a certain kind of knitted hat.
The Library
Now we come to the Meat and Taters of the development – our school. While we
already know this is a questing hub, we also need to make it interesting on its
own behalf since that was its original intention. It doesn't need to be bizarrely
elaborate, it just needs to add to the richness of the game.
We already know of the 'sorting the library' quest, but once it has been sorted it
would be nice if people could make use of it to find out information – this
information could be about Zombieville, about the Headmistress, about Scotland,
or... even better... about other players. However, we don't want to have to write
hundreds of books, even fake books of a paragraph each. It's Much Too Much to
do anything like that.
What would be best is if we had some kind of index card system listing topics,
and allow the player to simply ‘research' the topics on that list. Doing so would
give a random piece of information about the topic at hand. For example, we
could have a topic about the school, and provide a range of snippets to the player
who chooses to research that topic:


You research the school in the library, and find out that it was where a
certain pre-infection rock star first learned how to play guitar. How
interesting!
You research the headmistress in the library. You find her in an edition of
Who's Who, which indicates that she was actual minor royalty. The more
you know, eh?
Each of the snippets can lead on to other snippets, making the library a little
puzzle of its own. Indeed, it could even be a quest to piece together some great
mystery, but making such a quest dynamic, randomly generated and yet still
possible to solve is a tremendous challenge and one that we will not set for
ourselves for the time being.
The real meat of such a feature would be a way to research players, because that
adds a measure of interest to socialisers as well as explorers. Obviously we don't
248
Epitaph Online
http://drakkos.co.uk
want the information given to be overly powerful (no revealing of skills, or stats,
or level), or freely available elsewhere (projects, plans, refers, etc). We're not
limited to fact though, there's nothing to stop us making up slanderous lies about
players provided we do it in a humorous enough way.
First though, we don't want every player to appear in the library – having your
name mentioned should be a mark of honour, not just a random occurrence. It's
unlikely that a brand new player with no renown would appear in anyone's library.
Ideally it would be possible for someone to be entered into the library as a reward
for some other achievement. Perhaps those who managed to ascend to see the
Headmistress get their name recorded in the library for others to research. In that
way, it becomes another way in which a player can make an impact on the game
world.
If we do it that way, we shouldn't have their information random, because it would
be possible for someone to simply research the same person over and over again
to find out the full range of possibilities. Instead, their information should be
generated randomly when the necessary steps have been achieved, and that same
information returned each time that person is researched.
The information should ideally be Funny, because that is an incentive in itself for
people to research other players. And it should be obvious where it comes from.
Maybe once the Headmistress has been defeated, the librarian pops in and makes
a note on in a notebook:
You research Drakkos in the library, and find a neatly handwritten report in
one of the librarian's notebooks. ‘I met Drakkos today. He's an odd little
man, and smelled vaguely of donkeys. He grunted something unintelligible,
turned around, and promptly fell down the stairs. I hope he comes back some
day'
As with the randomly generated long descriptions of our zombies, we can adopt a
similar system for the library. First we add some descriptions:
({"odd little", "small-minded", "confused", "smelly"});
We can add the gender to that when the information is entered to make it correct.
We then add a little insult:
({"smelled vaguely of donkeys", "walked with a stutter",
"talked with a squint"})
Then in a similar vein, we setup the action the player took, and the result. We then
end with a little afterthought.
We could even have several of these templates into which we slot the information
– after a short time a single template becomes so familiar you can see the ‘Oz
behind the curtain', and by mixing it up a bit you can postpone that moment so
that it all appears Magical for longer.
We should also consider our library as an opportunity for some useful taskmaster
increases – any feature like this is going to involve some measure of skill in
249
Epitaph Online
http://drakkos.co.uk
teasing out useful answers. It doesn't have to be anything especially useful –
something in the mental tree is a good possibility. It's unlikely taskmaster
chances in these skills will draw in the Punters, but it's a nice bonus for those who
want to explore the little treats we have in store for them.
To further tie the library into our plans, we could liberally sprinkle a few quest
hints here and there. They don't even have to relate to this village, but if you are
going to hint at quests elsewhere make sure you clear it with the appropriate
authorities. In game quest hints are great because they are entirely in theme in a
way that the web interface can never hope to be, and provide a cross-polination of
developments that makes our game look much more coherent.
Feature Creep
Now we add a word of warning to our discussion of features – the importance of
restraint. There is a reason why we put a plan in place before we start coding – it's
to set the constraints of our project at something reasonable and achievable
within a specific time frame. What tends to happen as you develop is that you
think to yourself ‘Oh, and I will do this! And this! And wouldn't it be cool if I did
that too?'
This is common, and entirely understandable. The problem is this – you will
always have more ideas than you will have time, and if you continually get
distracted by new ideas, you will never get around to finishing your project.
There's a phrase for this – feature creep. As a development progresses, more
features start making their way into your todo list.
It's incredibly important to stick to a solid plan. I don't mean you have to be
constrained by it – you can change things around as and when you need to.
However, you shouldn't add things to it on a whim. Every addition should be on
the basis of you fixing some kind of issue that was encountered during the
development. There will be plenty of time when the project is completed for you
to add new features.
I talk again abot Discworld MUD because that has been in operation for nearly
twenty years and as such is full of excellent examples of this problem. There are
dozens of projects on Discworld that were delayed by years because of feature
creep. The original Lancre Castle was started way back in 1997, and entered
playtesting in 2008. During that time it expanded from a fairly reasonably
achievable project to a vast, lumbering monolith – all due to feature creep. As new
creators came on board the project, they brought their own ideas and put forward
their own plans for how the castle should be. I'm not saying that these ideas were
bad, but they were expanding a development far, far beyond its original scope.
Eleven years is an awfully long time for an area to be under development. The
Milords and Miladies Wedding Pavilion was started in 1998, and took until 2004
before it went into the game.
Long, prolonged developments are a death march for a domain – they are
indicative of a thing called the Concorde Effect, or the Sunk Cost Effect, whereby
250
Epitaph Online
http://drakkos.co.uk
the amount of effort invested in the past is used as a justification for future effort
despite the fact that is an unsound judgement. They also proves Brook's Law adding manpower to a late software project makes it later.
Throwing creators at a death-march project exacerbates burn-out problems. It's
difficult to remain motivated when you're working on a development that has
been ongoing for years and years, with no sign that you're going to see any of
your code in the game any time soon. Best avoid this problem entirely by setting
yourself realistic and achievable goals.
New feature can be added into your development, but leave them until the end.
Develop according to your plan and only once that plan has been achieved do you
consider what else needs to be incorporated to make your development as good
as it can be. There are a few examples of this in our plan for Zombieville – the
village is a little sparse, and could benefit from having another feature. There is
scope for another quest or two in terms of the backstory we've set up. The
problem is if we plan all that to begin with, we'll never get finished. We plan
something we can do, and then at the end when we have a finished (albeit
Spartan) development we can extend it.
As a new creator, you're not going to know what your limits are. Unless you have a
lot of experience with coding, you just aren't going to be able to estimate how
long things are going to take you to write. Once you've added something to your
plan, you are committing yourself to writing it – especially if your plan has been
approved by your domain administration. Many creators find it difficult to say
‘Actually, no – I'm not going to have that feature after all'. The trend is almost
universally for features to be added, not subtracted.
This brings us to the second important point – you should be prepared to make
cuts in your plan if you find you were originally over-ambitious. This is something
you should discuss with your domain administration, but we prefer modest but
completed projects over ambitious but never-ending developments. Features can
always be added later.
Exactly how long a development will take will vary from creator to creator – I have
no way of explaining how you will estimate it other than ‘it comes with
experience'. Ideally a project will be relatively short, giving you a reasonable
turnaround from project to project. If there is a particular feature that is going to
cause problems with your estimation, then consider recasting the feature. You can
scale it back, or remove it entirely and replace it with something more achievable.
This is not an admission of failure, but instead a mature and responsible attitude
to meeting your creator obligations. You need to repeat this like a mantra – it is
not an admission of failure.
The ideal approach then is to be conservative in your plans, finish what you say
you will develop (and no more), and then later direct new features to plug gaps
you have identified. This is the Path To Success, Enlightenment, and Love.
251
Epitaph Online
http://drakkos.co.uk
Conclusion
A good area has unique features to tempt players to explore. Our development
will have two – a clothing store full of unique and interesting tartan garments, and
a library chock full of quests, narrative, and even a few jokes. It's common to look
at these two features and think ‘No, it's not enough' when we're developing In
Anger. That kind of thinking leads to the danger of feature creep, and we should
avoid that when possible.
Think of this as a Law of Nature – plan the plan, but keep it reasonable. Develop
that plan as is, making adjustments only on the basis of what you can realistically
achieve. Once your development plan has been completed, and the project has
been coded, do a feature density analysis and decide what additional features are
needed to make your project fit for the game. Only by being strict with this can
you avoid turning your project into yet another Nightmare March for your domain.
252
Epitaph Online
http://drakkos.co.uk
Finishing Up
Introduction
We've pretty much planned the hell out of our little village, and we are coming to
an end of this set of material. The last thing we need to talk about in terms of our
specific development is its integration into the wider context of Epitaph. This may
be easy, or confusingly difficult, and that will depend on how well defined your
domain is in terms of geography and infrastructure. Adding a street to Dunglen is
easier than adding a village in the outskirts of Scotland, for an example of what I
mean by this. Dunglen has a structure we can add things to – outside of the city,
Scotland is undefined.
This is largely something that will be a problem for your domain as a whole rather
than a problem for you specifically, but we'll talk about the issues that are likely
to be encountered so that you are prepared for the discussions to follow.
Integration Mechanisms
It is almost never a specific intention to make an area difficult to reach. It's
usually a consequence of geographical remoteness. Perhaps the areas linking two
other areas don't exist and so it doesn't make sense. Perhaps there is no obvious
way in which a development could be reached (maybe it's on an island and there
are no boats in the game). That kind of thing is more common than you might
expect.
While it's always possible to have artificial means of access to an area (you could
have an NPC who will teleport you to a location if you pay him enough dog food),
these are artificial and unsatisfying for the majority of locations.
The ideal situation is that there is a placeholder already available for your
development. You could be walking around Dunglen and see a new street, but not
be able to enter it because it is 'blocked by rubble' or some such. That's a
placeholder – it's where your street will go when it's finished, and until it is it is
blocked by rubble.
For areas that are not covered by terrains, this integration has to be done in a
different way. We either need to find some kind of relatively believable mechanic
(perhaps a helicopter touches down at a certain time in a certain room every day,
and if you get on it will take you to a different place). For those players without
access to easy transportation though they are an exercise in frustration, turning
active play into a Waiting Game - you wait for the helicopter (blink and you'll miss
it), then you wait for the helicopter to arrive at the location in which you are
interested. Along the way, you kill time.
253
Epitaph Online
http://drakkos.co.uk
Blending these approaches is always going to give the highest amount of
satisfaction - the more accessible your area, the more likely people are to want to
visit it, and that's in your best interest as a creator.
Advertisement
Having completed a development, it's only natural that you should want to tell
people to go visit it. There are several ways to do this - the simplest way is to do it
Out Of Theme, such as posting on the boards, making a note on the recent
developments blog or, if the area is large enough, making a news announcement.
There is nothing wrong with any of these approaches, but also consider making
the news available in the game.
Creators can, for example, seed rumours through the game, and these rumours
become available when people search up appropriate objects. You could write a
small newspaper article about the village of Zombieville, and when someone finds
such a newspaper they will say 'Oh, hrm! That is new!'
What you're looking to do is create a buzz, not necessarily signpost everything you're not a tour-guide. Your advertisements also shouldn't undermine the thrill of
exploration that people get out of finding something new. This is an example of a
bad advertisement:
Come visit the new area in the Support domain! It's called Zombieville, and
you'll find it by going east, north, east, west, south, and west from the
village of Deadville! It's a rural village. It has a school and some quests,
and a shop with some things! You should check out the library especially,
because it's full of Red Hot Peril!
The problem with this is that you've removed all of the mystery - those who don't
find your advertisement convincing just won't visit, and those who like to be
surprised will find nothing of interest left to explore. What's much better is if you
hint at the great things to come for those who wish to spend a little time
investigating. An example of survivor graffiti that might work:
Don't go to the village of Zombieville - they're all dead there. I tried to
search the school, and only barely got out with my life. I don't know WHAT
that fucking thing was.
People are going to assume that there are new quests when an area is introduced
- those who don't assume will soon be made aware by the increase in available
quest points. Saying it's a village is going to also provide some expectations - you
don't have to remove the anticipation by telling people everything they should
look out for. We also mentioned that there is danger in the school, and that will
imply a boss to people who are paying attention.
We are criticised occasionally by players (and other creators) for our often cryptic
news posts, but I personally favour them as being much more in theme with our
development sensibilities. While I don't endorse cryptic posts relating to gameplay
issues (such as rebalancing of weapons, skills, removal of spells or such), I do feel
that they enhance the experience for in-game developments. However, this does
254
Epitaph Online
http://drakkos.co.uk
not extend to the recent developments blog - posts there should be clear and
unambiguous, otherwise it's not fulfilling its designed role.
As with anything though, it has to be done properly. A news post saying 'There's a
new thing' doesn't do anyone any favours. First, you need to give a fair idea of
what the new thing is. You also need to give a hint of some kind as to where to
find it - that can be built into the style of the writing. Imagine if you were
advertising a makeshift city full of teenagers:
Lol, u shld come 2 our new city, its better than urs, rofl
(See, I may be old but don't pretend I don't know how to jam with the kids).
The first hint is in the style of writing - 'Hrm, textspeak. That leads implicitly to a
clue in where the development is situated - it's situated somewhere where there
are a lot of children. It's a city too, so it's probably quite big.
You'd be forgiven though for not making use of in-theme postings, since the most
vocal minority of our players will complain bitterly. Still, we're allowed to have fun
as well!
Bookkeeping
A new area always causes problems, no matter how long it is developed and how
thoroughly it is tested. The more people exploring an area, the more people there
are to do things that you wouldn't have dreamed of in a million years. Things that
seemed like a good idea to code at 3am in the morning turn out to be really quite
bizarre in the cold light of day. Everyone has different things that they think
would be cool, or funny, or whatever, and it often differs wildly from person to
person. Imagine that spread over hundreds of players. Everyone will have
different issues:
I tried to chew the delicious chairs here, and I got a runtime.
Some of the problems will be technical in nature, but a lot will simply be because
of the novelty of the area. As such, upon completing any development you should
do what you can to minimise the burden on the liaisons who will be in the frontline fielding questions.
The first step in this is to introduce your development with the Five Dubyas - this
is a post on the Game board covering five pieces of information:
255
Epitaph Online
Dubya
What
Who
When
Why
Where
http://drakkos.co.uk
Information to Give
What is the new thing that has been added, or what is the old
thing that has been changed?
Who was responsible for the development (so liaisons know who
to contact for resolution of queries, or to whom they should
direct the undoubtedly lavish praise)
When is the change scheduled to go live.
Why was the new thing introduced, or why was the old thing
changed?
How do people get to it, if it's a new area?
This gives a quick, at a glance overview of some of the most important questions
for others to be able to answer, but by itself its not enough detail. Sadly, we are
all better at doing this in theory than we are in practise, but you should also
thoroughly document each part of the system that is likely to cause questions. For
example, people aren't going to ask much about the shop we've provided because
shops are a common feature across the game. They will however have questions
about our library, and the quests. Those questions will be directed, in the main, to
those creators foolish enough to be visible, and correspondingly few of them will
be experts on the code you just put in the game.
The information you write for this is Creator Only, so you can feel free to include
useful functions, exact solutions, difficulty levels - all of that information is useful
for a creator trying to track down player problems. Ideally you should be doing
this as you go along - if you are keeping to your plan, you'll already have a fair
amount written up to draw from.
Where you choose to make the information available is up to you - there are
several systems in place of which you could choose to make use. We have a
developer wiki which is an excellent place to store such documention, and we also
have the Oracle system. Ideally, the information will be available in both,
although in subtly different forms.
As I say, we are all better at talking about this than doing it, but please make an
effort to provide this information. It will make life so much easier for everyone you won't have to keep answering the same questions for different liaisons,
liaisons won't need to keep hassling you for information, and anyone caught in
the crossfire (such as a lord without the good sense to be invisible) will be able to
look up the answer to any queries without needing to really pay attention.
Everyone wins!
In terms of the information you provide for players, you should also consider what
hints you are going to make available through the quest hint system. This is
something you should decide upon before you start coding, because it will inform
your development if you know what information players are likely to possess.
Every room that has unique syntax should also come with a helpfile - this doesn't
cover syntax for quests, but it does cover syntax for general functionality. Our
helpfile should explain how the referencing system works and what the
commands are to use it. Our help system is quite powerful, and one of the most
256
Epitaph Online
http://drakkos.co.uk
useful frameworks we have for reducing player frustration.
Lessons Learned
Without reflection, we are little more than apes. Part of your 'wind up' process
should be to compile, either mentally or more formally, a list of the lessons you
have learned when putting together your development. These should be corrective
rather than congratulatory, although if you feel you've done a good job you
should indeed take pride in that. More important than your success though is
where you feel you either failed to meet your requirements or struggled with a
piece of code or concept.
These notes should be for your own personal use, but they can be invaluable in
working out how best to support yourself in future. Make a note of all the things
you found difficult, and all the things you got working but only after huge trial
and error. Especially take note of things that work, but you're not sure you did the
right way. All of these are tremendously valuable things to reflect on and ask
people about - it's how you make the big leaps in your learning.
A lesson's learned list might look something like this:


Don't copy and paste add_items, use an inherit.
Get the skeleton of the area working after you've written a placeholder
inherit.
While these are for your own personal use, they start to expand into a general
philosophy for how you should go about developing projects. They're your
Documented War Stories. Make a note of them, add the result of your reflection,
and use that as the basis to make your next project even better and run even
smoother!
As time goes by, you'll find that other people can start to learn from the lessons
you have learned - it can serve as a useful resource for anyone starting out, and
with a bit of editing you may even want to make the results of your reflection
available to other creators. That's how you progress from being a Frightened and
Young New Creator to being an old, grizzled and practically senile oldbie! For
context, pretty much everything I have spoken about in this and related texts is
the result of my own lessons learned.
Conclusion
There are certain things you need to do to 'wrap up' your development and put it
into the game as a finished product. You need to consider how your development
is to be made accessible, and you need to make the necessary information
available to other creators. You should really be planning this as you plan the
details of the area to ensure you don't have a Horrible Writing Task left to do
when you should be celebrating your achievement.
257
Epitaph Online
http://drakkos.co.uk
Additionally, you should spend some time reflecting on what went wrong, what
went right, and more importantly why. This will serve as the basis for ensuring
that the next project you develop is easier to get a handle on, and as time goes by
you'll find yourself internalising these lessons in such a way as to make project
development as smooth and painless as possible for you.
258
Epitaph Online
http://drakkos.co.uk
Lessons Learned
Introduction
Now that we have extensively planned out our development, let's reflect on what
we have learned!
Or not. Sadly, we can't really do that because we haven't yet started to develop
our area. Developing is when when we'll see where our plans will need trimmed,
expanded and reigned in. Instead, let's reflect on the lessons learned of other
people who have been through this process before, and what they've taken away
from the experience.
What you take away from these lessons is up to you - I would hope at least they'd
make you think before taking certain decisions with your code.
Death March Projects
Death March projects are those that have killed creators – every game such as
ours has at least a few of these, some more than others. Either their scope is so
ridiculously large that there's no realistic chance of it being completed in any
reasonable space of time, or their feature-set is so complex that it is outwith the
grasp of most creators. Death March Projects often overlap with feature creep
projects because of the sheer throughput of creators, each adding to the plan and
then leaving. The net result of adding a creator is that the end point of the
development gets further away.
Hopefully your project won't be a death march project, but if it is don't give up
hope. Instead, learn from the lessons of those who have gone before you. Don't
add to the project – instead, recast it entirely. Look through what code has been
completed, and figure out the easiest way to take it from development to a
finished project. This may mean closing off substantial chunks of it so it can be
developed and released incrementally. Breaking a project off into three or four
subprojects that can be developed independently and then released is the easiest
way to turn a Death March Project into a project for which you are the celebrated
hero who conquered it.
The problem with such projects is that the plans are always very cool. You read
whatever design documents there were, maybe view the board archives, and read
the wiki. ‘Wow!', you think, ‘This will be awesome'. But it won't be, because you'll
be crushed like everyone else who tried to get a handle on it. Make a realistic
assessment of how long it'll take to bring the project into playtesting, and then
ask yourself ‘Do I want to be spending that much time before I see any code in the
game?'. If the answer is no, then be aggressive – cut everything that is peripheral
to the main theme of the development. Check with your domain administration
259
Epitaph Online
http://drakkos.co.uk
while doing this – they may have opinions on which features are entirely
expendable.
Sometimes you'll find that the code for your Death March Project is actually
impossible to salvage – you're left with a horribly ambitious plan and no code! In
such cases, with the blessing of your domain administration, you should simply
start over. Don't feel as if you are bound to the plans of creators who have gone
before – they had their chance.
A Death March Project is a challenge, but one that everyone is capable of meeting
if they tackle it appropriately. Usually the projects are defining features for a
domain – something so integral in the books that lord after lord has felt
compelled to continue development. As such, you're usually getting a pretty sweet
brief for your project, it's just the cruft you are inheriting that is the problem. Just
remember a development plan is not a binding contract, you are allowed to
deviate from it, and scrap it entirely if it's unworkable.
Realism over Fun
Ho boy, are we all guilty of this one. At one point or another almost everyone has
said something like ‘I'm going to do this because it's more realistic than this other
way of doing it'. This is a warning sign – realism should never be the reason why
you implement or change something. We're game developers, and Epitaph is not a
life simulator. We are unashamedly, openly and without apology a game first and
a world second.
If you want to make a change, make it on the basis of gameplay, or balance
considerations. If your sole answer to being asked ‘Why do it that way' is a
variation on the theme of realism, step back, take a deep breath, and don't do it.
Realism is largely a code-word for ‘make things fiddly', and we gain no extra game
from that.
There are many things that are realistic, but in place for a different reason.
Sometimes you want to incorporate a little more realism to justify a change you
want to make for, say, game balance. That is fine – it is realism for the sake of
realism that must be avoided at all costs.
Consider the streamlined mechanics of something like Warcraft with regards to,
say, swimming to those in games with 'realistic' swimming systems. In Warcraft,
you don't drown and lose all your stuff. In a 'realistic 'system, that's entirely
possible. One is the emphasis of playability over realism, the other is the reverse.
Realism should always come as an added benefit for a change to gameplay, not as
a reason in and of itself. We have compulsory eating and drinking here. We don't
have it for realism, we have it because it is a game mechanic that accomplishes a
number of goals we have for the game:


The need to scavenge
The need to play rather than idle
260
Epitaph Online

http://drakkos.co.uk
The sense of a world where basic survivial is not a given.
Remember what alchemical process we are here to facilitate – time goes in, fun
comes out. If your changes for the sake of realism don't improve that, then don't
put them in.
Randomness Is Not Challenging
Every now and again someone develops something where survival or success is
based on the roll of a dice. You either get lucky or you don't... that's just the way
of it. While there are a few self-contained games in which chance is the sole driver
for winning and losing, they are very much the minority. Randomness does not
offer the potential for interesting game decisions.
Imagine a situation whereby you must roll a one hundred sided dice. You win the
game if you roll a 47. If you want to win the game, you need to roll that dice, and
keep rolling it, until you get a 47. You might never roll a 47– you may spend an
entire gaming session rolling the damn thing and never have it appear. That's not
a challenging game, it's a random game.
A challenge is something against which you can pit your own skills and abilities –
while randomness is often a factor in this, it should never be the sole determiner.
Careful consideration and planning should allow you to change the odds in your
favour. To make something like the dice-rolling above challenging, it needs to be
responsive to strategizing. If you could change the odds by rolling in a certain
way, or by blowing on the dice, or doing – well, anything that allows for some
measure of skill to adjust the outcome – that's moving in the right direction.
Complexity is not King
The principle of ‘Keep it Simple, Stupid' is substantially true for Epitaph. Many
people confuse complexity with quality, and think that providing complicated,
intricate systems is the best way to implement new game features. It's possible to
have depth of expression even with simple systems – indeed, a good design goal
is to make systems as simple as possible, but inter-related. That leads to
emergent game behaviour which is always more interesting than simple
complexity.
Take our material manipulation system for example – it's based on tremendously
simple rule. Consider working with metals - smelt a nugget of iron and you will
get a bar of iron. The quality of that bar of iron depends on the quality of the
nugget and your own skills. That is all there is to smelting, but because both the
nugget and the iron bar are parts of other systems (gathering and crafting
respectively), then there are interesting game decisions that emerge as a result.
When adding a substantial new feature, you're not looking to implement it
realistically – instead, you want the essence of the activity – enough that you can
genuinely feel part of the fun, but not so much that you are overwhelmed with
261
Epitaph Online
http://drakkos.co.uk
tedious micromanagement.
Look at how we are implementing our library – a single research command that
abstracts the task. We could just as easily have the player check each book in
turn, saying ‘You don't find anything about tedium in that book' until they find
something interesting, but that's not fun – it's micromanagement.
If you are adding systems into the game, consider how they could be streamlined
– lose the jagged edges, and keep the core. You can always add complexity later,
it's not always so easy to remove it.
Complaints Are Not Representative
If you handed everyone in the world a bag of money, you'd get people
complaining they had nowhere to put it. It's important to realize you are not going
to please everyone, and despite the effort you have put into your development
people are going to be unkind about it. There's not much I can say about that, you
just have to take it in stride.
The danger comes in when you take the complaints at face value. Complaints are
not generated according to a vote amongst all players, and they certainly aren't
quorate. There is a (probably apocryphal) story about President Harry Truman,
who growing frustrated by his growing pile of hate-mail turned to his wife and
said ‘Why is it only sons of bitches know how to lick a stamp?'
It's important not to think that complaints are representative – usually they are
not, they are usually from the vocal minority rather than the silent majority. That
doesn't mean you should ignore them, but you certainly shouldn't ascribe them
with any great truth value unless widely corroborated. Not being able to
contextualise complaints is likely to grind you down more quickly than anything
else. You will find, despite your being a volunteer developer on a completely free
gaming environment that some people will still not appreciate the effort you have
put into the game (that is, assuming you've been putting effort into the game).
These are often the professional malcontents who derive most of their joy in the
game from putting down others. In cases where you feel that you are being
unfairly criticised, you can always remind people of our guarantee – if not
completely satisfied, we will refund all of the money they paid us.
Know When to Step Back
On encountering an intractable problem, there are two courses of action:


Bang your head against it until you solve it.
Go away and come back later.
You should know that the latter of these is not the ‘cheating option'. Sometimes it
takes a while for your subconscious mind to tick over and provide you with the
262
Epitaph Online
http://drakkos.co.uk
solution. I can't count the number of times I've had a flash of inspiration in the
shower, while driving, or just sitting and reading something else. Your mind is
always working away, and avoiding consciously focusing on a problem means you
can view it with a fresh eye when you come back to it.
Don't feel that you have to keep struggling until you find the answer. Know when
to take a step back and let the answer come to you naturally. You'll be happier,
you'll be less stressed, and you'll enjoy yourself more. Which brings us to the
most important lesson that everyone should learn...
For God's Sake, Have Fun
Don't take it all too seriously – you didn't sign up as a creator to not have fun, you
signed up to experience fun from a different perspective. Sure, there are lots of
rules and guidelines and huge amounts of stuff to learn, but don't lose
perspective – it's all about having fun. There's a lot of fun to be had from bringing
something new into the game and having people enjoy it, and we sometimes lose
sight of that when getting frustrated with development setbacks. It's all part of
the learning experience.
In Software Engineering, there is a stereotype of the ‘guy in the room' –
tremendously hard working and dedicated, but not actually part of the team.
Don't be that guy (or gal). We do suggest quite strongly that you keep your head
down for a while until you get a feel for the social environment, but once you feel
you have a handle on how creator conversations actually work, then join in! The
social context in Epitaph is one of the most rewarding parts of being a creator –
there are people on Epitaph who have become very real and genuine friends of
mine, and one of the things that keeps most of us here are the people we know.
You're part of that community, and much of your support and enjoyment will
come from participation.
Don't forget to have fun – if you're feeling unproductive, play for a bit – you don't
want to lose touch with the game. If one part of your development isn't going the
way you want it to, change tack for a while to something more fun.
If you remain stressed, or unproductive, or just generally fatigued then take that
for the warning sign it is. It may be a hint that you need to step back from the
MUD for a while – talk to your domain leader if this is the case. Every one of us
knows that Real Life Comes First, and we'd all much rather someone took a break
than burnt out. If you are just not having fun here, then something is wrong and it
needs to be addressed.
The reason we are all here is to have fun. That's the most important lesson you
can take away with you from this chapter.
263
Epitaph Online
http://drakkos.co.uk
Conclusion
We've reached the end of our journey, save for some concluding remarks in the
next chapter. I hope you found it as enjoyable to read as I did to write. Ideally you
found it more enjoyable!
Life is about learning – everything you do provides you with a lesson, and coding
on Epitaph is no different. You should be mindful of the lessons you can learn
from your development because they're all part of what we refer to as
‘experience'. That's all experience is after all – it's what you learned from the
mistakes you made.
264
Epitaph Online
http://drakkos.co.uk
Final Thoughts
Introduction
Cor, do you remember when we were wide-eyed and innocent, knowing only that
we were going to develop something but with no firm plans as to what? Now look
at us – we have a firm handle on exactly what shape our development is going to
take, and all that's left for us to do is actually code it. Still, as a wise man once
said 'Programming is the last mile of game development'. While a lot of people
plan as they code, there is much to be said for simply sitting down and thinking
through what it is you're going to do.
In this chapter we're going to talk about where you go next with your training.
What Happens Now?
Certainly your next step is easy – you progress on to Intermediate LPC, in which
we'll actually start coding all of the stuff we've been planning. It's quite a complex
development involving handlers, inherits and quests – by the end of that
particular set of material you'll find yourself more than capable of handling most
of the tasks that life as a creator may throw at you.
You're probably going to have a project of your own you will be working on – the
best thing to do is apply all the stuff we have talked about to designing out your
own development. It's one thing to follow through some worked examples when
someone else is doing the thinking, it's another entirely to put your own mind to
such a plan.
We've talked a lot about game design in terms of how it specifically relates to the
MUD – as an environment it throws up a lot of interesting challenges that aren't
present in other such games. As a developer base we have been learning it all as
we go along, and other people may very well hold different views on these topics
than I do. You should talk to other people about the things in this material to get
a wider perspective than just mine.
Keeping Our Players Happy
Game development is a process of judgments. Not everyone is going to agree with
your choices, and not every player is going to find what you offer compelling.
That's okay – as long as we provide something for everyone as we go along, we
don't have to make everyone happy within a single development. If we did, our
task would rapidly become impossible.
At the core though, what we're looking to do is improve the efficiency of a simple
265
Epitaph Online
http://drakkos.co.uk
process. Our players invest time, we invest time, and what comes out at the end is
fun for both. Sometimes we need to reduce the fun of one group of players to
enhance the fun of a larger group, or to ensure that fun is had for a longer period
of time. All of the most unpopular ‘balancing' decisions that have been taken over
the years have been in service of this overall goal. While their effectiveness can be
argued, what we should remember at all times is that each and every one of us is
committed to making the game better. We may disagree on how that should be
done, but we can all agree that everyone has the best intentions.
One of the things that being a creator does open your eyes to is how hard it
actually is to do the kind of stuff that is so cavalierly dismissed on the boards. It
gives you a sense of perspective that a player simply cannot have, because they
are not part of the designer equation in our Grand Formula. Balance decisions are
tremendously intricate, and they reverberate far beyond the direct change itself.
While it is unlikely that your first few developments will have Mud Changing
Aftershocks, it is better that you start thinking in those terms sooner rather than
later. You should never lose touch with the game itself, but being a creator means
being willing to make decisions that will directly disadvantage your player
character, in the hope that the overall satisfaction of the playerbase rises.
Conclusion
That's it! Our business is concluded for now!
Hopefully you will have found this at least a little bit helpful in managing the
complexity that comes along with developing an area in our MUD. You have many
constraints to work within - your own confidence with code, the thematic
considerations, and the general principles of game design. You're going to make
mistakes as you go along, because everyone does - the important thing is that you
learn from them. Have fun!
266
Epitaph Online
http://drakkos.co.uk
Section Three
Intermediate LPC
267
Epitaph Online
http://drakkos.co.uk
The Mudlib Strikes Back
Introduction
Welcome to Intermediate LPC - the intermediate level creator material for Epitaph
Creators. Before progressing to this material, you should be sure that you
understand the material presented in Introductory LPC, and the Being A Better
Creator material. For this text, the assumption will be that you have read both of
these, and understand them all entirely. This is especially important for the
coding side of things - seriously, if you don't understand ever single thing in
Introductory LPC, you shouldn't think about attempting this book. I assume a lot
of you at this point.
Once we progress beyond the basics of LPC coding, it becomes possible to do
some very fun things - however, the theory and practise gets considerably more
complex when we start talking about commands and quests, so it's important for
you to be willing to persevere. Before we progress to the Meat and Taters of this
set of learning material, let's recap on what we're going to be doing as our worked
example.
Zombieville
We've set ourselves quite the challenge for our second learning village. We've set
the theme of an area as a rural village in the wilds of Scotland, complete with an
abandoned school containing a secret within. Within the school, we have decided
on three quests:



Sort the library
Break through the partition wall to the corridor beyond
Find and unlock the keypad that allows access to the cellar.
In particular, we have set ourselves the task of making these quests, as far as is
possible, dynamic. That, more than anything else, provides a substantial
challenge to our development skills.
We have also decided on a range of NPCs to populate our area. First we have the
zombies – ravenous corpses looking to feast on the meager nutrition offered by
your decaying human brain. We have our survivor, huddled in a knitwear shop
awaiting assistance from brave adventurers.
For our last NPC, we have the headmistress - our star attraction. She is a dread
boss NPC, with powers and capabilities far beyond that of the usual, everyday
undead fiend.
Finally, we have also decided on some features for the area. We have the library of
our school, which is the biggest draw to the village, and our local shop which is
268
Epitaph Online
http://drakkos.co.uk
full of tartan knitwear and schematics.
Along the way, we're going to look at building the coding infrastructure of this
development. It has quite a complex make-up of features, and we want to be able
to make sure it all works correctly and can be changed easily and efficiently.
However, what we won't be doing is focusing on the descriptions very much - this
is a coding manual, not a writing manual, and as with Deadville our focus is on
making the development actually work, not with making sure it reads well. It also
won't be 'feature complete' - we don't learn a lot by doing a dozen variations on
the same theme, and so we will talk about how to do something once and leave it
as exercise for the reader to fill in the blanks.
The Village Plan
We decided upon a layout quite early in Being A Better Creator - to recap:
6
/ \
5 + 7
\ /
4
/
3
|
2
/ \
1
8
\
9
Our first step is going to be to setup this framework. We're going to do it a little
bit differently to how we did Deadville. This is Intermediate LPC after all - we do
things Bigger and Better!
The biggest change we're going to make is in the architecture - we're not going to
use the standard inherits for our rooms, we're going to create our own set of
inherits. This gives us tremendous flexibility over shared functionality for rooms
and NPCs. That will be our first goal with the village - putting the rooms together.
You may think 'ha, I've already done that with Deadville', but not like this you
haven't - trust me.
A Word Of Caution
The quests we write as part of this material are not supposed to be tutorials for
how to write your own quests. We will cover certain key Epitaph concepts in the
process of building these, but you shouldn't think that any quest you write will be
written the same way. The important thing in all of this material are the tools and
techniques you use, rather than the product you end up building. This is an
important point - you shouldn't think of any of this as a blueprint, it's just a
269
Epitaph Online
http://drakkos.co.uk
process that is used to put together a complex area.
What you should be paying most attention to is the theoretical asides, such as
when we talk about handlers, or inherits - essentially any of the 'science bits'. It is
understanding when and where these programming constructs and design
patterns should (and should not) be used that is by far the most important
element of Intermediate LPC.
So please, don't just copy and paste the code that is provided - the code is the
thing that is safest to ignore! Why the code was written in the way it was written
though - ah, there be knowledge!
Conclusion
Fasten up tight kiddies, there's some pretty treacherous terrain ahead. By the end
of this book, if you've understood everything on offer, you'll have the necessary
toolkit to be able to code objects that will fascinate and delight young and old.
You won't know all there is to know about LPC, but you'll know more than enough
to be a Damn Fine Creator. Many creators over the years have been content to
write descriptions and develop simple areas, and there is nothing wrong with that.
However, when you want to do something that is genuinely cool, you need to
know how the code for it is put together. That's what we're here for!
270
Epitaph Online
http://drakkos.co.uk
Inheritance
Introduction
When we built the infrastructure for Deadville, we made use of the standard
inherits provided by the game for inside and outside rooms, as well as for NPCs.
This is a solid strategy, but one that limits your options for adding in area-level
functionality. In this chapter, we are going to build a series of custom inherits for
our new and improved area. These will be specializations of the existing inherits
(we’re not going to have to have much code in them), but they’ll make our lives
easier as we go along.
Inheritance is one of the most powerful features of object orientation, and one of
the reasons why the Epitaph Mudlib is so flexible to work with as a creator.
However, the cost of this is in conceptual complexity - it's not necessarily an easy
thing to get your head around.
Inheritance
The principle behind inheritance is simple – it’s derived from the biological
principle of children inheriting the traits of their parents. In coding terms, it
means that a child (an object which inherits) gains access to the functions and
variables defined in the parent (the object from which it is inheriting). That's why
functions like add_item work in the rooms we code. Some other creator wrote the
add_item method, stored it in the code that defines a room, and as long as we
inherit the properties of being a room in our own object, we too have access to
that function. If you inherit STD_OBJECT, you will find the add_item function is no
longer available.
At their basic level, inherits look something like this:
inherit STD_OUTSIDE;
void create() {
do_setup++;
::create();
do_setup--;
// My Code In Here
if (!do_setup) {
this_object()->setup();
this_object()->reset();
}
}
There is no requirement for an inherit to inherit anything itself, but since this is
271
Epitaph Online
http://drakkos.co.uk
going to be our outside room inherit, we’ll take advantage of having an object
that already does all the work for us.
The create() method is common to all LPC objects – it’s what the driver calls on
the object when it loads it into memory. The code here is slightly abstract, so
don’t worry too much about what it means. In brief, what it’s doing is making sure
that setup() and reset() get called at the right time when an object is created.
Without this, you get weird things like double add_items and such. Don’t worry
about it, it just has to be there.
Any code that we would normally put in the setup of an object can go between the
two blocks of code, where the comment says ‘my code in here’. For example, if
you want every room in your development to have an add_item, you can do that:
inherit STD_OUTSIDE;
void create() {
do_setup++;
::create();
do_setup--;
add_item ("zombieville", "Hell yes!");
if (!do_setup) {
this_object()->setup();
this_object()->reset();
}
}
Instantly, this makes life easier for us. We can have a common set of move zones,
add_items, and even functions available to all the objects we create when we use
this inherit this rather than STD_OUTSIDE.
As a general note of good practise, store all your inherits together so people can
easily find them – one of the side effects of it being possible to write them is that
they can rapidly make it difficult to navigate through an object unless people
know where they should be looking. For the sake of convention, all the inherits we
talk about in this book will be stored in the /inherits/ sub-directory of our
zombieville folder. This one in particular will be stored as outside_room.c.
Hooking It All Up
Our next step then is to make it so thi s inherit is freely available in our project –
in short, we setup our path.h file. Let's assume you have a ZOMBIEVILLE define
somewhere already – you know how to do that.
#define
#define
INHERITS
ROOMS
ZOMBIEVILLE + "inherits/"
ZOMBIEVILLE + "rooms/
Now that we have this, let’s put in place the architecture our our new village. We
do this the same way as we did for Deadville, except we have a new and exciting
inherit of which we can make use:
272
Epitaph Online
http://drakkos.co.uk
Once again, we’re going to do this for each of our outside rooms and add in the
exits to link them up appropriately. You should remember how to do that from
Introductory LPC 1 - My First Area. If you don’t, go back and read it. It’s okay, I’ll
wait.
Okay, having done that, we have the basic skeleton of the village in place. Now, lo
and behold, we can take advantage of our inherit by viewing our add_item in all
the rooms!
> look zombieville
Cannot find "zombieville", no match.
Egads, what’s this treachery? LPC, You go TOO FAR!
Replace Program
Well, there’s a tiny catch here – the MUD has a useful little function called
replace_program that it uses (or sometimes doesn't – its use is entirely optional
and will vary from MUD to MUD, even when they use the same lib). What it actually
does is far too technical to go into in detail, but essentially when you have an
inherit chain this function looks over the chain for objects that don’t really need
to be part of it and skips over them. It decides this on the basis of whether or not
an object has any functions defined in it. So it sees our inherit, thinks ‘Nah, you
don’t need that’ and skips over it. Our add_item then never gets added.
We don’t want it to do this, so we can get around it in two ways:


We can include the line set_not_replaceable (1) in the create function.
Include a function as part of our inherit.
I favour the latter of these, for no real reason other than I can never remember the
name of the function to call. So for now, let’s add a function to our inherit:
inherit STD_OUTSIDE;
void create() {
do_setup++;
::create();
do_setup--;
add_item ("zombieville", "Hell yes!");
if (!do_setup) {
this_object()->setup();
this_object()->reset();
}
}
int query_zombieville() {
return 1;
}
273
Epitaph Online
http://drakkos.co.uk
Now, when working with your own inherits, updating things gets a little more
complicated. You first need to update the inherit, and then update each of the
objects using it. You can also use the dupdate command, which updates each
object in the inherit tree. Don’t do that too often though, because you’ll end up
updating a lot more objects than you need to, and you never know when someone
may be in the middle of working with a file you just carelessly updated mid-edit.
Success!
So, you update the inherit, and then update everything in your rooms directory.
Magically, your add_item now appears in all its glory!
> look zombieville
Hell yes!
Now we are well placed to put in whatever functionality we want in our inherit. We
can add move zones (indeed, why not), light levels, room chats – anything we feel
like. We can make an area-wide search function, or add commands to every room
through the inherit. It’s enough to make a person giddy with power!
inherit STD_OUTSIDE;
void create() {
do_setup++;
::create();
do_setup--;
add_item ("zombieville", "Hell yes!");
set_light (100);
add_zone ("zombieville");
add_zone ("zombieville outside");
if (!do_setup) {
this_object()->setup();
this_object()->reset();
}
}
int query_zombieville() {
return 1;
}
Having done this for our outside room, we should also make an inside room
inherit. It’s done exactly the same way except that we inherit a different object:
inherit STD_INSIDE;
void create() {
do_setup++;
::create();
274
Epitaph Online
http://drakkos.co.uk
do_setup--;
add_item ("zombieville", "Hell yes!");
set_light (100);
add_zone ("zombieville");
add_zone ("zombieville outside");
if (!do_setup) {
this_object()->setup();
this_object()->reset();
}
}
int query_zombieville() {
return 1;
}
Now, we can use this for each of our inside rooms in the same way we can for
outside rooms.
However...
One of the things that makes LPC so powerful is that it's a language that supports
the principles of multiple inheritance. This means that rather than having only one
object that can be inherited from (as per Java and C#), LPC lets you define an
object as inheriting from multiple different parents. That’s extremely powerful,
but also full of twisty little mazes and traps.
Look at our two inherits above – what happens if we want to share functionality
between both of them? Like, for example, the query_zombieville function. It’s not
much of a function, but if it were more substantial (like a piece of complex code
attached to a search function), we wouldn’t want to copy and paste the code for
that. Instead, we make a shared inherit, and have our inside and outside rooms
inherit that as well as the core Mudlib object.
This inherit isn’t going to be an inside room or an outside room. Instead, it’s
going to just be an object we create from scratch – it inherits from nothing:
void create() {
seteuid (getuid());
}
int query_zombieville() {
return 1;
}
Notice our create method here – really, it isn’t doing anything at all. We'll talk
about uids and euids later in the material, but you can think of that line of code as
a kind of 'boilerplate'. That's the only line we need in create - there are no parent
275
Epitaph Online
http://drakkos.co.uk
inherits it has to deal with. All we do here is define a function that we want to be
available in both inside_room and outside_room. We’ll save this shared inherit as
zombieville_room.c
We then make use of that inherit within inside_room and outside_room. However,
this brings us back to our earlier problem with replace_program, so we also
change the query_zombieville function with more specialized versions:
query_inside_zombieville and query_outside_zombieville.
#include "path.h"
inherit STD_OUTSIDE;
inherit INHERITS + "zombieville_room";
void create() {
// Exactly as before
}
int query_outside_zombieville() {
return 1;
}
And:
#include "path.h"
inherit STD_ROOM;
inherit INHERITS + "zombieville_room";
void create() {
// Exactly as before
}
int query_inside_zombieville() {
return 1;
}
Thus, an inside room has query_zombieville (from zombieville_room) and
query_inside_zombieville (from inside_room). Outside also has query_zombieville,
but query_outside_zombieville instead.
Multiple Inheritance and Scope Resolution
Working with multiple inherits in one object causes problems with scope
resolution. Specifically, imagine if you had a function with one name in one
inherit, and a function with the same name but different functionality in a second
inherit. When you call that function on your object, which one is supposed to be
used?
There’s a little symbol that we use to resolve this – you’ll have seen it in the first
inherit code we looked at, and also in Introductory LPC 1:
do_setup++;
::create();
276
Epitaph Online
http://drakkos.co.uk
do_setup--;
The :: symbol is the ‘scope resolution operator’, and it tells LPC ‘call this method
on the parent object’. This is fine if there’s only one parent, but when there is
more than one, we need to refer to them specifically. In outside room:
do_setup++;
outside::create();
zombieville_room::create();
do_setup--;
And in inside_room:
do_setup++;
room::create();
zombieville_room::create();
do_setup--;
The create methods get called in the order you give them, and the parents are
differentiated by their unqualified filenames (their filenames without the directory
prepended). If we only wanted to call one creator or the other, then we could do
that too by omitting a call to the relevant parent.
'Woah, but back up there, Sparky!', I hear you say. Please, don't call me Sparky, I
don't like it.
'Why the hell is the outside one outside::create(), but the zombieville one is
zombieville_room::create()?'
That is an excellent question, imaginary reader! What LPC is looking for for the
left hand side of the scope resolution operator is the file name of the inherit. See,
as mentioned in an earlier chapter of Introductory LPC, STD_OUTSIDE is a define
that comes from mudlib.h, as is STD_ROOM. They map to different files –
STD_OUSIDE maps to /mudlib/inherits/outside, and STD_ROOM maps to
/mudlib/inherits/room. When we use scope operation, the MUD is looking for
'outside::' and 'room::' respectively. For our shared room inherit, the last part of
its filename is zombieville_room, and so zombieville_room:: is how we resolve its
scope.
The question of how you are supposed to know this is fair – it’s all in mudlib.h,
but if you don't fancy trying to work it out from that, you can do something like
this:
exec return STD_OUTSIDE;
Returns: "/mudlib/inherits/outside"
That won't work for defines that you put in our own code (it's only because every
file gets mudlib.h for free that it works in an exec), but you should find it helpful.
277
Epitaph Online
http://drakkos.co.uk
Our Zombieville Room Inherit
Now, the problem we have here is that since our zombieville_room inherit doesn’t
inherit any of the room code, we can’t use any of the normal room functions like
add_item or such. Or rather, we can but we need to do it a little bit more
awkwardly. If we try this, we will get a compile time error:
void create() {
seteuid (getuid());
add_item ("stuff", "There is some stuff, and some things.");
}
However, we can do something like this instead:
void setup_common_items() {
this_object()->add_item ("stuff", "There is some stuff, and some things.");
this_object()->add_item ("things", "There are things, amongst the stuff.");
}
The this_object function allows us to treat our code as a unified whole - there is
no add_item in zombieville_room, but there is in the 'package' of
zombieville_room and zombieville_inside_room, as an example. Using
this_object() lets us call methods on the whole package, and not on the
constituent bits.
In our inherits, we can make a call to setup_common_items as part of our code,
and it will all work seamlessly. So, for outside_room, our full code is:
#include "path.h"
inherit STD_OUTSIDE;
inherit INHERITS + "zombieville_room";
void create() {
do_setup++;
outside::create();
zombieville_room::create();
do_setup--;
add_item ("zombieville", "Hell yes!");
setup_common_items();
set_light (100);
add_zone ("zombieville");
add_zone ("zombieville outside");
if (!do_setup) {
this_object()->setup();
this_object()->reset();
}
}
278
Epitaph Online
http://drakkos.co.uk
int query_outside_zombieville() {
return 1;
}
And for inside_room:
#include "path.h"
inherit STD_ROOM;
inherit INHERITS + "zombieville_room";
void create() {
do_setup++;
room::create();
zombieville_room::create();
do_setup--;
add_item ("zombieville", "Hell yes!");
setup_common_items();
set_light (100);
add_zone ("zombieville");
add_zone ("zombieville outside");
if (!do_setup) {
this_object()->setup();
this_object()->reset();
}
}
int query_inside_zombieville() {
return 1;
}
Now we have an extremely flexible framework. If I want things that are common
to outside rooms but not inside rooms, I put the code in outside_room. If they’re
for inside rooms only, they go in inside_room. If they should be shared between
both, I can put the code in zombieville_room.
Notice though that we’ll have a problem if we want to create something that isn’t
an inside room or an outside room (like an item shop...). We’ll come back to that
later, but it’s not a problem to add what specific inherits we need to make it all
work seamlessly.
Visibility
Now, this system as it stands has a number of issues . Let's say for example that
my inherit defines a variable. That variable gets inherited along with everything
else, and so any object that makes use of mine can change the state of that
variable without me being able to control it. Imagine a simple bank inherit we're
writing for our village:
279
Epitaph Online
http://drakkos.co.uk
#include "path.h"
inherit INHERITS + "inside_room";
int balance;
void create() {
do_setup++;
basic_room::create();
zombieville_room::create();
do_setup--;
if (!do_setup) {
this_object()->setup();
this_object()->reset();
}
}
void adjust_balance (int bal) {
balance += bal;
if (balance < 0) {
do_overdrawn(this_player());
}
}
void do_overdrawn (object ohno) {
ohno->do_death();
tell_object (ohno, "Be more responsible with your money!");
}
Here, when someone goes overdrawn, they get killed as should be. However,
there's nothing that requires people to go through this adjust_balance method.
They can just directly manipulate the balance variable in the code they create:
inherit INHERITS + "crazy_bank_inherit";
void setup() {
balance = -1000;
}
This is because our variable is publicly accessible, which is how all methods and
variables are set as default. Public means that anything that can get access to the
variable can manipulate it. I don't want people to be able to do this, because it
circumvents the checking I wrote into my method. We can restrict this kind of
thing through the use of visibility modifiers, which we will discuss again in
Working With Others. By setting a variable to be private, it means that it can only
be accessed in the object in which it is defined:
private int balance;
Any attempt to manipulate it in a child object will give a compile time error saying
that the variable has not been defined. They can define their own balance
variable, but changing that won't change the state of the one in the inheritable.
This forces people to go through our adjust_balance method. Any time we protect
a variable in this way, we should expose a pair of accessor methods (a setter and
a getter) that allow it to be manipulated with our consent:
int get_balance() {
280
Epitaph Online
http://drakkos.co.uk
return balance;
}
void set_balance (int bal) {
if (bal < 0) {
do_overdrawn (this_player());
}
balance = bal;
}
In this way, we get the benefit of control over how a variable is manipulated, but
we don't restrict the freedom of others to work with the internal state of our
object.
The Impact of Change
We will talk more about the impact of change in the next section of this book, but
as a hint of things to come, this is why visibility modifiers are important - if we
have a public variable in our inherit, we have to assume that somewhere someone
is making use of that variable in their own objects, and if we change it we will
break their code. That's bad voodoo, man. We limit variables to being private to
keep our options open - if I want to change it from an int to a float, then I can't
easily do it if it's set as being public. If it's private, then the only code that will
break is in the inherit itself, and I can fix that directly.
For similar reasons, we may wish to restrict access to methods. We can do that
too. Methods can be set to private - once they are, they are no longer possible to
call from a child object, or from outside that object with call_other:
private void do_overdrawn (object ohno) {
ohno->do_death();
tell_object (ohno, "Be more responsible with your money!");
}
Sometimes though, we don't want to be this restrictive. Maybe we want people
who are making use of our inherit directly to be able to access the function, but
set it as inaccessible to outside objects. There is a 'middle' level of visibility that
handles this - protected. It allows access for the inherit in which the method or
variable is defined, and any objects that inherit that code. It does not allow access
via call_other or the -> operator - it'll simply return 0 if access is attempted.
Conclusion
Putting in the skeleton of an area becomes much easier when we make use of
bespoke inherits. We can bundle common functionality into one piece of code
(thus making it much easier to maintain), and we can future-proof our
development by making it easier for us to add wide-ranging systems at a later
date without needing to change all the other rooms in the development. When you
look at cities like Dunglen, or even smaller areas like the Dunglen hospital or the
Winchester, you’ll see they were all written with this kind of system in mind.
281
Epitaph Online
http://drakkos.co.uk
It may seem like overkill to put an architecture like this in place for a small
development like Zombieville, but on those occasions where I have not done
something similar for even small areas, I have regretted it. Take that advice in
whatever way you wish!
282
Epitaph Online
http://drakkos.co.uk
The Library Quest
Introduction
Let’s begin our discussion of the content with the school, our quest hub in this
particular area. Our first quest, as I’m sure you recall only too well, was to sort the
library shelves. We enter the room, and the floor is strewn with discarded books.
There are bookshelves everywhere, each marked with particular category
headings. Putting the books on the floor onto the right bookshelf is our quest.
It’s a solid quest – one that allows for us to put a dynamic framework around it so
that it can’t be solved simply from a quest list. Because of that, it needs a little bit
of thought to structure properly.
Setting up the quest also requires us to introduce both the concept of a quest
NPC and the structure of a quest as it is interpreted by our quest handler. Alas,
the nature of the quest handler means that you cannot easily experiment with
quests in your /w/ directory, so you will need to make use of the quests as they
are provided to you. Still, never mind – you can do whatever you like with the
code!
So, without any further ado – let's get started!
An Epitaph Quest
Many things on Epitaph are handled by files known as data files. They look a lot
like virtual files, but work differently – they're essentially text files that get turned
into LPC objects through the gentle ministrations of a handler called the data
handler.
When we want to write a quest for Epitaph, we need to make available a quest file
for it – that registers it with the quest handler and let's us set up all its pertinent
data such as its story, quest hints, reward levels, criteria and so forth. A simple
such quest file might look like the one in /data/quests/librarian.que:
::item "visiting librarian"::
::->name:: "visiting librarian"
::->story:: "obeyed the categorical imperative"
::->text:: "It appears that the library is still on Anges McKay's mind. It
would be an act of considerable kindness "
"if someone were to organise it and put her troubles to rest. The ones of
which she is aware, anyway..."
::->rewards:: ([ ])
::->consumed:: ([])
::->criteria:: ([
"organised library" : 1,
])
::->completion_message:: "It seems like a a futile way to spend an afternoon
283
Epitaph Online
http://drakkos.co.uk
during the apocalypse, but perhaps by "
"small acts of kindness like this, some of our humanity may be retained..\n"
::->hints:: ({
})
::->region:: "zombieville"
::->subarea:: "zombieville"
::->luck_needed:: QUEST_MINIMUM
::->randomness:: QUEST_MODERATE
::->player_skill:: QUEST_MODERATE
::->character_skill:: QUEST_NONE
::->listability:: QUEST_NONE
::->time_needed:: QUEST_MODERATE
You should get comfortable with this kind of file, because we use them a lot on
Epitaph. You don't need to worry too much though about what's happening, just
know that each of the little ::-> symbols indicates a piece of data about what
makes up a quest.
Data files drive most of our configuration details – every kind of material is a data
file, every quest, every achievement, every faction mission, every knack – so many
things, in fact, that I can't really give an accounting. List the contents of /data/ to
see how many things get represented like this.
I won't spend any time explaining what the individual bits do – they should be
obvious from context. The only ones that really matter for our purposes are the
name and story (which we agreed on in the last section), the criteria (which is how
we'll be able to tell if a quest is completed or not), and the various entries at the
bottom – these are the things that are used to calculate the level of the quest.
We don't need perfect values for any of these yet, we just need values, and these
are the ones we have. In a very real sense though, this is our quest – the code
that we still have outstanding is what allows a player to go from having no quest
information at all to having the right criteria for the quest to be completed.
Data Representation
Of all the decisions that a coder makes, how they choose to represent data is the
most important. It’s what changes an impossible task into a trivial task, or importantly - a trivial task into an impossible task. If you choose good data
structures for how you store and manipulate all of the data associated with a
system, your job becomes exponentially easier than if you choose bad data
structures.
When we talk about data structures, we mean the combination and
interrelationship of data-types within an LPC object. An array of mappings is a
data structure. A mapping of mappings is a data structure. Ten ints and a string
are a data structure.
Our choice in this respect should be influenced by several factors:

How easy is the data structure for others to read and understand?
284
Epitaph Online



http://drakkos.co.uk
How efficient is the data structure in terms of memory and CPU cost?
How simple is it to query the various parts of the data structure?
How simple is it to manipulate the various parts of the data structure?
The requirements for each of these factors will vary from situation to situation.
Core Mudlib code must be highly efficient and simple to query and manipulate. It
doesn’t have to be especially readable since the people likely to be working with it
are usually more technically experienced, but make no mistake – readability helps.
On the other hand, code written for these tutorials must emphasise readability,
and readability is one of the key predictors of how maintainable code will be. For
normal, every day domain code where you can’t assume a certain level of coding
knowledge, readability is a hugely important feature.
Our decision as to how to represent our data will emerge out of a consideration of
the data itself – what do we need to store, for how long, and in what ways do we
need to manipulate the data?
Let’s consider our library quest.










We
We
We
We
We
We
We
We
We
We
need
need
need
need
need
need
need
need
need
need
to
to
to
to
to
to
to
to
to
to
store the titles of some books.
be able to get a random list of these
store some category headers.
be able to get the list of these
store how a player has sorted books
know which category a player has associated with each book
let players move books from category to category
let players reset their entire categorization.
let players add books to categories
let players remove books from categories
It’s apparent here that the first two sets of data require little manipulation, and
the last set of data requires considerable manipulation. If we change the
parameters of our quest, we also change the nature of the data representation.
For example, say we let people add their own books (a bad idea, but let’s say we
did). In addition to the requirements above, we would also need to allow players
to add titles to the list of books, and remove titles from the list of books. We’d
also need to be able to save the data – note that we don’t need to do that at the
moment.
Changing the parameters of the quest will have an impact on how simple the data
is to manipulate. If you’ve chosen a good data structure for your purposes, it will
be possible to add new functionality as time goes by with minimal fuss. If you’ve
chosen a bad data structure, then new functionality becomes very difficult to
implement. That's what we mean by 'maintainability'.
Let’s look at two ways of representing this data. First, a straw-man to show the
impact of bad data representation:
string book1;
string book2;
285
Epitaph Online
http://drakkos.co.uk
string book3;
string book4;
string category1;
string category2;
string category3;
string category4;
mapping books_and_categories;
void create() {
seteuid (geteuid());
}
void setup_quest() {
book1 = "Some stuff about
book2 = "Some stuff about
book3 = "Some stuff about
book4 = "Some stuff about
category1 = "romance";
category2 = "action";
category3 = "erotica";
category4 = "comedy";
you.";
me.";
her.";
him.";
books_and_categories[book1]
books_and_categories[book2]
books_and_categories[book3]
books_and_categories[book4]
=
=
=
=
category1;
category3;
category2;
category4;
}
This kind of data representation does not lend itself well to modification or
expansion. What happens if you want to add in twenty new books? What happens
if you want books to belong to more than one category? What happens if you want
people to be able to sort the list of books so they can browse through it easier?
None of these things are simple to do with this bad data structure. A slightly
better one:
string *books;
string *categories;
mapping books_and_categories;
void setup_quest() {
books = ({"Some stuff about you.", "Some stuff about me.",
"Some stuff about her.", "Some stuff about him.");
categories = ({"romance", "action", "erotica", "comedy"});
books_and_categories[books[0]]
books_and_categories[books[1]]
books_and_categories[books[2]]
books_and_categories[books[3]]
=
=
=
=
categories[0]
categories[2];
categories[1];
categories[3];
}
It's still not great – we have to hand roll the connection between each book and
each category. How about this:
mapping books_and_categories;
void setup_quest() {
books_and_categories["romance"] = ({"Some stuff about you"});
books_and_categories["comedy"] = ({"Some stuff about me"});
books_and_categories["erotica"] = ({"Some stuff about her"});
286
Epitaph Online
http://drakkos.co.uk
books_and_categories["action"] = ({"Some stuff about him"});
}
Here, we’re going a slightly different way – if we ever want a list of the categories,
we need to pull it out of the data structure like so:
string *query_categories() {
return keys (books_and_categories);
}
We’ve traded off a little efficiency in favour of a more elegant representation.
Now, if we want to add a pile of new books, it’s a trivial task
void setup_quest() {
books_and_categories["romance"] = ({"Some stuff about you", "Gnoddy",
"Things that go bump in the day"});
books_and_categories["comedy"] = ({"Some stuff about me",
"Where's My Cow?"});
books_and_categories["erotica"] = ({"Some stuff about her",
"Things that go bump in the night"});
books_and_categories["action"] = ({"Some stuff about him",
"The Nevewending Story"});
}
There are better ways still to do this – our structure is efficient, but it’s not
especially expandable. What happens if we want to make the books a bit more
interesting? If for example we wanted to add the name of the author, or a blurb
on the back?
That’s not easy to do with our current representation – we need something
different.
In A Class of Your Own
LPC makes available a special kind of ‘user-defined’ data type called a class. For
those of you with any outside experience of object orientation, please don't make
any assumptions about the word 'class' - it's nothing like a class in a 'real' object
oriented language. With a class, you create a single variable that has multiple
different compartments in it. For example, we could do this:
class book {
string title;
string author;
string blurb;
string *categories;
}
With this definition we’ve created a brand-new data type for use in our object – we
can designate things as being of type ‘class book’:
void setup_quest() {
class book newBook;
287
Epitaph Online
http://drakkos.co.uk
}
We create new variables for a class in a different way from most variables – we use
the new keyword:
newBook = new (class book);
Once we have the new 'instance' of this class, we can set its individual constituent
parts using the -> operator:
void setup_quest() {
class book newBook;
newBook = new (class book);
newBook->title = "Some stuff about you";
newBook->author = "You";
newBook->blurb = "An exciting tale of excitement and romance!";
newBook->categories = ({"romance"});
}
You can also combine the steps of creating a new class and setting its elements
like so:
class book newBook;
newBook = new (class book,
title: "Some stuff about you",
author: "You",
blurb: "An exciting tale of excitement and romance!",
categories: ({"romance"))
);
A single variable of a class is useful, but when combined in an array format they
become especially powerful. What we get is something akin to a simple database.
Imagine the following data representation:
class book {
string title;
string author;
string blurb;
string *categories;
}
class book *allBooks = ({ });
void add_book (string book_title, string book_author, string book_blurb,
string *book_categories) {
class book newBook;
newBook = new (class book,
title: book_title,
author: book_author,
blurb: book_blurb,
categories: book_categories);
allBooks += ({ newBook });
288
Epitaph Online
http://drakkos.co.uk
}
void setup_quest() {
add_book ("Some stuff about you", "you",
"A exciting tale of excitement and romance!", ({"romance"}));
}
Now, when we want to add a new book, we just call add_book – as many books as
we like. However, if we want to get a list of categories, we have traded off the
simplicity of arrays for something that needs a more bespoke solution:
string *query_categories() {
string *cats = ({ });
for (int i = 0 ; i < sizeof (allBooks); i++) {
foreach (string c in allBooks[i]->categories) {
if (member_array (c, cats) == -1) {
cats += ({ c });
}
}
}
return cats;
}
A structure like this requires some management functions for ease of
manipulation. For example, what if I want to get the blurb that belongs to a
particular book? I need a way of finding that book element in the array, so I need a
function to do that:
int find_book (string title) {
for (int i = 0; i < sizeof (allBooks); i++) {
if (allBooks[i]->title == title) {
return i;
}
}
return -1;
}
Then, if I want to query the blurb of a book:
string query_book_blurb (string title) {
int i = find_book (title);
if (i == -1) {
return 0;
}
return allBooks[i]->blurb;
}
There’s some more upfront complexity here, but the trade-off is that it’s much
easier to add in new functionality as time goes by. If we want to add in a new set
of information to go with each book, we just add it and it’s there and supported.
Things get a bit trickier when we load and save classes, but that's a resolvable
issue.
289
Epitaph Online
http://drakkos.co.uk
The Library State
So, that sets us up with something that stores each of the books. How do we
represent how the player has currently sorted the library? Here, we need to answer
a new question – do we store this information about the player, or do we store it
about the room?
If we store it about the player, it means that multiple players can be working in
the same library without their actions impacting on the state of the other players.
If we store it about the room, then all people working within the library have a
shared library state.
Really, there isn’t a right answer here – but since it’s a little bit weird if everyone
is working within their own 'meta' library, let’s just decide we’re going to store the
state about the room – so when one player sorts a book on a shelf, it changes the
state of the room for everyone else in it. That means we need some more
variables in our room to store which books are where. We can store that pretty
simply as a mapping – book title X is on bookshelf Y:
mapping sorting;
int assign_book_to_shelf (string book, string category) {
int i = find_book (book);
string *cats;
if (i == -1) {
return -1;
}
cats = query_categories();
if (member_array (category, cats) == -1) {
return -2;
}
sorting[book] = category;
}
Believe it or not, that’s the bulk of the engine of the quest done. Everything else
proceeds simply from this point because we have a solid data representation we
can rely upon.
Dynamic Quest Design
Ideally, a dynamic quest will involve some kind of random setup of the books. For
example, let’s say that our book titles do not give the clue to what the category is,
only the blurb does. With this comparatively minor modification to the concept,
we can then randomly assign each book a category (and thus a blurb) when we
add it. Well, that’s no problem at all:
void add_book(string book_title, string book_author) {
290
Epitaph Online
http://drakkos.co.uk
class book newBook;
string my_blurb;
string *cats = ({"romance", "action", "childrens"});
string my_cat;
string *valid_blurbs;
mapping blurbs = ([
"romance" :
({
"A romantic tale of two estranged lovers",
"A heartbreaking tale of forbidden love!",
"A story of two monkeys, with only each other to rely on!",
}),
"action" :
({
"A thrilling adventure complete with a thousand elephants!",
"An exciting story full of carriages-chases and brutal sword-fights!",
"A story of bravery and heroism in the trenches of Koom Valley!",
}),
"childrens":
({
"A heart-warming tale of a fuzzy family cat!",
"A story about a lost cow, for children!",
"A educational story about Whiskerton Meowington and his last "
"trip to the vet!",
})
]);
my_cat = element_of (cats);
valid_blurbs = blurbs[my_cat];
my_blurb = element_of (valid_blurbs);
newBook = new (class book,
title: book_title,
author: book_author,
blurb: my_blurb,
categories: ({my_cat}));
allBooks += ({ newBook });
}
Now we have a quest architecture in place – we give in a list of titles and authors
of books, and it sets up the details randomly from that. People will need to read
the blurb to find out what the book is about and then sort it into the appropriate
category. There’s no way to short-cut this quest, you need to actually put in the
work yourself. Sure, someone may quest-list all of the blurbs and the categories to
which they belong, but it’s pretty obvious from the blurb anyway. There is simply
no value in quest-listing this.
We can add in as many books as we like here, but we want to set some kind of
sensible limit for our players – say, ten books to give a reasonable amount of
work to do before the quest is awarded.
The Rest of the Quest
This is the core of the quest, but we also need to provide the bits around the
edges – we need to be able to give lists of information on request. For example,
291
Epitaph Online
http://drakkos.co.uk
the list of all books that have not yet been sorted:
string *query_unsorted_books() {
string *unsorted = ({ });
for (int i = 0; i < sizeof (allBooks); i++) {
if (!sorting[allBooks[i]->title]) {
unsorted += ({ allBooks[i]->title });
}
}
return unsorted;
}
A way of telling to which category a book has been sorted:
string query_bookshelf_of_book (string title) {
return sorting[title];
}
And a way to reset the sorting if it’s all gone horribly wrong:
string reset_sorting() {
sorting = ([ ]);
}
There may be more we need, but these will do for now. We can’t anticipate
everything, after all.
This may not look like any quest with which you are familiar, and that’s because
it’s not – something like this sits behind every quest in the game, but that’s for
our eyes only. For the players, we need to give an interface to our code – a way for
them to call functions in a sanitized manner. For example, we may give the player
a command in a room:
sort book_title into category <category>
Upon typing this command, it hooks into the functions we’ve written – specifically,
it will do the ‘assign_book_to_shelf’ function, providing the book title and
category they give. We need to then give meaningful output to the player
indicating what has happened. That’s for another chapter though.
Conclusion
This chapter has introduced a new, powerful data-type to you – the class. I am a
big fan of classes, they make almost everything easier to do. However they are
syntactically distinct from the other data types with which you will be familiar, and
so you should make sure you are comfortable with how they work before you start
playing about with them.
Data representation is a hugely important topic – get your representation wrong,
and the rest of your code is doomed to be a hopelessly unreadable,
unmaintainable mess. Get it right, and good code will flow like honey. That’s just
the way of it.
292
Epitaph Online
http://drakkos.co.uk
Adding Commands
Introduction
We've built the bulk of the technical architecture for our first quest, but what we
need to do now is build the user interface to that architecture. The phrase ‘user
interface' usually conjures up images of windows based applications, but all it
means is ‘what sits between my code and my users'. In the case of a MUD, the
user interface may be an item, it may be a room, it may be a command, or it may
be something else.
What we need to decide then is how we are going to allow our user to interact
with the functions we have written. As with most things, this will be influenced by
the context of the code – there is no definitively right situation.
Deciding On a User Interface
There are certain elements of building a user interface that are universal rules to
which we must adhere. In fact, it's not a mandate – we have all sorts of things in
the game that violate these. However, if you break them knowingly then someone
(that is, me) will cut you.
A good user interface has the following traits:




Consistency
Predictability
Allows for undoing mistakes
Provides meaningful feedback to users
There are other traits consistent to all user interfaces, but these are the ones that
we need to view as iron-clad rules for Epitaph development.
A good user interface is consistent. That means that if similar or identical
functionality is available in a different part of the game, our functionality should
mirror the way it's accessed. For example, if we have a mail-room in every city in
the game, then it's violating user interface design if one is accessed through
commands and another requires you to talk to an NPC. We violate this rule all the
time, but those violations should be considered bugs – it's not okay to go against
consistency. We should always adopt the best interaction choice and be consistent
with how it is applied. This is one of the reasons why we like to bundle significant
features into inherits that get used across the game.
Predictability relates to how the cues that we provide in the game should be
interpreted. Imagine if we had a parcel that when unwrapped yielded not a gift but
293
Epitaph Online
http://drakkos.co.uk
instead a package of spiders that ate your face. That may be funny, but it's not
justifiable unless there is some kind of predictability built into the parcel. Perhaps
if you look at it close enough you get a clue as to what is within, or if you leave it
long enough you can see it moving, or hear legs within. If you provide a course of
action to the user, the result must be knowable otherwise you are punishing
people for exploring your code.
As a second example of this, imagine a situation in which we have a game full of
red doors and green doors. The red doors do some damage to you and yield a
reward, and the green doors permit access with no downsides. This is part of the
user interface since it is from this that the user builds their vocabulary of
experience. If you then suddenly half way through the game reverse these
colours, then the effect is you tell your player ‘Anything you think you have
learned may be discarded at any time'. The user then has no way of making
meaningful decisions regarding their choice of interacting with the game.
It is important that for everyday usage there is a mechanism for undoing changes.
We have a ‘high consequence' model for certain things in the game – rearranging
is easy to do and costly in terms of finite resources to undo, and you can't easily
undo a bad choice in learning skills. This is fine, but something to do sparingly.
For day to day interaction with the game, there should be an easy way to undo
anything that a player has done. For our quest, we make it easy for people to
reassign books from category to category, as well as start over.
Finally, your user interface should provide meaningful feedback to a player.
Imagine if whenever we try to shelve a book incorrectly we get the following
message:
> Try again.
How do I choose to make a meaningful decision here? Is the problem with my
syntax? Is it with the category I'm using? Am I not using an existing book? Is the
book already shelved?
There is no way I can get from this error message to a useful change in my
behaviour. Thus, when you provide feedback to the user it should indicate in a
meaningful way whether there is success or failure. It should also speak in
language the player is likely to understand. ‘Object reference is inconsistent with
expected parameters' may make sense to a developer, but a player would much
prefer something like ‘that particular item isn't what you need here'.
Quests on Epitaph don't have a cast-iron interaction convention, but the majority
of them are accessed through commands defined in rooms or items or NPCs.
Because that's a common interaction context, that's the choice we too will make.
In order to do this, we need to talk about a new Epitaph construct, the
add_command.
294
Epitaph Online
http://drakkos.co.uk
Adding Commands
You've already done this in a simple way in Introductory LPC, where we added
commands to add_items. Epitaph offers a powerful system for making available
commands to your players, and it's called add_command. Alas, its power ensures
that it's one of the hardest things to use properly, so we must spend a
considerable amount of time talking about the theory behind the function.
At its basic level what it lets you do is provide a command that exists only in a
certain context and as long as a certain condition is true. Usually that context is
while the player is either:



Inside the object (rooms)
Holding an object (items and NPCs)
In the same environment as the object (items and NPCs)
You don't need to worry about keeping track of this, it's all done for you by the
Epitaph parser. All you need to know is how to add the commands. It's the parser
that is responsible for taking what a user types into the game and breaking it into
different parts. You don't need to worry yourself overly about this, just treat it as
a bit of magic for now – just imagine it as that a player types in a string such as
‘stab drakkos with knife' and the parser breaks that up into the command (stab),
the objects involved (drakkos and knife) and the rest of the text which is just for
aiding in making our commands a little more ‘natural' to type in.
The exact way in which the parser does this is decided by the pattern we give the
command we add. We can decide specifically what kind of arguments that an
add_command should accept and work with.
Traditionally, an add_command is located in the init method of the object with
which you are working. Let's look at one simple example of this before we get into
the specifics. This would be a command that would exist on a room:
void init() {
::init();
add_command ("test", "<string>");
}
And the the function that goes with this add_command:
int do_test(object *indirect_obs, string dir_match, string indir_match,
mixed *args, string pattern) {
printf ("Test: %s\n", args[0]);
return 1;
}
In init, we add the command – it's called ‘test' and the pattern is any string of text
– it matches anything that follows the word ‘test'. When we use the command, the
MUD looks for a function called do_<command>, where <command> is what we
named the command. We can change this default behaviour, but we won't talk
295
Epitaph Online
http://drakkos.co.uk
about that just yet. Our command is called test, so the MUD looks for a function
called do_test to handle it.
When we enter a command in this way, the function do_test gets called, with all
the parameters in the parameter list above (they get handled for you). In this
function, we simply print out the text that the user typed while in the room (that's
what printf lets you do):
> test This is a test!
Test: This is a test!
Each of the parameters to the function holds a different piece of information. We'll
talk about indirect and direct objects later – for now the only one we are
interested in is args. This array holds each of the different parts of the command
that followed the name. Since we only have a string, that's all it shows. However,
consider if we had the following as a pattern:
void init() {
::init();
add_command ("test", "<string> with the <string>");
}
The pattern acts like a filter and a ‘fill in the blanks' system – the user must type
out this pattern exactly, but they can put whatever they like in the dynamic bits –
the bits marked with <string>. So the following would all be valid commands:
>
>
>
>
test
test
test
test
drakkos with the cakes
cakes with the tea
tea with the toasting forks
a whole load of people with the standard examination
However, none of the following would be valid:
> test drakkos
> test with the toasting forks
> test drakkos with the
When we try to enter an invalid combination of command and text, we get the
standard error message:
See "syntax test" for the input patterns.
When the do_test function gets called with a valid command string, our code
above only picks up the first string that was part of the match:
> test here with the thing
Test: here
If we wanted both bits, we'd need to change out functiona little:
int do_test(object *indirect_obs, string dir_match, string indir_match,
mixed *args, string pattern) {
296
Epitaph Online
http://drakkos.co.uk
printf ("Test (First argument): %s\n", args[0]);
printf ("Test (Second argument): %s\n", args[0]);
return 1;
}
Args thus contains an array of each specific 'blank' that the user gave to our
pattern. I'm sure you can see why that is useful!
Now, let's extend our simple example a little by incorporating meaningful user
feedback. We do this through the use of a method called add_succeeded_mess:
int do_test(object *indirect_obs, string dir_match, string indir_match,
mixed *args, string pattern) {
add_succeeded_mess ("$N $V the command.\n", ({ }));
return 1;
}
The $N and $V are examples of tokens that get processed by the MUD and turned
into meaningful output depending on who is seeing the message. You will see:
You test the command.
Other people would see:
Drakkos tests the command.
The $N gets replaced with the short of the person performing the command, and
the $V gets replaced with the verb used (in this case, ‘test'). Pluralisation is done
automatically depending on who is doing the observing.
Failure Messages
The final thing we need to discuss about this simple example of an add_command
is the return value. This is meaningful – a return of 1 means that the command
succeeded and that a succeeded message (added by us) should be displayed. If it
returns 0, it means the command failed and that a failed message should be
displayed, like so:
void init() {
::init();
add_command ("test", "<string>");
}
int do_test(object *indirect_obs, string dir_match, string indir_match,
mixed *args, string pattern) {
if (args[0] == "pass") {
add_succeeded_mess ("$N $V the command.\n", ({ }));
return 1;
}
else {
297
Epitaph Online
http://drakkos.co.uk
add_failed_mess ("You failed the test.\n", ({ }));
return 0;
}
}
A failed message goes only to the player, and should be used when the actual
functionality of a command has failed to begin - for example, if they were using
inappropriate objects, or they used an inconsistent pattern. When you return 0
from an add_command function, the MUD keeps checking for another object that
may be able to handle the player's input. If you return 1, it stops checking for
another object to match the command. In this way, multiple objects can define
commands with the same name, and only those appropriate to a situation will be
triggered.
There is another way to handle failure messages, and this should be done in
situations where there are multiple failures that a player must correct. To
illustrate what I mean by this, let me perform a little one man play for you:
> kill midget with sword
You cannot kill the midget with the sword, because you are not holding the
sword.
> hold sword
You hold the sword in your right hand.
> kill midget with sword
You cannot kill the midget because you are lying down
> stand
You stand up.
> Kill midget with sword
You cannot kill the midget because you are on fire.
> shout AAAAAAAAAAAAAAAA!
You shout: AAAAAAAAAAAAAAAA!
This kind of thing can be tremendously frustrating – it would be much better if we
got a message that listed all the things we would need to correct in order to
perform the action:
> kill midget with sword
You cannot kill the midget because you are not holding the sword, you are
lying down, and you are on fire.
That way, we have a list of things we can correct without having to fix one thing
and then retry.
We do this through a system of accumulating failure messages, using the function
add_failure_reason(), like so:
if (this_player()->query_position ("lying")) {
this_player()->add_failure_reason ("you are lying down");
}
298
Epitaph Online
http://drakkos.co.uk
Once we have done all these pre-checks, we provide a final failure reason like so:
if (sizeof (this_player()->query_failure_reasons())) {
this_player()->add_failed_mess (this_object(), "You cannot $V $D, "
"because $reason$.\n", ({ }));
}
The result is a much more helpful, much less frustrating trial and error system,
and that leads to a correspondingly friendlier user interface.
We'll talk about the array provided to each of the add_x_mess methods a little
later in this chapter.
More on Patterns
The real power of add_command comes from the fact we are not restricted to
matching strings - we can also make it match other things. A list of matching
codes and the things they are used for:
Code
<indirect>
<direct>
<number>
<word>
<fraction>
<preposition>
Matches
An object
The object in which the function
associated with a command is defined.
A number (which must be expressed in
digits)
A single word
A fraction, such as 3/4, 5/6, etc
Such as ‘to', ‘from' etc
We can also specialize these patterns, depending on what they actually are. We
can do so by separating following specializations with a : . Usually you do this for
objects rather than the other types, but the help file for add_command will give
you some things you can do with strings and numbers.
For indirect and direct, you will often want to specify what kind of object you are
are trying to match. The default is any object at all, but you can override that by
providing a second part to the match:
299
Epitaph Online
Code
object
living
distant-living
any-living
player
wiz-present
http://drakkos.co.uk
Matches
Any object
A living object
Uses the function find_living to match
an object.
Matches living objects in the room
first, and then distant objects later.
Also allows for the user of ‘someone’,
‘everyone’ and ‘creators’ as valid
matches
Only matches a player
Wiz present allows for the complex
creator level matching. See ‘help wizpresent’ for details.
We can specialize our patterns with a colon, so:
<indirect:living>
Or
<direct:object>
We can also further specialize these by specifying where a match should be made
from:
Code
me
here
here-me
me-here
direct-obs
Matches
The inventory of the living object
performing the command
The inventory of the environment of
the living object
Check the environment first, and then
the inventory
The inventory of the object, and then
the environment.
The inventory of the direct object
So, if I was in the strange situation of wanting to match any living object in my
inventory, I could use the following:
<indirect:living:me>
Or if I wanted to match players in my environment only:
<indirect:player:here>
The syntax of the command gets built automatically from our pattern (so when
players do ‘syntax command', they get the proper information). However, we can
make them more readable by providing a label that gets used instead of the
pattern:
300
Epitaph Online
http://drakkos.co.uk
<indirect:living:here'pets'>
The text in apostrophes is what will be displayed to the user instead of the
pattern itself.
Optional Parameters and Set Choices
We also have the option in a pattern of providing optional parameters (ones that
allow the command to flow properly), and choices from a restricted set. Optional
parameters are indicated by square brackets, and set choices are indicated by
braces:
add_command ("get", "<string> from [the] thing");
The ‘the' is not required in order to make the pattern match, but it won't cause a
problem if it's present. The optional parameters get ignored – they don't get sent
into the parameters of the function.
If we want to provide a set of choices, we can do that too:
add_command ("paint", "wagon {red|blue|green}");
This will match any of the following:
paint wagon red
paint wagon blue
paint wagon green
Nothing else will match. The exact option the player chose will be included in the
args array that gets passed as the fourth parameter to the function.
Direct and Indirect Objects
The different between these is not necessarily obvious. The direct object is the
one in which the function for handling the command resides – it will almost
always, unless you are doing something clever or weird, be this_object(). For
example, if you have a command defined in a room, then the room will be the
direct object. If you have a command defined in a weapon, then the weapon will
be the direct object. My advice is, don't worry about it until such time as you
encounter a situation where an indirect object isn't working the way in which you
need.
You could use it productively in the meantime as a resolution for which object to
which you are referring. If you're wearing a pair of boots:
add_command ("kick", "<indirect:living> with <direct:object>");
301
Epitaph Online
http://drakkos.co.uk
This would work almost the same as just using a string ("<indirect:living> with
boots") except that it allows for more complex pattern matching and gives you the
object of ensuring that the boots are being worn and such.
Indirect objects are everything else – anything that's used by a command but isn't
the object in which the command is defined.
Passed Parameters
Now that we've spoken a bit about how add command works, let's talk about the
parameters that get passed to the function. There are five of these, and they each
contain different information.
The first parameter is an array of indirect objects – all the objects that matched
the pattern that we set. So if we have a pattern of <indirect:object:me> and
someone uses ‘sword' for that, it gives us a list of all the objects in the player's
inventory that matches the word ‘sword'.
If we have more than one check for indirect objects in our pattern, we get an array
of arrays instead, each array containing a list of objects that matches the pattern.
For example:
add_command ("cut", "<indirect:living> with <indirect:object:me>");
Our player has this command, and types ‘cut drakkos with knife'. Our first
parameter then would contain an array of arrays, the first containing all the living
objects that match the name ‘drakkos', and the second containing all the objects
in the player's inventory matching the name ‘knife'. These get passed in the order
in which the objects were matched.
The second parameter is the string that was matched for any direct:object pattern.
You can safely ignore this for now.
The third parameter is the list of string matches for indirect objects. Ignore that
too for the moment.
The fourth parameter contains all the arguments that the user provided to the
command – they're the blanks that the user filled in. They get populated in order,
so if the pattern is:
add_command ("test", "<string> with <string> and <string>");
And the command is:
> test bing with bong and bang
The args array will be:
({"bing", "bong", "bang"});
302
Epitaph Online
http://drakkos.co.uk
Finally, the last parameter is the pattern that matched the user input. If a
command has many different patterns (which is common in guild commands and
such), this is the one that met the requirements of the user input.
Tannah's Pattern Matcher
Adding commands and working out what the parameters are is horrendously
complicated. Luckily, Tannah@Discworld, back in the dawn of the world, wrote a
wonderful tool for helping you get it clear in your head – Tannah's Pattern
Matcher, which may be found in /items/creator/ matcher.
The matcher allows you to add commands to it and then execute them – as a
result, it gives you what the parameters are for the command as added. Let's look
at a simple example of using it:
> add command bing with pattern "<indirect:any-living>"
You add the command "bing" with the pattern "<indirect:any-living>" to
Tannah's Pattern Matcher.
> bing citizen
Indirect objects: ({ /* sizeof() == 1 */
/d/forn/genua/chars/citizen#5116424 ("citizen")
})
Direct match: 0
Indirect match: "citizen"
Args: ({ /* sizeof() == 1 */
"citizen"
})
Pattern: "<indirect:any-living>"
I honestly can't emphasise enough how useful this can be to see what the impact
of various kinds of patterns are when using add commands. My advice is to clone
yourself one of these and try it out with every combination you can imagine until
you are 100% sure of what the patterns are and how they work. It'll make the rest
of your life as a creator so much easier.
When you read the matcher, it will show you what commands you have added:
You read Tannah's pattern matcher:
The pattern matcher is currently set to test the following commands and
patterns:
[0] "bing", "<indirect:any-living>"
See 'syntax add' and 'syntax remove' to modify the list.
Make this tool your friend, you won't regret it!
303
Epitaph Online
http://drakkos.co.uk
Conclusion
Add command is the tool that lets you add worlds of functionality to your objects.
As befitting a complex, powerful function it's not the easiest thing in the world to
learn how to use. However, once you have mastered it you will find it a breeze to
add complex, interesting functionality to everything you create.
Next we'll look at how we use add_command to build the user interface for our
library quest, so make sure you understand what we've spoken about in this
chapter. Read it over a few times if the next chapter doesn't make sense - the
assumption will be that commands, functions, the parameters they use, and the
patterns are all clear to you.
304
Epitaph Online
http://drakkos.co.uk
The Library Room
Introduction
Now that we’ve spoken in some depth about add_command and how it works, we
can start to build the front-end of our library quest. For this, we need a room (the
library) which will contain the usual Room Related Things as well as the code we
developed to handle the manipulation of the books and categories. It’s our job
now to craft a compelling environment in which players can easily participate in
the quest.
The Room
The room we’re going to develop is going to inherit from the inside room
inheritable we created earlier. We need to do a little retrofitting and expansion to
make our room work properly, but our starting point will look like this:
#include "path.h"
inherit INHERITS + "inside_room";
class book {
string title;
string author;
string blurb;
string *categories;
}
class book *allBooks = ({ });
mapping sorting;
void reset_sorting();
void setup_quest();
void setup() {
set_short ("main library");
add_property ("determinate", "the ");
set_long ("This is the main part of the library.
"discarded books thrown everywhere!\n");
set_light (100);
It's a mess, with "
add_exit ("west", ROOMS + "zombieville_09", "door");
setup_quest();
reset_sorting();
}
void add_book(string book_title, string book_author) {
class book newBook;
string my_blurb;
string *cats = ({"romance", "action", "childrens"});
string my_cat;
string *valid_blurbs;
mapping blurbs = ([
"romance" :
305
Epitaph Online
http://drakkos.co.uk
({
"A romantic tale of two estranged lovers",
"A heartbreaking tale of forbidden love!",
"A story of two monkeys, with only each other to rely on!",
}),
"action" :
({
"A thrilling adventure complete with a thousand elephants!",
"An exciting story full of carriages-chases and brutal sword-"
"fights!",
"A story of bravery and heroism in the trenches of Koom Valley!",
}),
"childrens":
({
"A heart-warming tale of a fuzzy family cat!",
"A story about a lost cow, for children!",
"A educational story about Whiskerton Meowington and his last "
"trip to the vet!",
})
]);
my_cat = element_of (cats);
valid_blurbs = blurbs[my_cat];
my_blurb = element_of (valid_blurbs);
newBook = new (class book,
title: book_title,
author: book_author,
blurb: my_blurb,
categories: ({my_cat}));
allBooks += ({ newBook });
}
string *query_categories() {
string *cats = ({ });
for (int i = 0 ; i < sizeof (allBooks); i++) {
foreach (string c in allBooks[i]->categories) {
if (member_array (c, cats) == -1) {
cats += ({ c });
}
}
}
return cats;
}
void setup_quest() {
add_book ("Some stuff about you", "you");
}
int find_book (string title) {
for (int i = 0; i < sizeof (allBooks); i++) {
if (allBooks[i]->title == title) {
return i;
}
}
return -1;
}
int assign_book_to_shelf (string book, string category) {
int i = find_book (book);
306
Epitaph Online
http://drakkos.co.uk
string *cats;
if (i == -1) {
return -1;
}
cats = query_categories();
if (member_array (category, cats) == -1) {
return -2;
}
sorting[book] = category;
}
string query_book_blurb (string title) {
int i = find_book (title);
if (i == -1) {
return 0;
}
return allBooks[i]->blurb;
}
string *query_unsorted_books() {
string *unsorted = ({ });
for (int i = 0; i < sizeof (allBooks); i++) {
if (!sorting[allBooks[i]->title]) {
unsorted += ({ allBooks[i]->title });
}
}
return unsorted;
}
string query_bookshelf_of_book (string title) {
return sorting[title];
}
void reset_sorting() {
sorting = ([ ]);
}
Phew, that’s quite a lot, but it sets us up nicely for the code to follow. We've
talked about all of this in the previous chapters, so go back and have another look
over them if this doesn't all make sense.
To begin with, let’s set the scene for our players. We need to ensure they have a
way of getting all information they need through interacting with the room. First
of all, they need to know what the books are that need sorted. Let’s tie that into
an add_item. Because it’s going to be dynamic as to what the add_item should
say, we need to use a function pointer (we'll talk about these at the end of this
text). The skeleton for that would be as follows:
string books_to_sort();
void setup() {
// Rest of setup as before.
add_item ("book", (: books_to_sort() :));
}
307
Epitaph Online
http://drakkos.co.uk
string books_to_sort() {
return "blah";
}
Now, we need to build our books_to_sort() function. It should have the following
behaviour:


If there are books that have not been shelved, we should list those books
If there are no book left to shelf, it should indicate that with a special
message.
Lucky ducks that we are, we already wrote a method to do the bulk of this work
for us – query_unsorted_books. We just need to tie that into this method:
string books_to_sort() {
string *unsorted = query_unsorted_books();
if (!sizeof (unsorted)) {
return "All the books have been neatly sorted away.";
}
else {
return "The following books are strewn around the library: " +
query_multiple_short (unsorted);
}
}
So, that’s step one – giving the player the cue as to what remains to be done. The
next step is to provide a way to find out what’s been done so far – specifically,
what books are currently on which bookshelves. There are many ways we could do
this:



Allowing players to look at each bookshelf directly
Looking at bookshelves gives the state of all books in all categories
Looking at books individually tells which bookshelf they are on.
I favour a single add_item providing all information, because I feel it gives
maximum usability with minimal frustration. So let’s add a bookshelf item:
add_item (({"shelf", "bookshelf"}), (: books_on_shelves() :));
We also need a function for handling the text for when the player looks at the
shelves. Our sorting mapping actually contains this information already, so we
just need to process it and print it out 'real nice like':
string books_on_shelves() {
string ret = "";
if (!sorting || !sizeof (sorting)) {
return "No books have been sorted onto the shelves.";
}
else {
foreach (string book, string category in sorting) {
ret += "The book \"" + book
+ "\" has been put on the shelf for the category \"" + category
+ "\"\n";
308
Epitaph Online
http://drakkos.co.uk
}
return ret;
}
}
The final piece of information we need to provide is what categories are actually
available. We could build that into the bookshelves, or provide a cue to look at
another add_item. I favour the latter as it’s not information that is constantly
updated and we risk overloading a player by bundling all the information into the
same place. So we change our bookshelf add_item a little:
string books_on_shelves() {
string ret = "The bookshelves are arranged according to categories.
";
if (!sorting || !sizeof (sorting)) {
ret += "No books have been sorted onto the shelves.";
}
else {
foreach (string book, string category in sorting) {
ret += "The book \"" + book
+ "\" has been put on the shelf for the category \"" + category
+ "\"\n";
}
}
return ret;
}
And then we add in an item that gives us the categories:
string book_categories() {
return "The following categories are labelled on the bookshelves: " +
query_multiple_short (query_categories());
}
Now all that we need is a mechanism for allowing players to shift books from the
main pile to a particular category, and for viewing the blurb on a book. A pair of
add_commands would do nicely here, don't you think?
Guess the Syntax Quests
I am as guilty of this as anyone, but we must be mindful when coding an
add_command to make it so that the syntax is obvious. Guess the syntax quests
are frustrating for everyone, and usually revolve around a creator having picked
an unusual word for a command (often without realizing it wasn’t intuitive), and
then providing no hints later as to what the right command actually is.
We need to avoid these, but it’s not easy. One good way is to provide multiple
command syntaxes that point to the same function, or just make sure you pick
clear words from the start. Hints as to the right words to use should also be
liberally sprinkled around your quest. For example, if we were to use the
command ‘sort’, our book item might suggest that:
309
Epitaph Online
http://drakkos.co.uk
string books_to_sort() {
string *unsorted = query_unsorted_books();
if (!sizeof (unsorted)) {
return "All the books have been neatly sorted away.";
}
else {
return "The following books are strewn around the library: " +
query_multiple_short (unsorted) + ". They are just begging to "
"be given a good sorting.";
}
}
If you design a properly dynamic quest, you might even consider making the
instructions available in a help-file, or on a web-page. The logical process you go
through to solve the quest should be the task, not finding the actual commands
you need to use.
Viewing the Blurb
Our books are not items. They could be, but they’re not – players can’t hold them,
and they certainly can’t open them up and read them. However, ‘read’ is a
command that will automatically be attempted if people want more information
from the book, and so we’ll provide a ‘read blurb on book’ command. We should
also give the hint to players that there are blurbs to read:
string books_to_sort() {
string *unsorted = query_unsorted_books();
if (!sizeof (unsorted)) {
return "All the books have been neatly sorted away.";
}
else {
return "The following books are strewn around the library: " +
query_multiple_short (unsorted) + ". They are just begging to "
"be given a good sorting. Each has a blurb on the back that "
"may help in discovering to what category they belong.";
}
}
So, first we add the command – it goes into the init method:
void init() {
::init();
add_command ("read", "blurb on <string'book'>");
}
And then we add the code that handles the functionality. It’s not complex, we
already have methods to do everything we need to do:
int do_read (object *indirect_obs, string dir_match, string indir_match,
mixed *args, string pattern) {
string book = args[0];
310
Epitaph Online
http://drakkos.co.uk
string blurb;
blurb = query_book_blurb (book);
if (!blurb) {
add_failed_mess ("That book does not seem to exist.\n");
return 0;
}
tell_object (this_player(), "The blurb on the book reads: " +
blurb + "\n");
add_succeeded_mess ("$N pick$s up a book and read$s the back.\n",
({ }));
return 1;
}
With that, the blurb has been dealt with. Except, not entirely – it doesn’t take into
account language skills or such. We’ll come back to that later.
The Sort Command
Now that we can read the blurb on the back of a book, let’s give ourselves a sort
command. As before, we put it in the init method of our room. It’ll need to take in
two pieces of information – the book we want to sort, and the category into which
we want to sort it:
void init() {
::init();
add_command ("sort", "<string'book'> into [category] <string'category'>");
add_command ("read", "blurb on <string'book'>");
}
int do_sort (object *indirect_obs, string dir_match, string indir_match,
mixed *args, string pattern) {
string book = args[0];
string category = args[1];
return 1;
}
So, when our player wants to take a book and sort it into a category, they type:
> sort book_name into category category_name
Or just:
sort book_name into category_name
The syntax message should hopefully be self-explanatory. No need to guess the
syntax, it’s all there wrapped up in the command. That’s good practise – a bad
syntax is bad for everyone.
311
Epitaph Online
http://drakkos.co.uk
So, what do we need to do in our command? Well, we need to provide a way of
giving useful feedback to the player – for example, if they are trying to sort a
book that doesn’t exist, or into a category that isn’t there, we should tell them
that:
int do_sort (object *indirect_obs, string dir_match, string indir_match,
mixed *args, string pattern) {
string book = args[0];
string category = args[1];
if (find_book (book) == -1) {
add_failed_mess ("That book does not seem to exist.\n", ({ }));
return 0;
}
if (member_array (category, query_categories()) == -1) {
add_failed_mess ("That category does not seem to exist.\n", ({ }));
return 0;
}
return 1;
}
Finally, if it turns out that they are trying to sort a valid book into a valid category,
then let it be so:
int do_sort (object *indirect_obs, string dir_match, string indir_match,
mixed *args, string pattern) {
string book = args[0];
string category = args[1];
if (find_book (book) == -1) {
add_failed_mess ("That book does not seem to exist.\n", ({ }));
return 0;
}
if (member_array (category, query_categories()) == -1) {
add_failed_mess ("That category does not seem to exist.\n", ({ }));
return 0;
}
assign_book_to_shelf (book, category);
add_succeeded_mess ("$N sort$s the book \"" + book +
"\" into category \"" + category + "\"\n", ({ }));
return 1;
}
Finally, we need to add in a function that checks to see if the quest has been
completed – if our current library state matches the desired end-state (which is
that all books are in the right category). That’s easy to do, since we have a pretty
adaptable data structure in place. We need to check the following:


If there are any unsorted books, then the quest is not complete.
If there are any books in the mapping that don’t exist in the database
(which shouldn’t happen, but it’s nice to make sure), we bail out.
312
Epitaph Online


http://drakkos.co.uk
If a book is shelved in an incorrect category (as in, one that it doesn’t have
in its category array), we bail out.
If none of these things are true, the quest is complete and our player should
be told so.
So, we translate that into a method:
int check_complete() {
int i;
if (sizeof (query_unsorted_books())) {
return 0;
}
foreach (string book, string category in sorting) {
i = find_book (book);
if (i == -1) {
return 0;
}
if (member_array (category, allBooks[i]->categories) == -1) {
return 0;
}
}
return 1;
}
Finally, we hook this function into our sort command:
int do_sort (object *indirect_obs, string dir_match, string indir_match,
mixed *args, string pattern) {
string book = args[0];
string category = args[1];
if (check_complete()) {
add_failed_mess ("The library is immaculately organised.
Don't go "
"spoiling it now.\n", ({ }));
return 0;
}
if (find_book (book) == -1) {
add_failed_mess ("That book does not seem to exist.\n", ({ }));
return 0;
}
if (member_array (category, query_categories()) == -1) {
add_failed_mess ("That category does not seem to exist.\n", ({ }));
return 0;
}
assign_book_to_shelf (book, category);
if (check_complete()) {
tell_object (this_player(), "If this were an active quest, you "
"would have just gotten a wodge of XP!\n");
}
add_succeeded_mess ("$N sort$s the book \"" + book
+ "\" into category \"" + category + "\"\n", ({ }));
return 1;
}
313
Epitaph Online
http://drakkos.co.uk
And voila – one quest, coded and ready to go. We still need to add in the quest
accepting and hand-in framework, but we'll talk about that later when we discuss
poor old Agnes.
Except, not quite...
Having gotten the core of the quest done, we need to do some fine-detail work to
make it clear and simple to use. For one thing, we have a fairly awkward user
interface problem:
> sort some stuff about you into action
That book does not seem to exist.
> sort Some stuff about you into action
If this were an active quest, you would have just gotten a wodge of XP!
You sort the book "Some stuff about you" into category "action"
Oh no! The reason for this is that we have capitalization in our book dataset, and
so not only the letters must match, but the case of the letters must match too. We
can resolve this by liberal use of lower_case and cap_words throughout our code.
As a convention, it is always best to store string data in lower case (unless casing
is important) and capitalize it as it is shown to the user. So when we add a book,
we could add it like so:
void add_book(string book_title, string book_author) {
// Code as usual
book_title = lower_case (book_title);
// Code as usual
}
And then when the user enters the book and category to sort, we do the same:
string book = lower_case (args[0]);
string category = lower_case (args[1]);
Now we have no need to concern ourselves about the capitalization of user input.
It’s all in the same format. However, it will look quite bad if we always output in
lower case, so we can make sure that the ‘user-facing’ representation gets a little
bit of polish before it gets to the player. For example, in books_on_shelves():
else {
foreach (string book, string category in sorting) {
ret += "The book \"" + cap_words (book) +
"\" has been put on the shelf for the category \""
+ cap_words (category) + "\"\n";
}
}
314
Epitaph Online
http://drakkos.co.uk
And in our do_sort:
add_succeeded_mess ("$N sort$s the book \"" + cap_words (book)
+ "\" into category \"" + cap_words (category) + "\"\n", ({ }));
There are some areas of our code where that’s slightly tricky to do, such as when
we’re working with query_multiple_short and arrays. We can easily write a method
that handles that for us though:
string pretty_array_output (string *arr) {
for (int i = 0; i < sizeof (arr); i++) {
arr[i] = "\"" + cap_words (arr[i]) + "\"";
}
return query_multiple_short (arr);
}
And then use it in place of each of the calls we have to query_multiple_short, like
so:
string book_categories() {
return "The following categories are labelled on the bookshelves: " +
pretty_array_output (query_categories());
}
And:
string books_to_sort() {
string *unsorted = query_unsorted_books();
if (!sizeof (unsorted)) {
return "All the books have been neatly sorted away.";
}
else {
return "The following books are strewn around the library: " +
pretty_array_output (unsorted) + ". They are just begging to "
"be given a good sorting. Each has a blurb on the back that "
"may help in discovering to what category they belong.";
}
}
These kind of helper functions can greatly simplify the task of building a
compelling user experience in your rooms, and also greatly increase the
consistency of your interface. If we only occasionally use quotation marks, then
we make our quest harder to do because people can’t trust the visual cues. We
don’t want that – either we do it everywhere, or we do it nowhere. In between is
worse than not doing it at all.
Now, back to our blurb. As it stands, it works – but it doesn’t work properly
because we have a fairly complex language system on Epitaph. What happens if
someone who has no knowledge of English tries to read our blurb? They shouldn’t
be able to do so. Luckily, our message system has a nice way of allowing you to
turn any arbitrary bit of text into language specific text, like so:
$L$[read:english]This text will be unreadable to anyone who doesn't know
315
Epitaph Online
http://drakkos.co.uk
English$L$
So therein lies our solution:
tell_object (this_player(), "The blurb on the book reads: "
+ "$L$[read:english]" + blurb + "$L$\n");
And then Bob is the brother of your mother or your father!
Conclusion
That’s a quest we just did. It’s easily the most involved thing we’ve done in any of
this material, but hopefully you’ll have seen it wasn’t too bad, Indeed, it’s actually
a good deal more complicated than many quests – it has random elements that
allow it to be entirely dynamic – plus, the more books we add, the more variation
can come into the quest.
This isn’t a quest room as it would necessarily appear in the game – instead, it’s a
starting point that you can play about with. There are many, many ways to make
this quest better than it is. Really, the limit is only your imagination.
This has been quite a complex chapter, and we haven’t yet touched on the output
of this quest – the secret code that feeds into the quests to follow. We’ll discuss
that later. For now, let’s bask in the warm glow of a hard job well done!
316
Epitaph Online
http://drakkos.co.uk
Quest Handlers
Introduction
The code that we have in place now describes the actual quest, but we still have to
actually make the quest available. This requires us to do three things:



Have the quest added to the quest handler
Hook the code we have written into the player library
Create the start and end point of the quest
In this chapter, we'll look at the process of setting up our code to interface with
the two handlers dedicated to managing quests.
The Handlers
There are two handlers that exist to manage the complexity of quests. The first of
these is the quest handler itself, which holds information about each of the
quests such as what they are called, the hints associated with them, and the level.
The second handler is the library, and the library holds the records of a specific
player with regards to the quests they have done and any quest info that has been
associated with them.
The library is what we'll spend most of our time manipulating. Before we can do
that, we need to include library.h to make available the library handler to our
code.
First, let's look at the one place in our code that currently has to handle the
awarding of the quest:
if (check_complete()) {
tell_object (this_player(), "If this were an active quest, you "
"would have just gotten a wodge of XP!\n");
}
If we want to make this quest operational, we replace this with a call to set the
appropriate player criteria in the library. When it comes time for the player to
hand in the quest, then the quest handler will check the player's criteria value
against the criteria value in the quest, and award (or refuse to award) accordingly.
First we define the name of the quest (the same one that we had in our quest data
file above) as QUEST_NAME, and then we call set_player_query_info on the library:
if (check_complete()) {
LIBRARY->set_player_quest_info (this_player()->query_name(), QUEST_NAME,
"organised library", 1);
}
In cases where we want to award a quest immediately after it has been completed,
317
Epitaph Online
http://drakkos.co.uk
we can use the function set_quest to do that. This quest though, is done through
a quest NPC and so we don't have to – the quest architecture will handle it all for
us.
Now, let's talk about the last bit of functionality we need to add to our quest to
make it fully functioning.
Cast Your Mind Back...
The last bit of the quest was that it was supposed to award the player with a
secret code that worked on the secret door to come. That requires us to have
some method of storing information on our player in a portable, easily accessible
way.
One method we have is the use of properties. We don't want to do that. Properties
are useful, quick and easy – but they tend to linger around and people forget what
they are for. Unless there's a compelling reason to use a property, we should
avoid it.
One of the things that the library gives us is a mechanism for storing questrelated information along with players. This information gets stored as a mixed
variable, and we have the responsibility in our code for working out what kind of
data it should be, and parsing it appropriately. If all we're storing is a single piece
of information, it's fine – otherwise we need to put some code in around the
edges. We'll come back to that later...
We can set player information using the same set_player_quest_info method on
the library. Our criteria will check this data, but we can use it store stuff that the
handler won't check too. Lets generate a random code for a player, and have it
stored along with the player's name:
string generate_random_code() {
int rand = random (9000);
rand += 1000;
return "" + rand;
}
And:
if (check_complete()) {
LIBRARY->set_player_quest_info (this_player()->query_name(), QUEST_NAME,
"organised library", 1);
LIBRARY->set_player_quest_info (this_player()->query_name(), QUEST_NAME,
"secret code", generate_random_code());
}
We can also pull information out with query_player_quest_info. Normally we'd
store this information in the function rather than querying it from the library, but
this is just proof of concept:
318
Epitaph Online
http://drakkos.co.uk
if (check_complete()) {
LIBRARY->set_player_quest_info (this_player()->query_name(), QUEST_NAME,
"organised library", 1);
LIBRARY->set_player_quest_info (this_player()->query_name(), QUEST_NAME,
"secret code", generate_random_code());
code = LIBRARY->query_player_quest_info (this_player()->query_name(),
QUEST_NAME, "secret code");
tell_object (this_player(), "As you put away the last book, a small slip "
"of aged paper flutters to the ground. You can see it has the "
"numbers \"" + code + "\" on it before it crumbles into dust.\n");
}
Now you get a new piece of information when the quest completes:
As you put away the last book, a small slip of aged paper flutters to the
ground. You can see it has the numbers "2023" on it before it crumbles into
dust.
You sort the book "Some Stuff About You" into category "Romance"
This is the information that we need for the later quest, now happily stored and
ready for us to make use of later. This allows us to handle things like storing
quest stage data – for example, as a player progresses through a quest, an integer
value can be stored to represent what state the quest is in for that specific player.
Finally, we can put restrictions on participation with completed quests through the
use of the query_quest_done method, like so, at the top of the sort method:
if (LIBRARY->query_quest_done (this_player()->query_name(), QUEST_NAME)) {
add_failed_mess ("You have already done this quest. Give someone else a "
"chance..\n", ({ }));
return 0;
}
The combination of these four methods will allow you to manage the functionality
for multi-stage quests. The other option is to set formal pre-requisites on quests
(which will stop them being available if you haven't met the pre-requisites), but
either approach is fine.
Our Second Quest
Let's move on from this quest on to our second quest – the partition wall that
leads us on to the corridor of the school. Remember how we planned this quest
out in Being A Better Creator. We have a dynamic setup that requires us to go
through several steps:
1. Randomly generate a location for the partition wall.
2. If the player passes a perception task, show the wall when they look in the
right place.
3. When a player looks at the wall, give them the clues as to how they need to
break through it.
4. The player uses the hints given as to tool needed to break through the wall.
5. Upon doing so, the quest is granted and the player is moved into the
319
Epitaph Online
http://drakkos.co.uk
corridor beyond.
So, now we need to first generate a random location for a wall. Let's place it
behind a particular bookshelf. When the player looks at the categories or the
bookshelves, we want to do a perception check and then generate a location for
them to see the wall. We also want to generate a random tool to break through
the roof. We'll store these as an array and then set the player's quest info
appropriately. Looking for an appropriate task for this, we find
TASKMAP_NOTICE_SOMETHING – we'll make it a very easy check, but it's a check
nonetheless.
string* check_wall_location (object player) {
string category = LIBRARY->query_player_quest_info (player->query_name(),
WALL_QUEST, "selected category");
string tool = LIBRARY->query_player_quest_info (player->query_name(),
WALL_QUEST, "selected tool");
string *cat = ({"romance", "action", "childrens"});
string *tools = ({"crowbar", "hammer"});
int result;
if (tool && category) {
return ({ category, tool });
}
result = TASK_MAPPER->perform_task (player, TASKMAP_NOTICE_SOMETHING,
TTSM_DIFF_INCREDIBLY_EASY, 0, TM_FREE);
switch(result) {
case AWARD :
taskmaster_mess ( player, ({"You feel a little more perceptive."}));
case SUCCEED :
category = element_of (cat);
tool = element_of (tools);
LIBRARY->set_player_quest_info (player->query_name(), WALL_QUEST,
"selected category", category);
LIBRARY->set_player_quest_info (player->query_name(), WALL_QUEST,
"selected tool", tool);
return ({ category, tool });
break;
default :
return ({ });
}
}
We hook this into our shelf add_item so that people looking at the add_item have
a chance of seeing the wall:
string books_on_shelves() {
string ret = "The bookshelves are arranged according to categories.
string *details;
";
details = check_wall_location (this_player());
if (!sorting || !sizeof (sorting)) {
ret += "No books have been sorted onto the shelves.";
}
320
Epitaph Online
http://drakkos.co.uk
else {
foreach (string book, string category in sorting) {
ret += "The book \"" + cap_words (book) +
"\" has been put on the shelf for the category \""
+ cap_words (category) + "\"\n";
}
}
if (sizeof (details)) {
ret += " You can see a crack in the roof above the \"" + details[0]
+ "\" bookshelf. If you had a " + details[1]
+ ", you could probably break through the roof.";
}
return ret;
}
Lovely, huh? Now we need an add_command that lets the player break the wall:
add_command ("break", "wall behind <string'category'> bookshelf");
And then the code to handle it:
int do_break (object *indirect_obs, string dir_match, string indir_match,
mixed *args, string pattern) {
string
string
string
object
category = args[0];
*details;
location, tool;
*valid_tools;
if (LIBRARY->query_quest_done (this_player()->query_name(), HOLE_QUEST)) {
add_failed_mess ("You have already broken through the wall, no need "
"to do it again.\n", ({ }));
return 0;
}
details = check_wall_location (this_player());
if (!sizeof (details)) {
add_failed_mess ("Break what? Why?
"being such a vandal.\n", ({ }));
return 0;
}
You are a bad person for "
location = details[0];
tool = details[1];
if (category != location) {
add_failed_mess ("The wall there is too solid to break.\n", ({ }));
return 0;
}
valid_tools = match_objects_for_existence (tool, this_player(),
this_player());
if (!sizeof (valid_tools)) {
add_failed_mess ("You have no tool suitable for breaking through "
"the wall.\n", ({ }));
return 0;
}
321
Epitaph Online
http://drakkos.co.uk
LIBRARY->set_player_quest_info (player->query_name(), WALL_QUEST,
"broken wall", 1);
add_succeeded_mess ("$N break$s through the wall with $I.\n",
({ valid_tools[0] }));
return 1;
}
Our final step is then adding the command that lets the player clamber through
the wall. So that they don't need to keep breaking it, we'll make it conditional on
them having actually completed the quest previously. However, since we're not
actually completing these quests (they are set to inactive so they don't show up in
player scores), we need to comment that bit out to test it.
And there we have it – one room, two quests – it's tremendous value for money by
any standard!
Conclusion
We've made a lot of progress in this chapter – we turned our quest architecture
into an actual quest, and then added in the second of our three quests. Okay, so
the second one isn't especially interesting, but it's there!
We have one more quest to add to our development, and then we have our three.
Then there are all sorts of other exciting things we need to include in our village.
Are you excited? I'm excited – touch your nose if you're excited too!
322
Epitaph Online
http://drakkos.co.uk
Our Last Quest
Introduction
Two quests behind us – we're burning through our list of features here! Now we
add in the third, and then we can devote our attention to the other parts of the
village we are currently ignoring. Our last quest as I am sure you can recall from
Being A Better Creator is to find an electronic keypad behind a painting, and then
enter the secret code that we were given to open a the door to the basement. The
clues that we give to this quest should be provided by the sorted library below –
we should be able to ‘research' details about the portraits and the hints that we
get reveal the answer to the puzzle.
That's the topic for this chapter – researching in the library, and the mechanisms
of pushing portraits.
The Quest Design
The corridor of our school is going to be a portrait gallery, and behind one of
these portraits is an electronic keypad. Finding the portrait will be the quest,
rather than simply entering the code – after all, that's just ferrying a number from
one place to the other.
The puzzle we build then should be logical, possible to solve by diligent effort,
and that effort should be a worthwhile shortcut to simply brute-forcing the quest
by pushing aside every single portrait. Our design then must accommodate this.
First, let's think about how to handle the brute-forcing bit. We have two obvious
courses:


Thousands of portraits
A limited number of guesses
Thousands of portraits could be generated fairly easily by a function, but it seems
like a pretty awkward solution. On the other hand, if we make it so they can push
aside perhaps three portraits in twenty before the state of the puzzle resets, then
it's important that those three portraits are the right, or sensible portraits.
Let's consider what these portraits might actually be – it's a rural school, so they
are likely to be pictures of past headmasters and headmistresses, and perhaps
well-known alumni. It's pretty easy to algorithmically generate twenty or so
suitably headmastery names. Likewise, we can decide on some features that
define each one – size of head, colour of hair, height, weight, eye-colour,
attractiveness, and so on. Then, we can provide a set of clues that uniquely
identify one specific portrait as being worth moving.
We could hard-code each of these portraits, but we like dynamic quests here on
Epitaph, so we'll do them randomly.
323
Epitaph Online
http://drakkos.co.uk
Anyway, enough of that though, we are eager for action! But before we begin
coding, let's talk about a very Epitaphy way of dealing with distributed data
access.
Handlers
We use the word ‘handler' a lot on Epitaph. For new creators, they often conjure
up mental images of horribly complicated and important code, locked away in
mysterious mudlib directories and not for casual perusal. While that's true of a
number of our handlers, the basic concept is very simple.
In our scenario above, we are faced with the task of co-ordinating some
functionality across two separate rooms. We must be able to research people in
the library, and the people we research must be represented in portrait form in
the corridor. We need a way of storing data so that these two objects can have
access to it.
One way to do this is to have one room call functions in another room.
Unfortunately, due to the way rooms work on Epitaph, this is not a reliable
strategy. Rooms unload themselves when no-one is in them so that we reduce
memory load. When a function gets called on an unloaded room, it forces the
room to reload – along with any setup that gets done. So if we generate twenty
random portraits, then simply moving from one room to another may be enough
to change the entire state of the quest for a player. They go from a room
containing one set of portraits to a library referring to another set. That's not
good.
Instead, what we use is a handler – a handler is just a normal object that is
designed to be accessed by multiple other objects. It defines a set of data and
stores it in a single place, and it provides functions for acting on that data.
Henceforth, no room has to store this data because they just make calls on the
handler. Really, the kind of handlers that are associated with small area
developments are mini versions of the more substantial handlers that exist over
entire domains and in /mudlib/handlers. Nonetheless, the concept remains the
same.
There is often some confusion as to the distinction between a handler and an
inherit – they do after all seem to occupy similar roles in coding. The difference
lies in the persistence of data – in an inherit, each object gets its own copy of the
data, and if that data is randomly set up, it'll have a different set of data from all
the other objects. In a handler, there is only one set of data and all objects make
use of that single set. In terms of what inherits allow you to do with shared
functionality, they are almost identical except that handlers are easier to add in
after the fact.
Our taskmapper is a handler. There is no reason why things like perform_task
couldn't be provided as a function as part of STD_OBJECT. However, if you were
writing a piece of code that didn't inherit STD_OBJECT, then you'd need to bind in
324
Epitaph Online
http://drakkos.co.uk
the inherit yourself. Additionally, if you wanted to change the way perform_task
worked, you'd need to force a reboot of the MUD – otherwise you couldn't be sure
which version of the perform_task code any given object would be using at any
time.
In essence, you use an inherit when all you care about is a set of objects having
access to some common set of functionality. You use a handler when you want a
set of objects having access to some common set of data. For our purposes, we
need a handler.
The Portrait Handler
Our handler is not complicated. It doesn't inherit from anything because it's just
an object for storing some data and functionality. We'll put it in a new subdirectory of Zombieville, and include a new define in our path.h:
#define
#define
#define
HANDLERS
INHERITS
ROOMS
ZOMBIEVILLE + "handlers/"
ZOMBIEVILLE + "inherits/"
ZOMBIEVILLE + "rooms/"
Our handler just needs a create method – much like the first look we had at the
code for the library:
void create() {
seteuid (getuid());
}
Now, we build a data representation. Since we've already done some work with
classes, let's make a class for a portrait:
class portrait {
string name;
string title;
string eyes;
string height;
string weight;
string looks;
string hair;
}
class portrait *all_portraits;
void create() {
seteuid (getuid());
all_portraits = ({ });
}
Now we need something to randomly generate each of the different elements of
the portrait. A real quest would be more imaginative in the names we apply here,
but never mind. First, something to generate a random name:
string random_portrait_name() {
string *forename = ({"albert", "eric", "alison", "agnes", "dumbledore",
"wanda", "derek", "doris", "marta", "pauline"});
325
Epitaph Online
http://drakkos.co.uk
string *surname = ({"mactavish", "mckay", "king", "wilson", "gregor",
"stuart"});
return element_of(forename) + " " + element_of(surname);
}
And something to generate a random title:
string random_portrait_title() {
string *titles= ({
"former patron of the school",
"former head of the school",
"former almunus of the school",
});
return "the " + element_of (titles);
}
The rest we can generate a little more easily. However, we need the name of the
wizard to be a unique identifier – how else will we be able to allow players to
research them? So we first need a way to find a specific wizard's portrait. We've
already seen this in action:
int find_portrait (string name) {
for (int i = 0; i < sizeof (all_portraits); i++) {
if (all_portraits[i]->name == name) {
return i;
}
}
return -1;
}
Now, let's make a method that creates an entirely random portrait:
void add_portrait() {
class portrait new_portrait;
string wname;
string wtitle;
string *eyes_arr = ({"blue", "green", "black", "red"});
string *height_arr = ({"tall", "short", "medium-height"});
string *weight_arr = ({"fat", "skinny", "stocky", "plump"});
string *looks_arr = ({"handsome", "ugly", "grotesque", "beautiful"});
string *hair_arr = ({"black", "blonde", "grey", "white", "brown"});
do {
wname = random_portrait_name();
} while (find_portrait (wname) != -1);
wtitle = random_portrait_title();
new_portrait = new (class portrait,
name: wname,
title: wtitle,
eyes: element_of (eyes_arr),
height: element_of (height_arr),
weight: element_of (weight_arr),
looks: element_of (looks_arr),
hair: element_of (hair_arr));
all_portraits += ({ new_portrait });
326
Epitaph Online
http://drakkos.co.uk
}
Then we add a method that gives us the string description of a portrait (what we
get when we look at it). We're going to use an efun called sprintf here – sprintf
lets us perform some sophisticated string parsing without us having to play
around with concatenation and such. It works very similarly to a pattern in an
add_command, except we use a %s to represent a string and we pass the bits to
be filled in as parameters to the function, like so:
string portrait_long (string person) {
int i = find_portrait (person);
if ( i == -1) {
return 0;
}
return sprintf ("This is a portrait of %s, %s. The portrait is of a a %s,
%s person, "
"with %s hair, %s eyes and a %s face.\n",
cap_words (all_portraits[i]->name), all_portraits[i]->title,
all_portraits[i]->height, all_portraits[i]->weight,
all_portraits[i]->hair, all_portraits[i]->eyes, all_portraits[i]->looks);
}
When we call this function with a wizard represented as a portrait, we get
something like the following back out:
This is a portrait of Pauline Wilson, the former patron of the school. The
portrait is of a a medium-height, skinny person, with brown hair, blue eyes
and a ugly face.
Now we just need a mechanism to identify which of these portraits is the one
behind which the secret panel may be found. Exactly how we'd choose to do this
in a real quest for the game doesn't matter – we don't want to lock great quest
ideas away in a tutorial! Aside from the library quest (which would be serviceable
as an actual quest for the game with some modification), these quests are
illustrations of concept rather than quests we'd expect people to enjoy.
What we're going to do next is illustrate why classes are such a good way of
representing data. We need to add in a new element of the class – a ‘history' for
people to find when they research the wizard. We'll assign these semi-randomly by
means of a parameter passed to the add_portrait method:
Step one is to add two new elements to the class:
class portrait {
string name;
string title;
string eyes;
string height;
string weight;
string looks;
string hair;
string history;
int secret_panel;
327
Epitaph Online
http://drakkos.co.uk
}
And then a modification to add_portrait:
void add_portrait(int suspected) {
class portrait new_portrait;
string wname;
string wtitle;
string *eyes_arr = ({"blue", "green", "black", "red"});
string *height_arr = ({"tall", "short", "medium-height"});
string *weight_arr = ({"fat", "skinny", "stocky", "plump"});
string *looks_arr = ({"handsome", "ugly", "grotesque", "beautiful"});
string *hair_arr = ({"black", "blonde", "grey", "white", "brown"});
string *banal_history = ({
"this person donated lots of money to the school.",
"this person was a school sport's day winner.",
"this person dramatically improved school dinners.",
"this person was made, almost entirely, from cheese."
});
string *suspect_history = ({
"this person was known for hiding things behind other things.",
"this person had a gift for secret, sleekit things.",
"this person was a dirty, sneaky little spy.",
});
string hist;
if (suspected) {
hist = element_of (suspect_history);
}
else {
hist = element_of (banal_history);
}
do {
wname = random_portrait_name();
} while (find_portrait (wname) != -1);
wtitle = random_portrait_title();
new_portrait = new (class portrait,
name: wname,
title: wtitle,
eyes: element_of (eyes_arr),
height: element_of (height_arr),
weight: element_of (weight_arr),
looks: element_of (looks_arr),
hair: element_of (hair_arr),
history: hist,
secret_panel: suspected
);
all_portraits += ({ new_portrait });
}
Now, if we pass in a positive number as a parameter, we'll get one of the ‘suspect'
histories. We'll make it so that a secret_panel value of 1 means that the person
might be where the panel is, and 2 means that's where it actually is. Next, we
328
Epitaph Online
http://drakkos.co.uk
need a method to setup the state of the handler:
void setup_data() {
int location = random (10);
int panel;
all_portraits = ({ });
for (int i = 0; i < 10; i++) {
panel = 0;
if (i == location) {
panel = 2;
}
else if (i % 3 == 0) {
panel = 1;
}
add_portrait (panel);
}
}
So here we generate where the panel is going to be, and we make every third
portrait a ‘suspect' one. Now, although we can query the handler as to which
portrait is which, every time the method is called we get a brand new set of data
to work with. Before we're done here though, we need to add in a few more utility
functions:
string query_portrait_history (string wizard) {
int i = find_portrait (wizard);
if ( i == -1) {
return 0;
}
return all_portraits[i]->history;
}
int query_portrait_panel (string wizard) {
int i = find_portrait (wizard);
if ( i == -1) {
return 0;
}
return all_portraits[i]->secret_panel;
}
string* query_portraits() {
string *ret = ({ });
foreach (class portrait p in all_portraits) {
ret += ({ p->name });
}
return ret;
}
And that's pretty much the code we need for our portrait handler, give or take
329
Epitaph Online
http://drakkos.co.uk
some light ornamentation and perhaps some elegant wainscoting.
Back to the Library
So, we're not done with our library yet. Now we're going to add yet another piece
of functionality – the ability to ‘research' things. Some of these things will be
related to other parts of the village, some should be related to other players (as
per our village design in Being A Better Creator), but we also want people to be
able to research wizards to see what they are known for.
The process for doing this should be familiar now – we create an add_command.
However, instead of making use of functions in our room, we're going to make
calls on the handler we just created.
Handlers are usually #defined in a header file somewhere. We have a define for
our handlers directory, but we also want one for the handler we created itself:
#define
#define
#define
HANDLERS
INHERITS
ROOMS
#define
PORTRAIT_HANDLER
ZOMBIEVILLE + "handlers/"
ZOMBIEVILLE + "inherits/"
ZOMBIEVILLE + "rooms/"
(HANDLERS + "portrait_handler")
Notice here that we surround the path in brackets, which we haven't done before.
The reason for this is the pre-processor that does all the clever replacing of our
defines in our code. We'll come back to this.
We need an add_command as usual;
add_command ("research", "<string>");
And the code to handle it:
int do_research (object *indirect_obs, string dir_match, string indir_match,
mixed *args, string pattern) {
string history;
string topic;
topic = args[0];
if (check_complete()) {
add_failed_mess ("The library is too disorganised for you to be able "
"to find any useful information.\n", ({ }));
return 0;
}
history = PORTRAIT_HANDLER->query_portrait_history (topic);
if (!history) {
add_failed_mess ("The library doesn't seem to have any information on "
"that topic.\n", ({ }));
return 0;
}
330
Epitaph Online
http://drakkos.co.uk
tell_object (this_player(), "As far as you can tell, "
+ history + "\n");
add_succeeded_mess ("$N flick$s through some of the books "
"in the library.\n", ({ }));
return 1;
}
Note how we make our call on the portrait handler in exactly the way we've made
calls on the taskmapperin the past. This is why we need to use the brackets in our
define. The pre-processor does the search and replace on the code we write,
putting in the correct values for each of our defines. If we miss out the brackets,
this is what the code looks like to the compiler:
history = "/d/support/learning/zombieville/" + "handlers/" +
"portrait_handler"->query_portrait_history (topic);
Due to the way the MUD evaluates these operators, it attempts to call the function
on the object ―portrait_handler", rather than the fully qualified name. With the
brackets, the code looks like this:
history = ("/d/support/learning/zombieville/" + "handlers/" +
"portrait_handler")->query_portrait_history (topic);
The brackets tell the driver ‘Hey, put all this together before you attempt to call
that function', pretty much exactly in the way you probably remember from your
primary school maths classes.
This ensures the function gets called on the fully qualified
"/d/support/learning/zombieville/handlers/portrait_handler", which is an actual
object it knows how to find.
Conclusion
We only have the corridor of the school to do now to make this quest work, and
that follows on naturally from what we've been discussing in this chapter. We've
got a handler now, and being able to bundle all the functionality into that greatly
simplifies the task of implementing the rest of this quest. Handlers are a common
and powerful approach to development on Epitaph, and you'll find that you will
have cause to make use of them often if developing code a little beyond the
ordinary.
They are nothing to be feared – they are identical to almost every other piece of
code you have seen, and you are very much capable at this point of developing
your own, as we have just done.
331
Epitaph Online
http://drakkos.co.uk
332
Epitaph Online
http://drakkos.co.uk
Finishing Touches
Introduction
And now we round off our fairly lengthy discussion of the Zombieville quests with
the last part of the puzzle – the main corridor of the school. In this room we tie
together all of our quests into a neat, integrated whole. We've already got a
portrait handler, so now all we need to do is provide the mechanism for moving
portraits aside and entering the secret code. Come, take my hand – I have such
wonders to show you.
The Second Level Room
Okay, let's start off with the room itself. It's a simple, humble room – but it's ours.
We begin with a skeleton:
#include "path.h"
#include <library.h>
inherit INHERITS + "inside_room";
void setup() {
set_short ("main corridor through the school");
add_property ("determinate", "the ");
set_long ("This is the main corridor that makes its way through the "
"school. It is lined with potraits of dull people..\n");
set_light (100);
add_exit ("east", ROOMS + "library", "path");
}
And there's a pretty familiar pattern we follow. First, we add in some add_items
that let us represent the view of the data from our handler. One for portraits to
begin with, so that our players can see the portraits available for them to view:
string portrait_long() {
string *portraits = PORTRAIT_HANDLER->query_portraits();
return "There are many portraits hanging around the room. You can see "
"portraits for the following people: " + query_multiple_short
(portraits) + ". Undoubtedly more information about each of them "
"could be researched in the library.\n";
}
Now when we look at the portraits, we'll see something like this:
There are many portraits hanging around the room. You can see portraits for
the following people: pauline king, doris gregor, derek gregor, albert mckay,
doris wilson, marta mactavish, marta king, dumbledore mactavish, dumbledore
wilson and albert stuart. Undoubtedly more information about each of them
could be researched in the library.
Unfortunately, we need to do a little bit of processing so that it actually looks
333
Epitaph Online
http://drakkos.co.uk
good – capitalising each of the names. There are ways we can do this quickly and
easily, but we'll have to do it longhand for now:
string portrait_long() {
string *portraits = PORTRAIT_HANDLER->query_portraits();
for (int i = 0; i < sizeof (portraits); i++) {
portraits[i] = cap_words (portraits[i]);
}
return "There are many portraits hanging around the room. You can see "
"portraits for the following wizards: " + query_multiple_short
(portraits) + ". Undoubtedly more information about each of "
"them could be researched in the library.\n";
}
Now we see something a lot easier on the eye:
There are many portraits hanging around the room. You can see portraits for
the following wizards: Pauline King, Doris Gregor, Derek Gregor, Albert Mckay,
Doris Wilson, Marta Mactavish, Marta King, Dumbledore Mactavish, Dumbledore
Wilson and Albert Stuart. Undoubtedly more information about each of them
could be researched in the library.
We've already handled the research part, so now we just need to add in the
mechanism for handling the quest. Remember, we've decided upon there being a
limit to the number of guesses a player can make regarding the portraits they
work with – let's set it at three guesses. Each wrong guess will result in a clue to
the player, and a correct guess will reveal the secret pad for them to enter the
number.
I'm Looking Through... uh... behind you
Looking behind a portrait is a pretty straight-forward affair – an add_command
does the business as ever:
add_command ("look", "behind portrait of <string>");
And then we need the function to handle it. Remember that we decided that the
secret_panel value of the portrait would define if it was where the number-pad
could be found:
int do_look (object *indirect_obs, string dir_match, string indir_match,
mixed *args, string pattern) {
string head;
int panel;
int h;
head = lower_case (args[0]);
h = PORTRAIT_HANDLER->find_portrait (head);
if (h == -1) {
add_failed_mess ("You can't find a portrait of that person...\n",
334
Epitaph Online
http://drakkos.co.uk
({ }));
return 0;
}
panel = PORTRAIT_HANDLER->query_portrait_panel (head);
if (panel == 2) {
tell_object (this_player(), "There is a number-pad behind the "
"portrait of " + cap_words (head) + "! It's just waiting "
"for you to enter a number.\n");
}
add_succeeded_mess ("$N move$s aside a picture and look$s behind it.\n",
({ }));
return 1;
}
Now we need to decide how to handle the finding of the keypad. We could create
an actual keypad object that gets cloned into the room when a player finds it –
that would work. Or, we could simply update the player's quest info with the
appropriate stage of the quest. Either of these is a workable solution, but we're
going to go for the latter because it removes a number of complications (for
example, should a player that wanders into a room after another player be able to
see the secret number pad?). We'll make entering the code handled by another
add_command, and make the entrance criteria of that command linked to the
quest stage the player is at. Simple!
This quest in the quest handler is stored as ‘art investigator', so that's the quest
we're going to define. Remember too that we need access to the quest info for
‘visiting librarian' in order to determine if the code entered is correct . We define
our door quest as DOOR_QUEST, and incorporate an update of the quest info in
the success:
if (panel == 2) {
tell_object (this_player(), "There is a number-pad behind the "
"portrait of " + cap_words (head) + "! It's just waiting "
"for you to enter a number.\n");
LIBRARY->set_player_quest_info (this_player()->query_name(),
DOOR_QUEST, "found pad", 1);
}
That's the portrait side handled – most of the work is done in the handler, so we
just need to provide methods to query the various elements. Next, we move on to
handling the secret code.
Ecretsay Odecay
To begin with, the add_command:
add_command ("enter", "code <string>");
And then the functions to handle it:
335
Epitaph Online
http://drakkos.co.uk
int do_enter (object *indirect_obs, string dir_match, string indir_match,
mixed *args, string pattern) {
string entered_code = args[0];
string needed_code;
if (!LIBRARY->query_quest_done (this_player()->query_name(),
DOOR_QUEST)) {
add_failed_mess ("Enter what into what?
({ }));
You're crazy, dude.\n",
return 0;
}
needed_code = LIBRARY->query_player_quest_info
(this_player()->query_name(), LIBRARY_QUEST, "secret code");
if (entered_code != needed_code) {
add_succeeded_mess ("$N enter$s a code on a secret number pad, but "
"nothing happens.\n", ({ }));
return 1;
}
add_succeeded_mess ("$N enter$s a code on a secret number pad.\n",
({ }));
call_out ("transport_player", 0, this_player());
return 1;
}
Note the use of the call_out in the do_enter method – unfortunately, the nature of
message parsing on the MUD means that it's sometimes difficult to enforce the
proper order in which we want messages to be printed. The call_out ensures that
our function has time to complete before our new messages get shown.
Random Guessing
We noted in an earlier chapter that we didn't want players to simply look behind
every single portrait in order to find the secret pad – we want them to have a
limited number of guesses. That's pretty easy to handle – in the room, we hold a
mapping that stores how many guesses a player has had. We can clear it every
time reset is called:
mapping guesses;
void reset() {
::reset();
guesses = ([ ]);
}
Then in the do_look method, we incorporate the code for handling the number of
guesses along with a message indicating how close the player has come to using
336
Epitaph Online
http://drakkos.co.uk
up their last chance. Of course, these messages are just proof of concept – real
quests don't have quite such feeble justification. Or at least, they shouldn't!
num_guesses = guesses[this_player()->query_name()];
num_guesses += 1;
guesses[this_player()->query_name()] = num_guesses;
if (num_guesses > 3) {
add_failed_mess ("None of the portraits will move for you!
({ }));
return 0;
}
How odd.\n",
switch (num_guesses) {
case 1:
tell_room (this_object(), "The painting moves easily, but there is "
"an rumble of creaky machinery that appears and then fades
away...\n");
break;
case 2:
tell_room (this_object(), "It is hard to move the portrait - as if "
"someone was holding on from the other side of the frame. Odd.\n");
break;
case 3:
tell_room (this_object(), "It is almost impossible to move the "
"portrait. It's like it has suddenly become mechanically glued to "
"the wall!\n");
break;
}
Now each player gets a maximum of three attempts to find the secret panel per
reset. If we want to be Complete Bastards, we can even make it so that the entire
state of the quest is set up anew every reset:
void reset() {
::reset();
guesses = ([ ]);
PORTRAIT_HANDLER->setup_data();
tell_room (this_object(), "There is a grinding of machinery, and all the "
"portraits in the room are shuffled around as if they were a deck of "
"cards being shuffled by Escher.\n");
}
And that's it – all three quests in place. They are crying out for polishing and
prettying up, but the process of building the quests is complete. There is always
more we can do, and interested creators are advised to spend time looking at how
they can adapt these quests to be better and more interesting – it's always a
valuable lesson to expand on a framework that is already provided.
What Kind Of Day Has It Been?
So, what have we learned over the past few chapters? Well, quite a lot! However,
what we haven't learned really is to write quests. What we've learned is how to
337
Epitaph Online
http://drakkos.co.uk
write these specific quests, and use the tools that are available to build a back-end
architecture and a user front-end. Every quest is different though – like delicate
little snow-flakes. For each one, you'll need to go through the same process, but
the results will always be different:



Choose a data representation
Implement methods for managing that representation
Put in place a user interface for those methods
The important thing to take away from these last chapters is the technical tools
we have been discussing:





classes
inheritables
add_commands
handlers
the quest library
These will serve you in good stead for putting together the back-end of any quest
you can imagine. The limit is really your imagination. The quests that we have put
in place have shown us examples of all of these tools being used in concert to
achieve a fairly complex end.
Conclusion
Three quests – that's not to be sniffed it. Fair enough, they're not particularly
good quests, and lack a certain panache – but we'd be doing ourselves a
disservice if we hid all our best ideas in a set of tutorial documents. Nonetheless,
what we have are three operational quests involving dynamic quest design and a
consistent narrative. Understanding how these three quests are put together is the
key to developing your own original and much more interesting developments.
Now that we have finished with the library (for now), we'll move on to the other
parts of the village. We've still got a boss to write, some NPCs, and a shop full of
lovely tartan merchandise for the fashion conscious survivor. Our village demands
our attention, we must answer its siren call!
338
Epitaph Online
http://drakkos.co.uk
Zombies
Introduction
We've been through some pretty intense material in the past few chapters, so let's
calm the pace a little by talking about our cannon fodder – the zombies who make
up the majority of the wandering population of Zombieville. They are poor souls,
caught by the infection and turned into ravenous, murderous ghouls looking to
eat your tiny brain as an hors d'oeurve.
We know how to develop NPCs – we did that several times as part of Introductory
LPC 1. In this chapter we're going to extend the concept a little to discuss some of
the additional functionality built into NPC objects.
The Zombie Template
Because our zombies are the only wandering NPCs in the village, we want them to
look as different as possible. As such, we won't develop NPCs with static
descriptions – they'll be dynamically generated to ensure variety. So, we create a
chars directory in Zombieville and add it to our path.h:
#define
#define
#define
#define
HANDLERS
INHERITS
ROOMS
CHARS
#define
PORTRAIT_HANDLER
ZOMBIEVILLE
ZOMBIEVILLE
ZOMBIEVILLE
ZOMBIEVILLE
+
+
+
+
"handlers/"
"inherits/"
"rooms/"
"chars/"
(HANDLERS + "portrait_handler")
In our zombie NPCs, we're going to make use of a lot of things we've discussed in
previous chapters to add the dynamics. It's not a complex system, but it's very
flexible. Let's start off simply with the basic framework. Let's create an NPC
inherit, just because we can:
inherit STD_ZOMBIE;
void create(){
do_setup++;
::create();
do_setup--;
if ( !do_setup ){
this_object()->setup();
this_object()->reset();
}
}
We'll use this inherit for all our zombie NPCs, just in case we want to add any
common functionality to the various inhabitants of the area. Notice that we
inherit STD_ZOMBIE here - as you should expect by now, all our neat zombie
339
Epitaph Online
http://drakkos.co.uk
related functionality is stored in here.
Setting up the NPC involves us making use of some randomised elements – we
want to set the long description based on the type of zombie it is, which requires
us to first set up an array of choices and then select one of those at random:
string *types = ({"spinstress", "schoolchild", "farmer"});
And:
string selected = element_of (types);
Now, this will work just dandy - but we have anouther choice as well. Maybe we
don't want there to be an even distribution of these - maybe we want them to
appear in different proportions. There are various ways to accomplish this, but
one way is to use what is known as a roulette wheel. Basically this is a mapping
that defines weights of options, which we then pass into an efun called,
appropriately enough, roulette_wheel:
mapping types = ([
"spinster" : 1,
"schoolchild" : 5,
"farmer" : 3
]);
string type;
type = roulette_wheel (types);
When you use the roulette_wheel sfun, it's just like spinning a real roulette wheel
except all the compartments are differently sized - for our wheel above, we'll get a
spinster once out of every nine times (1 + 5 + 3), a schoolchild five times out of
nine, and a farmer three times out of nine.
Either of these ways are appropriate for setting up the internals, but we're going
to use the roulette wheel just because we haven't before.
Having done this, we can actually setup the various parts of our NPC: #include
"path.h"
#include <npc.h>
inherit INHERITS + "zombie";
void setup() {
mapping types = ([
"spinster" : 1,
"schoolchild" : 5,
"farmer" : 3
]);
string type;
string *ret = ({ });
type = roulette_wheel (types);
set_name (type);
340
Epitaph Online
http://drakkos.co.uk
switch (type) {
case "spinster":
ret += ({ "This is a poor old lady who never got married, and then "
"she became a zombie." });
set_gender (2);
basic_setup ("zombie", "standard", MONSTER_LEVEL_WEAK);
break;
case "schoolchild":
if (random(2) == 0) {
ret += ({ "This is a schoolboy, who became a zombie. It's not "
"the dog that ate his homework, it's the boy who ate his "
"teacher." });
set_gender (1);
}
else {
ret += ({ "This used to be a schoolgirl. Now it's just a "
"flesh-hungry, psychotic automaton." });
set_gender (2);
}
basic_setup ("zombie", "standard", MONSTER_LEVEL_MINION);
break;
case "farmer":
ret += ({"This is a farmer, or at least, someone who used to be. "
"Now the only thing this ghoul is farming is the living."});
set_gender (1 + random (2));
basic_setup ("zombie", "standard", MONSTER_LEVEL_NORMAL);
break;
}
set_long (implode (ret, "
") + "\n");
}
Notice that we take advantage of the flexibility of this kind of system to set up
each kind of zombie slightly differently - schoolchildren are minions, spinsters are
weak, and farmers are of normal strength. Note too the way that we build our
long description too - by creating an array and then imploding it in set_long.
Next, we can make use of the same system that we used for the portraits to
describe each of the girls. First we declare arrays to hold all the choices:
string
string
string
string
*lengths = ({"long", "short"});
*colours = ({"blonde", "brunette", "red", "black"});
*weight = ({"fat", "thin", "skeletal", "plump", "well-built"});
*height = ({"tall", "short"});
We've got two ways in which we can handle the building of the rest of the long
description. If we have something different we want to say for each options, we
can do that through the use of sequential switch statements:
switch (element_of (height)) {
case "tall":
ret += ({"The zombie is unusually tall, which adds an extra dimension "
"of fear to its shambling approach."});
break;
case "short":
ret += ({"The zombie is unusually short, meaning its gnashing jaws are "
"even closer to bits of your body you wouldn't generally want "
"gnawed."});
break;
341
Epitaph Online
http://drakkos.co.uk
}
On the other hand, we can also handle it by simply slotting the selected values
into a preset string - that's a lot quicker, but loses us the opportunity to have
specific descriptions for each aspect. They are both perfectly appropriate ways of
accomplishing this goal, so you should be motivated by how much you think you
can benefit from writing out each options.
Let's look at how we could accomplish it the second way. We are going to handle
the building of the descriptive string a little differently here – We're going to
create our own control codes and use the replace efun on the description:
ret += ({"$C$$pronoun$ is a $height$, $weight$ zombie with $length$, $colour$
hair."});
long_desc = implode (ret, "
");
long_desc = replace (long_desc, ({
"$height$", element_of (height),
"$weight$", element_of (weight),
"$length$", element_of (lengths),
"$colour$", element_of (colours),
"$pronoun$", this_object()->query_pronoun(),
}));
set_long (long_desc + "\n");
Note we don't have a case here for $C$ - that's because it's a general code useable
in any string, and it means 'capitalise the next letter'.
By using the replace efun, we can thread these codes easily throughout a whole
long description - the code will be changed into the appropriate value when the
string is constructed, and what we end up with is something like this:
This is a poor old lady who never got married, and then she became a zombie.
She is a tall, skeletal zombie with long, red hair. The left cheek of the
spinster has been chewed away at some point, exposing grey, chipped bone to
the world. The neck the spinster is cocked at an awkward angle, and visible
bits of bone jut out of the greying flesh.
Finally, we need to set up our chats - zombies aren't great conversationalists, so
we don't need to worry too much about this, just add some generic moans and
groans:
load_chat(10, ({
1, "@moan",
1, "@groan",
1, "' Braaaaaaaains",
1, "' Kiiiiiddddnneeeeeeeysss",
1, ": staggers.",
}));
load_a_chat(10, ({
1, "@moan angrily",
1, "@groan angrily",
}));
342
Epitaph Online
http://drakkos.co.uk
We can dress them as usual through the NPC clother - but if you want to create up
some unique outfits for them, you should consider that as a reader exercise.
Event Driven Programming
One of the things that the MUD does at pre-set intervals is generate events. These
are functions that get called on objects when certain things have happened in the
game. We can provide functions that catch these events and execute code when
the events occur. This is something easier to see in practise than it is to describe
in theory, so let's look at a specific example.
One of the times that the MUD triggers an event is when an NPC or a player dies.
A function gets called on the NPC itself, as well as all of the objects in the
environment of the NPC. The function that gets called is event_death, and it
comes with a number of parameters:
void event_death(object ob, object *killers, object killer, string
room_mess, string killer_mess) {
::event_death (ob, killers, killer, room_mess, killer_mess);
}
The first parameter is the object that died. Killers is the array of objects that made
up the dead NPC's attacker list. Killer is the object that struck the killing blow, and
room_mess and killer_mess are the strings that get displayed to the room and
killer respectively.
So, let's make our NPCs a little more responsive to the world around them, and
express their bloodlust when they see someone killed, and react with excitement
if they were the one to do it.
void event_death(object ob, object *killers, object killer, string
room_mess, string killer_mess) {
if (member_array (this_object(), killers) != -1) {
init_command (": snarls in triumph!", 1);
}
else if (ob != this_object()) {
init_command (": looks longingly at the fresh meat that has arrived in "
"its vicinity.", 1);
}
::event_death (ob, killers, killer, room_mess, killer_mess);
}
You'll see this working if you clone a couple of the girls into your environment,
and then set them fighting against each other. After they've exchanged a few hits,
you'll see something like this:
The farmer shreds the schoolchild's chest with her fingernails.
The schoolchild dies.
The farmer snarls in triumph!
343
Epitaph Online
http://drakkos.co.uk
If all we want to do is have something special happen when an NPC dies, we don't
need to catch the event – we can just provide a second_life function. This gets
executed as part of the natural processing of the death code, and can be used like
so:
void second_life() {
do_command (": was just faking it!
}
That was MURDER!")
Now she will express her poignant last words when she is unceremoniously
executed by an indifferent creator:
The
The
The
The
spinster bites at the schoolchild's right foot.
schoolchild dies.
schoolchild was just faking it! That was MURDER!
spinster snarls in triumph!
Event handling is one of the most powerful techniques you have available for
providing responsive code. The important distinction is that you don't trigger
events in your own code, you just provide code that should be executed when the
event is triggered externally. The code should sit dormant until the trigger event
occurs ‘in the world'.
There are many events that get triggered in the game. For example, we can make
our zombies even more annoying by having them comment on fights in their
environment:
void event_fight_in_progress(object me, object him) {
if (me != this_object() && him != this_object()) {
if (!random (10)) {
do_command (": sniffs the air, eager for the fresh meat to come.");
}
}
::event_fight_in_progress (me, him);
}
Each event has its own set of parameters, so you may have to consult the
documentation to see exactly what information you are working with. For the
event_fight_in_progress method, we have two object parameters - one for the
attacker and one for the defender. Thus, we make it so that our NPC only chats if
they are not involved in the fight – load_a_chat handles what they should be
saying when they are in combat.
Events are not limited to NPCs – we can catch these events in rooms and items
too. Imagine for example we wanted our rooms to magically dest any corpse that
appears – 'Keep Zombieville Tidy' for example. If we want that to happen in a
specific room, we can provide an event_enter function. This is called whenever an
object enters the environment or the inventory of another object. So we'd override
that function in the room where we wanted that to happen - say, for example, in
our main inherit for the village:
344
Epitaph Online
http://drakkos.co.uk
void event_enter(object ob, string mess, object from) {
if (ob->query_corpse()) {
ob->move ("/room/rubbish", "$N appear$s with a pop.",
"$N disappear$s with a pop.");
}
::event_enter (ob, mess, from);
}
Kill a zombie in this room, and the following occurs:
> call do_death() zombie
The spinster dies.
The spinster was just faking it! That was MURDER!
The fresh zombie corpse disappears with a pop.
Keep Zombieville tidy!
The Power of Inherits
Now, let's see why we went to the trouble of creating inherits for Zombieville. One
of the things that we can now do is implement area-wide functionality just by
putting an event function in an appropriate inherit – for example, if we want to
have corpses disappear across the entire village, we put the above event_enter
function into the zombieville_room inheritable.
Unfortunately when we do this and update all our inherits, we get the following
warnings:
/d/support/zombieville/zombies/inherits/outside_room.c line 31: Warning:
event_enter() inherited from both
/d/support/zombieville/zombies/inherits/zombieville_room.c and
/mudlib/inherits/room/basic_room.c (via /mudlib/inherits/outside.c); using the
definition in /d/support/zombieville/zombies/inherits/zombieville_room.c.
before the end of file
Yikes, what does all of that mean??
Well, remember how we talked about the idea of scope resolution? What we have
here is one of the problems that multiple inheritance causes. In zombieville_room,
we have put an event_enter function. However, /std/room/outside already has an
event_enter, and /std/room/basic_room also already has an event_enter. These
warnings are telling us that the driver can't tell which it should use, and so it has
decided to just use one over the other because it doesn't know how to use both.
We resolve this by providing an event_enter in inside_room and outside_room,
and have that function handle the execution of the inherited functions. In
inside_room:
void event_enter(object ob, string mess, object from) {
room::event_enter (ob, mess, from);
zombieville_room::event_enter (ob, mess, from);
}
345
Epitaph Online
http://drakkos.co.uk
And then in outside room:
void event_enter(object ob, string mess, object from) {
room::event_enter (ob, mess, from);
zombieville_room::event_enter (ob, mess, from);
}
These functions override the event_enter functionality, and tells the MUD to call
the inherited event_enter first in basic_room or outside, and then in
zombieville_room. This is the usual approach to resolving this kind of errors, but
in certain complicated situations more complex, tailored functionality may be
required. You can worry about that when it happens to you, though.
Conclusion
Our discussion about NPCs was little more than a chance for us to move the topic
on to a more rich vein of inquiry – the power of events in MUD coding. Events are
powerful and supported at multiple levels of the MUD – you can associate
functionality with a particular thing happening, such as a spell being cast or
someone emoting in the room, or one of the many other events that get triggered.
That's a lot of power for a small, humble function. You'll have cause to explore the
idea further in the code you develop from here on in.
346
Epitaph Online
http://drakkos.co.uk
Agnes McKay
Introduction
We have two additional NPCs that are part of this development – the
headmistresses and Agnes McKay, our survivor. Part of our creation of these NPCs
will be in setting up the relationship between the two – as you will undoubtedly
recall from Being A Better Creator, they are former colleagues and Agnes holds
the key to the mystery of what happened within the school.
There are several things we'll need to put in place to make all of this work – two
NPCs for example! We'll also need some way of making available Agnes' notes on
the player she encounters.
Agnes
Agnes is relatively straightforward herself - she's really just a normal NPC with a
requirement for additional responsiveness. She's also a quest NPC, which requires
us to set up the quests she has, and which she accepts as 'hand-ins'. Her basic
skeleton will look like this:
#include <npc.h>
inherit STD_MONSTER;
void setup() {
set_name ("mckay");
set_short ("Agnes McKay");
add_adjective ("agnes");
add_property ("determinate", "");
add_alias ("agnes");
set_long ("Agnes McKay is the former librarian of the Zombieville school.
"She's not dead yet.\n");
"
basic_setup ("human", "survivor", MONSTER_LEVEL_WEAK);
}
There is nothing new here at all. Let's start off by making her hand out the first of
the quests in our area - we use the add_quest method for this:
add_quest ("visiting librarian");
add_hand_in ("visiting librarian");
The add_hand_in method tells the game that when we have completed our quest,
we can return to agnes and use the 'complete' command to mark ourselves as
having finished. The add_quest method makes it available through the 'accept'
quest, and marks it out as existing for the questsense command.
> syntax accept
accept [quest] from <object>
347
Epitaph Online
http://drakkos.co.uk
> accept quest from agnes
It appears that the library is still on Anges McKay's mind. It would be an
act of considerable kindness if someone were to organise it and put her
troubles to rest. The ones of which she is aware, anyway...
You accept a quest from Agnes McKay.
We can try to complete the quest right away:
> complete visiting librarian for agnes
You have not yet met the completion criteria for that quest.
It's only when our quest info contains the neccessary criteria will the quest
actually complete.
We can add as many quests as we like to her, so let's add the other two quests
that we have:
void setup() {
set_name ("mckay");
set_short ("Agnes McKay");
add_property ("determinate", "");
add_adjective ("agnes");
add_alias ("agnes");
set_long ("Agnes McKay is the former librarian of the Zombieville school.
"She's not dead yet.\n");
"
add_quest ("visiting librarian");
add_hand_in ("visiting librarian");
add_quest ("mister gorbachev");
add_hand_in ("mister gorbachev");
add_quest ("art investigator");
add_hand_in ("art investigator");
basic_setup ("human", "survivor", MONSTER_LEVEL_WEAK);
}
Next, we need to add in an appropriate range of chats and responses to lead
people to the conclusion that here be quests, and to create the narrative structure
for the rest of the area. That's all stuff we learned in our previous two sections,
so let's not dwell on it here.
Librarian Notes and Callbacks
When our player has completed the task of rearranging the library, we're going to
have their name recorded in a journal for all to see - we'll accomplish this by
allowing people to 'research' players along with the names of the people on the
paintings. We already have a portrait handler set up - we'll make use of this
handler to accomplish our goal.
We want this to be based on when a player completes the reorganisation quest,
and we can handle this in several ways - one way is to do it via an event in the
348
Epitaph Online
http://drakkos.co.uk
librarian (the event for when a quest is completed, of course), or we can do it via
what is known as a callback, which is something we define in the quest data file
itself.
Callbacks are functions that get called when a certain thing happens - it's a way to
distribute some of the coding responsibility. It also gives us a way for objects to
say 'hey, you know when this thing happens? Well, drop me a line please when it
does'. The function that gets called is one you decide, but the information that
the function gets sent is handled by the core system.
They get handled in most of the major game systems, and one of those systems is
the quest system - in our quest config file, we can set an on_complete function,
calling a function on our portrait handler. Now, the problem here is that our
config file has no idea about our portrait handler, and we can't make it aware
without including a path file that may end up being moved around, so our
callback would look like this:
::->on_complete:: (: ("/d/your_name_here/zombieville/
handlers/portrait_handler")->register_completion ($1->query_name()) :)
For all the reasons why fixed paths are bad, we won't do it this way. Instead, we'll
catch the event that gets generated on Agnes when a quest is completed - it's
called event_quest_completed and gets two parameters - the object for the player,
and the name of the quest:
void event_quest_completed (object player, string quest) {
if (quest == "visiting librarian") {
PORTRAIT_HANDLER->register_completion (player->query_name());
}
}
Now all we need to do is add in this function to our portrait handler, and we can
make it possible to research players who have completed the quest as well as the
paintings that make up the quest.
Back to the Portrait Handler
Our portrait handler is pretty simple, but there's no reason it has to be shoddy.
There are two kinds of methods that are used in handlers – the first set of
methods define the ‘interface' of the handler – they're the set of methods that
developers should be using in their own code to interact with it. The rest of these
are internal methods – methods that exist to make the work of the handler
progress smoothly. Often these will be set as private methods so that no-one can
accidentally make use of them but it can be useful as a developer for the handler
coder to be able to query them directly with calls and execs. That can't be done
with private methods.
The external interface methods should make sure they are not easily flummoxed
by other coders making use of the calls – for example, what happens if they pass
the reference of a player rather than the name of a player? That kind of thing is
349
Epitaph Online
http://drakkos.co.uk
easy to compensate for in our code, and so we should:
void register_completion (mixed player) {
string name;
if (objectp (player)) {
name = player->query_name();
}
else {
name = player;
}
}
It's likely that Agnes is the only object that will ever call this message, but there's
no guarantee. Developer facing interfaces like this can benefit hugely by being a
little more forgiving with what data they are expecting.
This method will take in a mixed parameter – if it's an object it gets, it sets name
to be the query_name() of the object. Otherwise, it assumes a string and sets
name to be whatever we got as a parameter. Now we can record some random
notes about a player. First, let's have a mapping to store it:
mapping player_notes;
void create() {
seteuid (getuid());
all_portraits = ({ });
player_notes = ([ ]);
setup_data();
}
Then, setup the notes when a player is registered:
void register_completion (mixed player) {
string name;
int gender;
object ob;
string note_text;
if (objectp (player)) {
name = player->query_name();
ob = player;
}
else {
name = player;
ob = find_player (name);
}
note_text = element_of (({
"I met $name$ today. $C$$pronoun$ is a $size$, $quirk$ person and "
"smells vaguely of $smell$. $C$ muttered something about $thing$ "
"and then $comic_event$.",
"When $name$ $comic_event$, I realised that $pronoun$ and I were "
"going to be firm friends, even discounting the terrible smell of "
"$smell$ and the fact $pronoun$ seems fascinated by $thing$.",
}));
note_text = replace (note_text, ({
"$name$", cap_words (name),
"$pronoun$", ob->query_pronoun(),
350
Epitaph Online
http://drakkos.co.uk
"$size$", element_of (({"fat", "skinny", "huge", "small", "skeletal"})),
"$quirk$", element_of (({"weird", "strange", "peculiar"})),
"$smell$", element_of (({"armpits", "donkeys", "cheese", "feet"})),
"$thing$", element_of (({"donkeys", "monkeys", "chunkies", "flunkeys"})),
"$comic_event$", element_of (({"fell down the stairs",
"fell up the stairs", "fell across the stairs",
"stared at some falls"})),
}));
player_notes[name] = note_text;
}
string query_player_notes (string name) {
return player_notes[name];
}
The last piece of the puzzle is to then incorporate this into the research, which we
can do like this:
string query_portrait_history (string head) {
int i = find_portrait (head);
if ( i == -1) {
return player_notes[head];
}
return all_portraits[i]->history;
}
This allows the research command in the library to work, although it will look a
little strange:
> research drakkos
As far as you can tell, I met Drakkos today. He is a small, weird person and
smells vaguely of armpits. muttered something about flunkeys and then fell
up the stairs.
You flick through some of the books in the library.
Cleaning up these messages is left as an exercise for the reader.
One final note on this handler - it's no longer, strictly speaking, a portrait handler.
It handles also the storing of player data. When you find that the language choice
on a handler is no longer appropriate for its functions:


Break it off into several handlers.
Rename it appropriately, and all the methods within.
For this piece of functionality we have added, it seems overkill to create a player
note handler, since we'd need to make the two handlers talk to each other anyway
in order to make it work. Renaming the methods is a better strategy here, and
one that is also left as an exercise for the reader.
351
Epitaph Online
http://drakkos.co.uk
Data Persistence
The last thing we need to discuss is how to make our handler save its state – it
shouldn't be the case that people lose their scant game immortality just because
the MUD reboots.
There are several ways in which data persistence is handled in the MUD. We'll talk
about the simplest of these in this chapter. First of all, let's talk about how the
MUD represents data in a file.
Every data type has a particular kind of text representation that the driver knows
how to parse into a variable reference. When an object is saved in its entirety, it is
saved as a .o file, and that .o file contains the state of each variable in the object,
except those marked as nosave, like so:
no_save int do_not_save_this;
When an object is saved, the state of all its variables are recorded and written to a
file. For example, the .o file associated with the weather handler is as follows:
#/mudlib/handlers/weather.c
pattern ({23,72,3,})
current ({6,54,5,})
variance ({10,75,20,})
mooncycle 3
moonupdate 998721449
This is the .o representation of an object, which contains the variables it is going
to save, and the values those variables have. The MUD knows exactly what it is
supposed to do with this string of text to turn it into an actual variable, it just
needs to know when it needs to load and save things.
We tell the MUD to save an object by using the save_object efun. However, this
sometimes needs a little more instruction to the MUD because of the way our
permissions system works.
Effective User Identifiers
Every object on the MUD has what is called an euid, which stands for ‘Effective
User Identifier'. For creators, your euid is the same as your username. For players,
the euid is PLAYER. For rooms, NPCs and such, it depends on in which directory
the object is located. For domain directories, the euid will be the same as the
domain in which the object resides (so for /d/support/, the euid is ‘Support.
Other objects have a default euid according to the following table:
352
Epitaph Online
http://drakkos.co.uk
Directory
/secure/
/mudlib/
/cmds/
/soul/
/open/
/net/
/www/secure/
/www/
Default EUID
Root
Room
Room
Room
Room
Network
Root
WWW
The euid defines what access an object has to read and write from other
directories on the MUD. Objects in a /w/ directory have the same permissions as
the person who owns the directory – so if the creator draktest has write to /save/,
so too do all objects in /w/draktest/.
To see what directories each of the above euids has access to, you can use the
following command:
permit summary <euid>
For example:
> permit summary WWW
Euid
Path
WWW
RW /save/www
WWW
RW /www
The consequence of this is that if an euid doesn't have write access to a directory,
it can't write there. This ensures at least some measure of protection against
rogue objects doing Bad Things.
Now, this may seem like a fairly irrelevant distraction, but it relates to how saving
and restoring objects works in the MUD. Let's add a function that saves the status
of the handler. First, we add a new define:
#define
SAVE
ZOMBIEVILLE + "save/"
And then we add a save function to the disembowel_handler:
void save_me() {
save_object (SAVE + "portrait_save");
}
Now, when we call this function in our own directory, it works absolutely fine.
However, if someone else calls this function in our directory (someone who does
not have write access to the save directory we defined) it'll give the following
message:
Denied write permission in save_object().
This is because when an interactive object (such as a player or creator) is the
353
Epitaph Online
http://drakkos.co.uk
source of a call like this, the MUD notes it and says ‘Oh-ho, you're not permitted
to do this' and gives an error even though the directory in which the object
resides has access. To get around this, we must say to the MUD ‘it's okay, you can
trust this guy. It's cool', which you do by using the unguarded function:
void save_me() {
unguarded ((: save_object (SAVE + "portrait_save") :));
}
Unguarded says to the MUD ‘this is far as you need to have checked. If this object
has permission, then whoever causes this function to trigger also has permission'.
It's safe to do this for pretty much any code that goes into the game – anyone who
knows how to use this kind of thing For Evil already has access to worse ways to
break our security model.
For restoring the state of the handler, we use the restore_object function:
void load_me() {
unguarded ((: restore_object (SAVE + "portrait_save") :));
}
All that is left at this point is to tie these two functions into the rest of the handler
– load the handler when the object is created, and save the object whenever its
internal state changes. We need to set everything except the player data as no
save, because we don't want to save the bits that get set up randomly for the
quest:
nosave class portrait *all_portraits;
mapping player_notes;
void setup_data();
void save_me() {
unguarded ((: save_object (SAVE + "portrait_save") :));
}
void load_me() {
unguarded ((: restore_object (SAVE + "portrait_save") :));
}
void create() {
seteuid (getuid());
load_me();
if (!all_portraits) {
all_portraits = ({ });
}
if (!player_notes) {
player_notes = ([ ]);
}
setup_data();
}
We also need to make sure that we save every time a name gets added:
354
Epitaph Online
http://drakkos.co.uk
void register_completion (mixed player) {
// Blah blah
player_notes[name] = note_text;
save_me();
}
One last thing to discuss about saving objects – we can also tell the MUD to
compress save files. This is usually a good thing to do, especially when working
with large amounts of data – we just pass an additional parameter to save_object,
like so:
void save_me() {
unguarded ((: save_object (SAVE + "portrait_save", 2) :));
}
Now when the handler saves it saves as a gzipped file – for big files, this will save
huge amounts of disk space and file I/O at a cost of a little CPU. It's a bargain
whatever way you look at it!
Conclusion
Our NPC in this chapter is not an especially complicated one, but it led us into a
discussion of the way file permissions work on the MUD, and what we can do to
restore the state of objects when we need it. Data persistence is not tricky to do –
there's not much more to it than we have talked about here. For very complex, or
very intensive file I/O, we may need to do something a little more tailored. For
most objects and handlers, this is as much as you ever need to know.
355
Epitaph Online
http://drakkos.co.uk
Function Pointers
Introduction
Before we talk about the headmistress, we're going to touch on the topic of
function pointers. We've been using these for a while now, and the final step that
we are going to take is to explain what they are, how they work, and what they
can do for your code. These get used an awful lot on Epitaph, and it's high time
we took some time out from our exciting coding to talk about them properly.
The Structure of a Function Pointer
All the way through our code we've been using variables, and these are just
containers for some information that we want to store. A function pointer is a
variable too, a variable of type function. However, unlike the other data types
which are passive, a function pointer contains some actual code that can be
executed (or evaluated) on command. Function pointers of the type we've been
working with are what I like to refer to as ‘happy code' because they're enclosed in
smiles like so:
function f = (: 1 + 2 :);
One way to think of a function pointer is as a ‘delayed blast function'. It sits there
until someone decides it's time for the code to be triggered through the use of the
evaluate efun:
return evaluate (f);
Now, this example is pretty trivial – the real power of function pointers comes
from how flexible they are. We can make use of placeholder variables inside a
function pointer like so:
function f = (: $1 + $2 :);
Then when we evaluate the function, we can pass parameters to it. They get
handled in the pointer in the order in which they are provided:
return evaluate (f, 10, 20);
When the function gets evaluated, 10 gets substituted for $1, and 20 for $2.
We can bind the function pointer to a local function – this is something we've
done quite a lot of with add_items. Think back to when we made the add_item you
could vault, way back in Introductory LPC:
add_item ("jagged rock", ({
"long", "This is a jagged rock.",
"position", "on the jagged rock",
356
Epitaph Online
"vault", (: do_vault :),
({"kick", "punch"}), "Ow!
}));
http://drakkos.co.uk
That stung!\n",
What we've done here is create a function pointer that binds to the locally defined
function called do_vault. When this function pointer is evaluated, it calls the
defined function. We can even do this with arguments, if we so desire:
int test_function(int a, int b) {
return a + b;
}
int test_pointer() {
function f = (: test_function, 10, 20 :);
return evaluate (f);
}
All of this works perfectly. We can also define function pointers that look, to all
intents and purposes, like normal function calls:
function f = (: test_function ($1, $2) :);
These kind of function pointers cause problems with debugging though – when an
error occurs in one of these, the runtime trace tells us simply that there is a
problem with the pointer, but very little useful information beyond that.
The last kind of function pointer lets us simply write a function and store it in a
variable. This is horrible in all sorts of ways, so please don't do it. However, you
may find other people doing it in other parts of the mudlib, so you should at least
be able to recognize it when it occurs:
int test_pointer() {
function f = function (int a, int b) {
return a + b;
};
return evaluate (f, 10, 20);
}
If I see you doing this anywhere, I will cut you. You have been warned!
All of these are different ways of achieving the same end – create a ‘delayed blast
function' that gets evaluated at a later date. The power of this as a mechanism
can't really be over-stated – it lets you bundle an awful lot of functionality into a
very small space if you know what you're doing. To see the power invested in this
kind of data type, we're going to talk about what I like to refer to as the Four Holy
Efuns.
357
Epitaph Online
http://drakkos.co.uk
The Four Holy Efuns
There are four efuns that, when used in combination with function pointers, will
make available a huge amount of functionality with very little expended effort.
They are implode(), explode(), filter(), and map(). The first two we have seen. The
last two we will talk about in depth in this chapter.
Filter is an efun that takes an array, and then returns another array that contains
only those elements that passed a particular check. This check can be defined as a
local function, or implemented as a function pointer. Within the function pointer,
the argument $1 refers to the ‘current element' in the same way our variable in a
foreach loop refers to the current element of the array we are looping though.
Let's say for example that we wanted to get the list of online creators. One line of
code can do that:
return filter (users(), (: $1->query-creator() :));
The array we provide to the efun is the object array returned from the users()
function. We step over each element of that array, calling query_creator on each.
Those objects that return 1 from that are added to the array to be returned. Those
that return 0 are discarded.
Do you want to get the list of interactive objects in the room in which you are
currently standing? Try this in an exec:
exec return filter (all_inventory (environment (this_player())),
(: interactive ($1) :));
The process is exactly the same – filter steps over each element of the array and
passes it as an argument in the interactive efun. Those objects that return 1 get
returned from the filter.
The sister function of filter is map, and it works in the same way – the difference
is, it doesn't filter out the objects, it gives the return value of whatever function
we provided it. Would you like to get the names of all players online? Well, you can
do this:
return map (users(), (: $1->query_name() :));
The process that map goes through is to get the array provided by users, call
query_name() on each element, and take the result of that function call and add it
to an array to be returned. What we get out of that then is an array containing all
the names of each online player.
Now, here's where we start getting a bit adventurous. The real power of these
efuns comes from when we combine them together. What if you want the names
of all online creators? Really that's the process we went through above, except the
result of one feeds into the other:
358
Epitaph Online
http://drakkos.co.uk
string *online_creator_names() {
object *creators = filter (users(), (: $1->query_creator() :));
return map (creators, (: $1->query_name() :));
}
You will often find these chained together, especially if you need to quickly check
something with an exec:
exec return map (filter (users(), (: $1->query_creator() :)),
(: $1->query_name() :))
Rapidly, code like this becomes very difficult for people to effectively read.
Function pointers give a huge amount of expressive power with a minimum of
coding effort, but the cost is in casual readability of your code. Nonetheless, the
benefits are hard to deny.
Implode can also take a function pointer as its second parameter. When we do
this, it takes the first and the second parameters of the array we are imploding,
and passes them into the function. It then takes the result of that and the next
element, and passes them in, and so on. So imagine if we had an array of
integers, like so:
Index
0
1
2
3
Element
10
20
30
40
And now imagine that we ran the following implode over it:
implode (arr, (: $1 + $2 :));
For the first step of the implode, the function gets elements 0 and 1:
10 + 20
The result of this is 30, and at the next step of the implode, the function gets the
results of the previous evaluation (30), as well as the next element to be imploded
(element 2):
30 + 30
And then at the last, it gets the results of the evaluation plus the last remaining
element:
60 + 40
The result of this implode is to return the value 100 – it sums up all of the
elements in the function pointer in a quick, elegant way.
Explode doesn't permit the use of a function pointer, but it's the natural
companion to implode as thus still remains one of the Four Holy Efuns.
359
Epitaph Online
http://drakkos.co.uk
Taking the four of these together gives tremendous expressive power to a creator
when dealing with strings or arrays. For example, let's say I have the following
string:
"drakkos,ploosk,haug,eek,hambone"
And what I want is to check to see if these people are online, and if they are
display their properly capitalized names. I can do that with a function:
string fix_names (string str) {
string *names = explode (str, ",");
string *online = ({ });
string ret;
for (int i = 0; i < sizeof (names);i++) {
if (find_player (names[i])) {
online += ({ cap_words (names[i]) });
}
}
ret = implode (online, ", ");
return ret;
}
Or I can do it in a much more compact way with the four holy efuns:
return implode (map (filter (explode (str, ","), (: find_player ($1) :)),
(: cap_words ($1) :)), ", ");
You will find much of our code is based around this kind of compact
representation. However, this should all come with a disclaimer – there is very
little that you can do with function pointers that you cannot do with explicit
functions, and these are always more readable and maintainable. Working
effectively with function pointers is the key to understanding lots of the mudlib,
and to making your exec command the best friend you ever had, but there are
only a few situations in which you should really use them. Many of us over the
years have gotten into the bad habit of using them largely automatically, and you
would be doing yourself a favour if you used them only sparingly.
However, we are now in a position to look at the areas where we have used
function pointers in our code, and why we have gone down that route.
Throughout Zombieville (and indeed, Deadville), function pointers were used only
when they were the only way to achieve the necessary ends.
Back to the Library
Look at our library – it's beautiful. It is absolutely bursting with quests, and we
should be proud of ourselves that we have them. However, one last thing remains
– the long description of the library:
set_long ("This is the main part of the library.
"discarded books thrown everywhere!\n");
It's a mess, with "
360
Epitaph Online
http://drakkos.co.uk
Here, we need to have something a little more dynamic – it doesn't make sense to
have this long description when the books have all been sorted away. Instead, the
long description should change with the state of the library. Now, if we have a
long description that can change in the course of play, we can make a dynamic
long description by making use of a function pointer. There are other ways to do
it, but they are awkward. All we do is something like this:
set_long ((: library_long :));
And then a function to return a string based on the state of the library:
string library_long() {
string ret = "This is the main part of the library.
";
if (check_complete()) {
ret += "It is immaculate, with books neatly stored on shelves. ";
}
else if (!sizeof (query_unsorted_books())) {
ret += "It is very tidy, but it doesn't look as if the books have "
"been sorted properly.";
}
else {
ret += "It's a mess, with discarded books thrown everywhere!";
}
return ret + "\n";
}
Now, this is a good way of providing a dynamic long – but there are other options.
If the state of the library is going to change a lot, then it's fine. If it's going to
change once, then consider simply setting the long to be something different at a
later part of the code.
Internally, the set_long that we give the room gets stored as the variable long_d in
/mudlib/mixins/basic/desc. When the long description is queried (for example,
when we do a look in a room), the following function gets triggered:
varargs mixed query_long(string str, int dark) {
if (functionp(long_d))
return evaluate(long_d);
return long_d;
}
This is the difference between setting a function pointer as the long, and setting
the return value of a function. For example, consider the following:
set_long (library_long());
This is evaluated only when the set_long is called – whatever comes out of that
function is what becomes the string stored in long_d. As such, this just creates a
static description – the only difference is where that static description comes
from. If we store it as a function pointer it means that every time the query_long
361
Epitaph Online
http://drakkos.co.uk
function is called, the function pointer gets evaluated. As I'm sure you can
imagine, that's not great for the efficiency of your code if it's not necessary,
especially if your function requires a lot of CPU time to process. We can take that
hit every now and again if there is suitable benefit, but it's not something to get
into the habit of doing.
It is exactly this same principle at work when we set a function pointer to be
evaluated in an add_item. That function gets evaluated every time it is accessed,
so if it's used in place of the descriptive text, it'll be called whenever you look at
the item. If it's used as the response to a command, then it'll be called whenever
that command is triggered.
Function Pointer Support
Function pointers are not supported universally throughout the mudlib – they
require someone to have put the support in place in the low level functions.
However, they are supported widely. For example, if you wish to change the name
of a function attached to an add_command, you can do that with a function
pointer. Let's say we wanted to give players the option to either ‘read' our books
or ‘browse' them, with the same thing happening in either case. We simply use a
function pointer as a third parameter to add_command:
add_command ("read", "blurb on <string'book'>");
add_command ("browse", "blurb on <string'book'>", (: do_read :));
If we use a function pointer in this way, we always need to prototype the function
because the driver will do compile time checking on the code to make sure the
function is present.
We can even use function pointers to simplify the parameter list of the methods
themselves. For example, we don't care about anything other than the blurb the
player entered for the do_read function, and yet we still have a pile of parameters
we need to represent:
int do_read (object *indirect_obs, string dir_match, string indir_match,
mixed *args, string pattern) {
}
We can use the function pointer to say ‘actually, all I want is the first element of
the fourth parameter':
add_command ("read", "blurb on <string'book'>", (: do_read ($4[0]) :));
add_command ("browse", "blurb on <string'book'>", (: do_read ($4[0]) :));
And now, rather than our complicated list of parameters, we just have one string
coming in:
int do_read (string book) {
}
We do that a lot around these here parts. As long as you're clear in your mind as
362
Epitaph Online
http://drakkos.co.uk
to what the little dollar sign numbers mean, it should be pretty simple for you to
work out what information is going into the functions.
Often, we use a different kind of way of supporting functions that are dynamically
called from other functions. Where possible, you should use these rather than
pointers. A case in point is in a load_chat or add_respond_to_with, where we use
the # notation.
These are handled in a different way in the mudlib. They are not function
pointers, they get parsed by lower-level mudlib inherits to do the right thing.
Really all that happens is when this chat or response is selected, the function
checks to see if the first character of that response is a #, and if it is it uses the
function call_other to trigger the function response. This is taken directly from
expand_mon_string in /mudlib/inherits/monster:
switch ( str[ 0 ] ) {
case '#' :
args = explode(str[ 1..], ":");
call_other( TO, args[0], args[1..]... );
break;
The # system is supported erratically throughout our mudlib, but you should use
it instead of a function pointer whenever you can. Function pointers are tricky for
others to read unless they are thoroughly steeped in sin, difficult to debug in
certain cases, and do not benefit from any specialized functionality that comes
from more tailored support in the mudlib.
A Few More Things about Function Pointers
You can pass parameters to a function pointer, but when doing so you can't make
use of local variables:
string *online_creator_names(string is_online) {
object *creators = filter (users(), (: $1->query_name() == is_online :));
return map (creators, (: $1->query_name() :));
}
This will not work – you'll get a compile time error saying ‘You can't use a local
variable in a function pointer'. If you want to use a variable like this, you need to
explicitly force them to be literals rather than variables by enclosing them in $(),
like so:
object *creators = filter (users(), (: $1->query_name() == $(is_online) :));
This syntax tells the MUD 'hey, I'm just going to need the value of this - extract it
from the variable, because the variable might not be around later'.
You will also find that sometimes function pointers cause all sorts of strange
errors. Function pointers are bound to a particular context (a specific object), and
363
Epitaph Online
http://drakkos.co.uk
they don't save with save_object or work very well in call_outs. The error you'll see
in this case is along the lines of ‘Owner of function pointer is destructed', and it
will cause a runtime.
Conclusion
Powerful as all get out, but costly in terms of code readability and often efficiency
– function pointers are the guilty little indulgences of Epitaph. We have
comprehensive (if not universal) support for them in our mudlib, and they are the
key to making your exec command a powerful weapon rather than a distracting
novelty. It is important that you know how to use them, because there are very
few parts of our game that aren't touched by them in some way, and many of the
things you might like to do require at least passing familiarity with the concept.
Think way back to Introductory LPC – we couldn't even keep them out of the
introductory text for Epitaph.
Many of us use them automatically, but that's not a good thing. They should be
used with caution and with forethought. Whenever it is possible, use good,
honest, properly written functions rather than function pointers. It may cost you a
little extra time, but those who have to maintain your code will thank you for it.
364
Epitaph Online
http://drakkos.co.uk
The Headmistress
Introduction
Now that we've happily implemented our zombies and our survivor, let's write up
the last of our NPCs. The headmistress is a pretty straightforward character - a
boss zombie with all sorts of neat powers designed to make her a challenge. She
is both a boss, and a zombie, so we use the base inherit STD_BOSS_ZOMBIE for
her.
Remember what our intentions are here - a few boss powers, some semirandomised articulations, and a whole lotta heart! So let's get started!
The Skeleton of a Boss Zombie
Our skeleton file for the headmistress looks like this:
#include <npc.h>
inherit STD_BOSS_ZOMBIE;
void setup() {
set_name ("headmistress");
add_property ("determinate", "the ");
set_long ("The infection has not been kind to this poor woman - her body "
"has erupted in violent, pus-filled boils and blisters that eject "
"greasy fluid like minature volcanos. Her fingers are like iron claws, "
"hooked and sharpened to impossible points at the nails.\n");
basic_setup ("zombie", "standard", MONSTER_LEVEL_BOSS);
set_boss_level (1);
}
The three main things here are the use of the STD_BOSS_ZOMBIE inherit, the
basic_setup which sets her as a MONSTER_LEVEL_BOSS, and the set_boss_level call
- this sets up the level of her skills - when we set up the skills of rank and file
NPCs, there is some randomness (within certain ranges). It is important though
that for bosses there is reliability - if people fail to kill a boss, it should be
because their strategy was ineffective or the execution was flawed, not because
they were unlucky enough to get a boss that had an extra bonus of 100 on all the
key skills.
When we talked about the headmistress in the previous section of the book, we
discussed having her semi-articulate - being able to say things, but not being able
to fully express them because, well, she's dead. So every so often she will try to
speak, but other times she will simply moan, or groan, or growl.
We want her growling to be vaguely animalistic, and animals don't growl the same
365
Epitaph Online
http://drakkos.co.uk
way each time – so let's write a function to do some semi-randomised growling,
like so:
void zombie_growling() {
string *start_letters = ({"Gra", "Ra", "Kra", "Bwa"});
string *start_bit = allocate (3 + random (4), "a");
string *mid_bit = allocate (3 + random (4), "r");
string *end_bit = allocate (3 + random (4), "a");
do_command ("say " + element_of (start_letters)
+ implode (start_bit, "") + implode (mid_bit, "")
+ implode (end_bit, "") + "r!");
}
In order to speak, an NPC needs a language, and the easiest way to get that is via
a nationality - we can set that up like we did with Beefy way back when:
setup_nationality (MUDLIB_NATIONALITIES + "british", "scotland");
Implode is a function that takes an array and condenses it down to a string, and
allocate lets us create an array – the first parameter is how many elements the
array will have, and the second is the starting value to give each element of the
array. The result is that the Headmistresses growls vaguely realistically:
The
The
The
The
headmistress
headmistress
headmistress
headmistress
exclaims:
exclaims:
exclaims:
exclaims:
Bwaaaaaarrrrraaaaaar!
Graaaaaarrraaaar!
Kraaaaaaarrraaaaar!
Kraaaarrraaar!
There's no real need for that of course, it's just a l'il bit of sugar for our star
attraction.
Next, we want our 'speech but not speech'. We can handle this by giving each
response an unmangled form, and pass it through a mangling function. Our
mangling function will change random parts of the string into grunts, groans and
moans. We should also make sure that we reuse code where we can:
string random_growling_bits() {
string *start_bit = allocate (3 + random (4), "a");
string *mid_bit = allocate (3 + random (4), "r");
string *end_bit = allocate (3 + random (4), "a");
return implode (start_bit, "") + implode (mid_bit, "") +
implode (end_bit, "");
}
void animal_growling() {
string *start_letters = ({"Gra", "Ra", "Kra", "Bwa"});
do_command ("say " + element_of (start_letters) + random_growling_bits()
+ "r!");
}
string contort_word (string word) {
string start = word[0];
string end = word[<1];
return start + random_growling_bits() + end;
366
Epitaph Online
http://drakkos.co.uk
}
string mangle_string (string str) {
string *arr = explode (str, " ");
for (int i = 0; i < sizeof (arr); i++) {
if (!random (3)) {
if (sizeof (arr[i])) {
arr[i] = contort_word (arr[i]);
}
}
}
return implode (arr, " ");
}
It's the mangle_string function here that handles changing a chat into a contorted
string – at random intervals throughout the string, it'll use the contort_word
method to turn a word like ‘Hello' into the word 'haaarraarrro' or such. Try to
update this NPC though, and we get the following:
Type mismatch ( string vs int ) when initializing start before
Type mismatch ( string vs int ) when initializing end before
This is all down to how the MUD actually represents individual characters of a
string – it stores them as numeric representations of the letter they are supposed
to be. Technically they get stored as ASCII codes, each number representing a
particular letter. So when we get a single character off of an string (such as
word[0]), what we get out is the number representing that letter. As such we need
to store them in ints:
string contort_word (string word) {
int start = word[0];
int end = word[<1];
return start + random_growling_bits() + end;
}
Now it compiles, but we have a new problem. Try to call the function with a
testing string, and we get something rather strange back:
> call mangle_string ("get in ma belly") headmistress
Returned: "get 105aaaaarrrrrraaaaa110 ma 98aaaarrrrrraaaaaa121"
Say what? What? Why is this! Why do you hit us, LPC, when we show you nothing
but love?
Well, I'm sure you can guess – we represented letters coming off of our string as
integers, and so that's how they get added to the string - as numbers. If we want
to turn these numbers into characters, we need to pass them through sprintf and
tell it ‘treat this number I give you as a character' – we do that with the %c code:
string contort_word (string word) {
int start = word[0];
int end = word[<1];
return sprintf ("%c", start) + random_growling_bits()
367
Epitaph Online
http://drakkos.co.uk
+ sprintf ("%c", end);
}
To see what's actually happening here, try this little exec:
exec int num = 97; for (int i =0; i < 26; i++) { printf ("%c\n", num + i); }
This will print out all the letters of the alphabet. To see it print out the numbers
instead, use the %d code for sprintf:
exec int num = 97; for (int i =0; i < 26; i++) { printf ("%d\n", num + i); }
This second exec does the same thing as the first, except what you'll get out of it
are the numeric ASCII codes.
Some C-type languages have a ‘char' datatype to simplify the process.
Unfortunately LPC doesn't, and so we need to make use of these kinds of workarounds.
Anyway, we want to bind both the growing and talking into load_chats for our
little zombie boss. One final methid:
void do_zombie_chat (string str) {
str = mangle_string (str);
do_command ("say " + str);
}
And then set that up in our load_chats:
load_a_chat (10, ({
1, "#zombie_growling",
1, "#do_zombie_chat:Get in ma belly!",
1, "#do_zombie_chat:They were delicious!",
1, "#do_zombie_chat:The boys tasted like chicken!",
1, "#do_zombie_chat:The girls tasted like sugar!",
}));
We can have as many of these chat strings as we like, and if we want the horrible
grunts and growls to be even more effective, we just need to change the
mangle_string function to make it so.
NPC Combat Behaviours
You'll have noticed that many NPCs across the game make use of fairly solid
(albeit unimaginative) tactics as they fight with players. They launch specials, trip
people up, use the right kinds of weapons and use the right kind of defences.
Every NPC on Epitaph gets access to a behavioural subsystem that lets you define
new and interesting behaviours for them, or choose from the a la carte menu of
basic functionality. All of these behaviour templates are located in
/data/behaviours/ - the one for a basic zombie looks like this:
368
Epitaph Online
http://drakkos.co.uk
::item "basic zombie"::
::->minimum_time_between_specials:: 10
::->energy_in_reserve:: 500
::->preferred_position:: POSITION_STATE_SCRUM
::->breach_chance:: 100
::->combat_actions:: ([
(: $1->query_prone() == 0 :) : "wrestle $target$",
(: $1->query_prone() && sizeof ($1->query_pinned()) < 2 :) : "pin $target$",
])
::->on_consider:: ([
])
::->barricades_modules:: ({
"zombie barricades"
})
::->applied_state:: ([
])
If you think 'that looks like a quest file', then you're right - it's another data file
which is interpreted by another handler. There are only a few bits here that are
really revelent for now - preferred_position handled the default position the NPC
will attempt to reach, and the minimum time between specials is the time it will
wait, at a minimum, before attempting to perform a 'clever' combat action.
It is the combat_actions part that defines those 'clever combat actions' - and now
you see why we had to take time out to talk about function pointers before we got
here.
Combat actions are defined as mappings - the key contains some 'prerequisite
checks' that must be passed before the action (the value of the mapping) is made
available. That action can either be a string that the NPC will perform (with
$target$ being replaced with the object reference of the current target on the
NPC), or a function pointer that contains more exotic functionality.
Let's look at our two combat actions here to see what is going in. For the first, we
check to see if our target (denoted by $1) is prone. If they are not prone, one of
the options we have for a 'clever' action is to wrestle them to the ground.
For the second, it checks to see if they are prone, and if they are currently being
pinned by fewer than two opponents. If both of these things are true, we also
have an option to attempt to pin an enemy to the ground.
When it comes time to perform a special action, the NPC will evaluate all its prerequisites, and construct an array of possible actions - one of these will be picked
at random and performed. This ensures that NPCs only perform those actions
that make sense in their current context.
Now, the problem here with our boss NPC is that it's not a normal zombie - it's a
boss zombie, and it shouldn't simply try to wrestle people to the ground. There is
a behaviour that simply makes our boss auto-attack like most unintelligent NPCs,
and it is called basic zombie boss. We make our headmistress use this by using
the function set_fixed_behaviour in her setup:
set_fixed_behaviour ("basic boss zombie");
369
Epitaph Online
http://drakkos.co.uk
This will make sure her boss powers are the only thing she really has to work
with.
Boss Powers
You will, I am sure, be entirely unsurprised to find out that boss powers get
handled via data files - they look like this:
::item "driver aoe knockdown"::
::->start_text:: ":roars and starts to push hard against the sides of the "
"carriage."
::->end_text:: ": stops pushng the side of the carriage."
::->valid:: (: 1 :)
::->aoe:: 1
::->delay:: 5
::->defence_task:: TASKMAP_ASSESS_AN_ENEMY_INTENTION
::->difficulty:: TTSM_DIFF_EXTREMELY_EASY
::->messages:: ({"You learn a little something about keeping your feet."})
::->min_damage:: 50
::->random_damage:: 50
::->damage_type:: "blunt"
::->target_text:: "The shaking carriage knocks you onto your back!\n"
::->room_text:: "The carriage shakes alarmingly!\n"
::->extra_payload:: ({
(: $1->set_prone ($2->modify_value_by_power (10 + random (5), $3)) :)
})
::->cooldown:: 10
::->freeze:: 1
This is a boss power from an existing NPC - the train driver in the dangling train
carriage in Dunglen. Most of it should be fairly self-explanatory, so Im not going
to spend an awful lot of time explaining it - just those bits that are relevent to us.
Bosses are constantly ready to use their powers, the only thing that stops them is
the cooldown - they will only ever perform one power at a time, but the one they
perform is just based on their powers and cooldowns. Let's look at one example
of a power file for the disarming attack our NPC has:
::item "headmistress claw"::
::->start_text:: ":screeches and flexes her claw like fingers."
::->end_text:: ": stabs a clawed hand towards her target."
::->no_apply:: ": wails angrily as her target evades her attack."
::->valid:: (: 1 :)
::->aoe:: 0
::->delay:: 5
::->defence_task:: TASKMAP_ASSESS_AN_ENEMY_INTENTION
::->difficulty:: TTSM_DIFF_EXTREMELY_EASY
::->messages:: ({"You learn a little more about keeping your weapon "
"in your hand."})
::->min_damage:: 50
::->random_damage:: 50
::->damage_type:: "blunt"
::->target_text:: "The headmistress snatches your weapon out of your hand!\n"
::->room_text:: "The headmistress snatches a weapon right out of $target$'s
hand!\n"
370
Epitaph Online
http://drakkos.co.uk
::->extra_payload:: ({
(: $1->fumble_current_weapon ($2->modify_value_by_power (10 + random (5)))
:)
})
::->cooldown:: 30
::->freeze:: 1
::->apply_test:: (: sizeof ($1->query_weapons()) :)
We need to add this boss power to our headmistress's setup:
add_boss_action ("headmistress claw");
Facing off against our headmistress, she will now perform her steely hand attack.
The headmistress screeches and flexes her claw like fingers.
You slash at the headmistress with your machete but it just dodges out of the
way.
The headmistress snatches your weapon out of your hand!
Her other attacks are handled in exactly the same way - let's look at one more,
and leave the third as an exercise for the reader. We're going to look at her pus
explosion attack, because that offers us a few more opportunities to look at how
boss powers function.
Pus Attack
As a recap - this attack is going to explode over everyone who is in melee range,
and it is going to make her do dramatically more damage to anyone who is coated
in her pus. The basic structure of the attack is the same as the the one above - a
data file which we add using add_boss_action:
::item "pus attack"::
::->start_text:: ": starts to shudder, her pus-filled boils starting to weep
and pulsate."
::->end_text:: ": screams, and a volano of hot, sticky pus exploded out like a
shell of vileness."
::->valid:: (: 1 :)
::->aoe:: 1
::->delay:: 5
::->target_text:: "Urgh, you are covered in disgusting pus!!\n"
::->room_text:: "The headmistress explodes in a spray of disgusting pus!\n"
::->extra_payload:: ({
(: $1->add_stain ("pus", 1) :)
})
::->cooldown:: 60
::->freeze:: 1
::->apply_test:: (: $1->query_combat_state() == POSITION_STATE_SCRUM :)
And then:
add_boss_action ("pus attack");
And now, when we fight our boss, we get:
371
Epitaph Online
http://drakkos.co.uk
The headmistress starts to shudder, her pus-filled boils starting to weep and
pulsate.
The headmistress screams, and a volano of hot, sticky pus exploded out like a
shell of vileness.
Urgh, you are covered in disgusting pus!!
Urgh. It doesn't do anything at the moment - it just adds a stain through our
stains system. There were all sorts of other ways we could handle this - an antiknack, an effect, or a property - it doesn't really matter. All that matters is there
is some way for our NPC to tell how much pus someone has been covered with:
He is spattered with pus, caked with blood and caked with paint.
The last bit of this attack is to make her do extra damage based on how much
pus someone has been covered with. We do that by over-riding modify_damage in
our headmistress:
varargs int modify_damage( int damage, string attack_name, object player ) {
damage = ::modify_damage (damage, attack_name, player);
if (player->query_stain_amount ("pus")) {
damage *= player->query_stain_amount ("pus") + 1;
}
return damage;
}
And guess what! That is our second boss power implemented. The last of these I
shall leave as an exercise for the reader.
Boss Drops
As a reward for downing a boss, we like to offer some potentially unique items as
a 'drop' for those who participated. Not everyone gets a chance at the look, that's
handled by our loot chest system. Making a drop available is simple - let's make
one available from our headmistress - that's done using add_drop:
add_drop ("bag of dog food", 50);
The first is the item we want to add as a potential drop, and the second is its
chance (this gets done via a roulette wheel as we've discussed previously).
Now when we kill our headmistress, we get a nice reward:
You see a loot chest out of the corner of your eye.
Uh, thanks miss. I'll cherish this dog food forever.
372
Epitaph Online
http://drakkos.co.uk
Conclusion
Boss NPCs are well known in many online games, and Epitaph is no different - our
boss NPC system offers a convenient way to make genuinely interesting combat
encounters for groups, and also provides a way to make available unique items
that are not available anywhere else.
Of course, no encounter will be interesting if time isn't taken to design it properly,
so always make sure that you have thought through the different parts of the
encounter - design here is much more important than implementation.
We still haven't talked about how to make the basement of the school an instance
- don't worry, that will come.
373
Epitaph Online
http://drakkos.co.uk
Shopping Around
Introduction
There's a feature we haven't yet addressed in Zombieville – that of our tartan
knitwear store. It's not a major deficiency, but it's still something we have left to
do. Along the way, we'll learn about a system called autoloading, which is the
system that is used to make items save their properties over logins.
Item Development
We spoke about clothes and virtual files in the first Introductory LPC, so we won't
rehash that here. First of all, let's write a basic skirt for the shop. We won't write it
as a virtual file (for reasons that will become clear later), we'll do it as a normal
LPC .c file. First of all though, we need to talk about where we're going to save
these files.
The logical place to save them, considering the way we have been saving things
so far, is to save them in an ‘items' sub-directory of Zombieville. Unfortunately,
this doesn't work as well as we might hope – the armoury will not pick items out
of sub-directories in this way. The only way the armoury knows where to find
objects is if they are stored in /d/some_domain/items or a sub-directory of same.
This is a huge drawback for the way we've been doing things – it means we
suddenly need to switch from storing things in one easily explored directory and
navigate instead across two different directories.
We're not going to do that – we are going to be awkward and store things in
zombieville/items. This is purely something we are doing to make these tutorials
hang together properly – when developing code in the ‘real world', it should
always be accessible from the armoury. Let this be a warning!
Anyway, we're doing the following:
#define
ITEMS
ZOMBIEVILLE + "items/"
And then we're going to create a simple dress item in that directory:
inherit STD_CLOTHING;
void setup() {
set_name ("skirt");
set_short ("$colour$ $material$ skirt");
add_adjective (({"tartan"}));
set_main_plural ("$colour$ $material$ skirts");
set_long ("This is a $colour$ skirt, like you could imagine being worn "
"by a Scottish grandmother against the unforgiving Caledonian "
"winters. It's made of $material$.\n");
set_material (([ "wool" : 1]));
set_colour ("tartan");
374
Epitaph Online
http://drakkos.co.uk
set_type ("skirt");
}
So far, so straightforward. Now, let's get a little more adventurous.
Auto Loading
When a player saves, we go through a process whereby we take their skills, tell
history, health, wellbeing values, and all the other things that make up that
unique player and we store that info in a file on disk. As part of that process, we
also store their inventory, including all those elements of each item that are
subject to change as the game goes on, such as stains, condition, engravings, and
so forth. This is a fairly complicated procedure, and is known as the auto load
system. The consequence of this is that creating a player item that saves its state
doesn't work like creating a handler that saves its state – we need to hook into the
auto load process.
Each object that inherits STD_OBJECT has a method called
‘query_dynamic_auto_load' defined in it. It is this function that contains all of the
information that makes one object unique and distinct from another object. For
example, if I call it on a machete in my inventory, I get the following output:
Returned: ([ /* sizeof() == 4 */
"upgrade" : ([ /* sizeof() == 1 */
"upgrade : upgrades" : ([ ]),
]),
"::" : ([ /* sizeof() == 10 */
"properties" : ([ /* sizeof() == 4 */
"virtual clone" : "/mudlib/inherits/weapon",
"salvageable" : 1,
"shop type" : "armoury",
"help file name" : ({ /* sizeof() == 1 */
"weapon"
}),
]),
"gore" : 0,
"keep" : 0,
"materials" : ([ /* sizeof() == 1 */
"steel" : 1,
]),
"cloned by" : "drakkos",
"read mess" : ({ }),
"gore message" : "",
"identify" : 0,
"colour" : "silver",
"light" : 0,
]),
"hold" : ([ /* sizeof() == 2 */
"limbs used" : 0,
"limb" : -1,
]),
"condition" : ([ /* sizeof() == 2 */
"cond" : 7350,
"lowest cond" : 6762,
]),
])
375
Epitaph Online
http://drakkos.co.uk
In there is all the information the MUD needs to take a basic machete and turn it
into the specific machette I know I love. This mapping gets built through
successive specializations of the method in each object that inherits another.
Essentially each file takes the responsibility for adding its own state to this
mapping, and then passing the call along the line to the next inherit in the chain.
Let's look at query_dynamic_auto_load as defined in the scabbard:
mapping query_dynamic_auto_load() {
mapping map;
map = ([
"::" : object::query_dynamic_auto_load(),
"condition" : condition::query_dynamic_auto_load(),
"hold" : holdable::query_dynamic_auto_load(),
"upgrade" : upgrade::query_dynamic_auto_load(),
]);
return map;
}
Notice that as part of this method, it makes a call to the query_dynamic_auto_load
of its parent objects, object, condition, coldable, and upgrade:
"::" : object::query_dynamic_auto_load(),
"condition" : condition::query_dynamic_auto_load(),
"hold" : holdable::query_dynamic_auto_load(),
"upgrade" : upgrade::query_dynamic_auto_load(),
The first line here takes whatever comes out of query_dynamic_auto_load for the
object, and stores it in the mapping key "::", and whatever comes out of the
condition call gets stored in the "condition" key, and so on. These values for these
keys thus contain the mappings that come out of the parents, and the parents
themselves contain mappings that come out of their parents.
Let's take a short break from this, and go back to our skirt.
Dressing Up
Let's say I want these skirts to be a little more configurable than normal dresses.
In addition to having the short and the long, I want them to have a specific 'clan
tartan' that can be set to define the pattern. I could create dozens of different
clothes files to do this, but that's a maintenance and development nightmare. The
easiest solution is to let me set it directly on the skirt:
inherit STD_CLOTHING;
string motif;
void setup() {
set_name ("skirt");
set_short ("$colour$ $material$ skirt");
add_adjective (({"tartan"}));
376
Epitaph Online
http://drakkos.co.uk
set_main_plural ("$colour$ $material$ skirts");
set_long ("This is a $colour$ skirt, like you could imagine being worn "
"by a Scottish grandmother against the unforgiving Caledonian "
"winters. It's made of $material$.\n");
set_material (([ "wool" : 1]));
set_colour ("tartan");
set_type ("skirt");
add_extra_look (this_object());
}
void set_motif (string m) {
motif = m;
}
string query_motif() {
return motif;
}
string extra_look (object ob) {
if (query_motif()) {
return "The skirt has been picked out in the tartan of the "
+ query_motif() + " clan.\n";
}
return "";
}
Now when I clone one and call set_motif, I get the following:
This is a tartan skirt, like you could imagine being worn by a Scottish
grandmother against the unforgiving Caledonian winters. It's made of wool.
The skirt has been picked out in the tartan of the MacGregor clan.
It is in excellent condition.
Lovely, just what I have always wanted. Alas, it is ephemeral, like the love of a
young girl. If we log off and then back on, then the motif disappears:
This is a tartan skirt, like you could imagine being worn by a Scottish
grandmother against the unforgiving Caledonian winters. It's made of wool.
It is in excellent condition.
We know we wanted the motif to be saved. The MUD alas does not. This is where
we need to hook into the auto load code.
Back To The Auto Loading
See, this auto load stuff is not a magical mystery tour – this kind of state storing
is a very common thing creators want to do. It's not actually hard for us to hook
into this process, but it's useful to understand why we're doing it rather than
assuming it to be some kind of magical transaction. We need to provide our own
specialization of query_dynamic_auto_load to store the motif along with
everything else:
mapping query_dynamic_auto_load() {
return ([
"::" : ::query_dynamic_auto_load(),
377
Epitaph Online
http://drakkos.co.uk
"motif" : query_motif(),
]);
}
In the mapping we create, the key ―::" holds the result of the call on our parent
class (/mudlib/inherits/clothing) which does the same kind of thing for its
parents, all the way along the line. Additionally, we add a motif entry to the
mapping - this holds what our motif was set as. We don't need to call this method
at any point, it gets called naturally as part of the saving and logon process.
Now we can log off and log on, and... it's still not there. Damnation! Why is this
the case?
Well, we've saved the information fine. The second part of the auto load process is
that each object is also responsible for restoring its own state. This works
identically as a process, but we use the method init_dynamic_arg to do this. As
with query_dynamic_auto_load, this method gets called automatically as part of
the auto load process, and the parameters get provided from without. The first
parameter is the mapping that contains all of the auto load information, and the
second contains the player for whom the inventory is being loaded.
void init_dynamic_arg( mapping map, object ob) {
if (map["::"]) {
::init_dynamic_arg(map["::"], ob);
}
if (map["motif"]) {
set_motif(map["motif"]);
}
}
In this first part of this code, we check to see if the mapping we have been
provided has anything associated with the key ―::". We know it does – that key
contains all the information from the parent object, and so we call
init_dynamic_arg on the parent passing only those parts of the mapping we got
from it.
Motif is something we handle internally, so we check to see if one has been set,
and if it has we call set_motif with whatever was in the mapping.
The fact that auto loading is a complex and tricky concept at the deep levels of
the mudlib doesn't impact on your code especially. You just need to honour two
rules in your objects:


Make sure you honour the chain of invocation
Store and restore your own state
If every object does this, then auto loading works like a charm and no-one has to
know more than how to handle their own object state. It's really very clever when
you think about what the alternatives would be.
378
Epitaph Online
http://drakkos.co.uk
Doing It Better
The above is how the majority of objects on the MUD handle auto loading. You'll
see it a lot, so it's important you understand how it works. However, there's a
better way that leads to much more readable, maintainable code – we use the
add_auto_load_value and the query_auto_load_value functions to achieve our
nefarious aims.
The first function, add_auto_load_value lets us add a piece of data to the
mapping:
map = add_auto_load_value(map, "zombieville skirt", "motif", query_motif());
The first parameter is the mapping we're working with,the second is a unique
identifier for this object. The third is the name we'll set as the key of the mapping
entry, and the last is the value itself. Thus, our method should look like this:
mapping query_dynamic_auto_load() {
mapping map;
map = ::query_dynamic_auto_load();
map = add_auto_load_value(map, "zombieville skirt", "motif", query_motif());
return map;
}
And then we use init_dynamic_arg to put them back in place, like so:
void init_dynamic_arg( mapping map, object ob) {
string tmp;
::init_dynamic_arg (map, ob);
tmp = query_auto_load_value (map, "zombieville skirt", "motif");
set_motif (tmp);
}
You can see right away that the code is more readable, but there is an additional
benefit – if every object in an inherit tree is using this system, what you get out
the function is something that can be read and understood by a human. Alas, we
don't use it all the way through the mudlib yet, and so results may vary from
region to region. Nonetheless, this is how you should be handling your auto
loading.
Back to our Shop
We can make our skirts available in the shop easily enough by using our normal
scavenge architecture - but when they get searched up, they won't have any motif
at all. We can handle this though by defining a function in our shop that does this
379
Epitaph Online
http://drakkos.co.uk
for us. Whenever an item is searched up, the function item_search_callback is
called on that item. We can override that to alter the items that come out of the
search system:
object item_search_callback (object ob, object player) {
string *clans = ({"MacGregor", "Stuart", "Robertson", "Carnegie"});
ob = ::item_search_callback (ob, player);
ob->set_motif (element_of (clans));
return ob;
}
This will call the set_motif function on anything that gets found in the room - it's
possible, if we have multiple items, some of which we cannot configure with
set_motif, that this is not the desired behaviour. We can handle that too by
checking to see whether or not motif setting is possible using the
function_exists efun to say whether or not a function is available in our object:
object item_search_callback (object ob, object player) {
string *clans = ({"MacGregor", "Stuart", "Robertson", "Carnegie"});
ob = ::item_search_callback (ob, player);
if (function_exists ("set_motif", ob)) {
ob->set_motif (element_of (clans));
}
return ob;
}
There are other ways too of handling this, and it doesn't actually do any harm if
you don't have a check like this - calling a function that doesn't exist on an object
will simply result in nothing happening. It's a worthwhile efun though, and it's yet
another arrow in our quiver of LPC skills.
Conclusion
Having items that can hold their state information is one of the most important
things to be able to do when creating interesting code. Almost anything of any
kind of complexity has a state that gets manipulated as the player works with it,
and being able to store that state between logins is mandatory – there's no way to
work around it other than resetting the state to some kind of starting value each
time. That's bad for all sorts of reasons.
The auto load system is complex and intricate, and it requires all objects in the
inherit chain to play nicely with all other objects. When that happens though, it's
extremely flexible and puts the responsibility for proper restoring and storing of
state information in the hands of individual creators, where it belongs.
380
Epitaph Online
http://drakkos.co.uk
Cause and Effect
Introduction
In the last chapter we looked at spells, but rather simple spells. Often spells act as
a kind of delivery engine for an effect rather than something that instantly
delivers a payload. In this chapter we are going to discuss the idea of an effect,
how it can be used, and what it can do.
Once again, we start by adding a new define to our path.h file:
#define
EFFECTS
ZOMBIEVILLE + "effects/"
We are going to address the path that leads to our library in this chapter – as
you'll undoubtedly remember from Being A Better Creator, this is supposed to be
hidden. That's no problem, but we're going to make the searching a little more
interesting than we have done in the past.
What's An Effect?
The easiest way to think of an effect is as a temporary set of conditions that
impact on a player or an object. For example, an effect may be a disease, or a
curse, or a buff, or any number of other things. They get attached to objects on a
conditional basis – they may last until a certain condition is met, or until a certain
period of time has passed. While they are attached to an object, that object can be
impacted on an ongoing basis.
In the Olden Days, effects often used to come as a pair with a thing called a
shadow. A shadow is an object that can be attached to another object and works
like a temporary mask on a function. We might have a shadow that overrode
adjust_xp to give double the experience it would normally give, or overrode
query_skill_level to simulate an object that gave bonuses to certain skills.
We don't do that any more – that particular mudlib feature is now considered to
be deprecated. You'll still see a number of shadows around and about because we
haven't gotten them all out of our code. No new shadows should be coded.
However, that doesn't mean effects are useless – far from it.
What we're going to do in this chapter is build an effect that acts as ‘damage over
time' system on a player. As a result of searching for the hidden path, they'll get
scratched and damaged and bleed for a while. It's a simple effect, but that that
will illustrate a good deal about how they are put together.
Effects don't need to inherit anything – they exist as self-contained sets of code.
381
Epitaph Online
http://drakkos.co.uk
However, they must implement five functions to work properly.
#include <effect.h>
int beginning( object player, mixed args, int id ) {
}
int merge_effect( object player, mixed old_args, mixed new_args, int id ){
}
void end( object player, mixed args, int id ) {
}
string query_classification() {
}
void restart( object player, mixed args ) {
}
The first of these is beginning, and it gets called when an effect is first added to
another object. The merge_effect function is called if an effect is added to an
object that already has the same effect active. The end function gets called when
the effect is removed from an object. Restart is called when a player logs off and
then on again. Finally, query_classification provides an 'identifier' code.
Let's start by filling in a placeholder for each of these functions and discussing
what their parameters do.
int beginning( object player, mixed args, int id ) {
tell_object (player, "Argh! You must have put your hands in some "
"stinging nettles.\n");
player->adjust_hp (-1 * args);
return args;
}
The first parameter is the object on which the effect has been applied – it doesn't
have to be a player, but since this particular effect will indeed be applied to
players, we may as well indicate that with the parameter name. When we add an
effect to an object, we can provide some arguments to it for configuration
purposes. Within the beginning function, they are accessible as the second
parameter. The third argument you don't need to worry about.
When we return a value from this function, the value we return becomes the
‘argument' of that effect – we can access that later in code, which we will come
back to later. For now, all this effect does when it is applied to a player is do an
amount of damage equal to the argument we pass it.
The end function doesn't have to do anything, but it's always called when an effect
is removed and it can be a good way for us to provide some information to the
player reflecting that the effect has come to an end. The parameters are the same
as for beginning:
void end( object player, mixed args, int id ) {
tell_object (player, "The stinging from the nettles subsides.\n");
}
382
Epitaph Online
http://drakkos.co.uk
For query_classification, we give some meaningful identifier for this effect. It
doesn't really matter what it is, but for convention we usually use something like
‘body.nettles' to give a fair idea of what it is and where it comes from:
string query_classification() {
return "body.nettles";
}
By default, if two effects are added to the same object, the object gets two copies
of an effect running on them. Usually we don't want that, and so we override that
behaviour with the use of merge_effect. Whatever we return from this function
becomes the new value of the argument of the effect. Let's make it so that we
simply use the stronger of the two:
mixed merge_effect( object player, mixed old_args, mixed new_args, int id ) {
if (old_args > new_args) {
return old_args;
}
return new_args;
}
Restart gets called each time the effect gets restarted, so let's use it to supply a
message to the player:
void restart( object player, mixed args ) {
tell_object (player, "Argh! You must have put your hands in some "
"stinging nettles.\n");
}
And that, believe it or not, is an effect. It's not much of one, but it's an effect
nonetheless. We can test it out by using the add_effect method, like so:
> call add_effect ("/w/your_name_here/zombieville/effects/nettles", 100) me
Argh! You must have put your hands in some stinging nettles.
Along with that message comes 100 points of damage, as we planned all along.
To get a list of all the effects on an object, I can use the ‘effect' command:
> effect drakkos
Effects on Drakkos:
[0]
body.wetness (1043)
[1]
body.nettles (200)
In square brackets before each of these effects is a numeric identifier, the effect
number (or enum for short). Here we can see the effect with the enum 1 is
body.nettles, which is what we gave it for a classification. The 100 in brackets
indicates what the argument of that effect is. This effect will remain in place until I
die, or I delete it like so:
> call delete_effect (1) me
The stinging from the nettles subsides.
I can add this effect as many times as I like to myself, and I will only ever have one
body.nettles effect on me – merge_effect deals with that.
383
Epitaph Online
http://drakkos.co.uk
Since this is an effect that has a visible player impact, we want it to show up using
the debuffs command so that people know they are being affected by it. To
support this, we need to define three additional methods in our effect:
string query_buff_name() {
return "stung by nettles";
}
string query_buff_description() {
return "Your hand is throbbing - you must have stuck it in some "
"nettles, silly billy.";
}
int query_debuff() {
return 1;
}
Now it'll show up properly when we check our debuffs:
> debuffs
stung by nettles
Your hand is throbbing - you must have stuck it in some nettles, silly
billy.
soaked to the skin
You are cold, and miserable. This has no real game impact, but it's
a little annoying.
To make it show up with the buffs command instead, we simply omit the
query_debuff function, or make it return 0.
So that's an effect – it's simple, but not exciting. Let's open this puppy up though
and see what we can do.
The Power of Effects
The real power of effects comes from how easily we can cause our code to have
certain functions called at regular intervals. With our nettle effect, it just does the
damage once and that's it. If we want damage to also be done on a periodic basis,
we can do that using the submit_ee (ee is short for 'effect event') function on our
player. This function takes three parameters – the first is the function to be called
when the effect event occurs. The second is the interval between the ticks of the
event, and the third is how often the event should be submitted. This should be
one of three values, as defined in effect.h:
Effect Interval
EE_REMOVE
EE_ONCE
EE_CONTINUOUS
Description
Call it once, and then remove the effect from the
player when it has occurred.
Call it once, but leave the effect intact
Call it repeatedly, leaving the tick period between
invocations
384
Epitaph Online
http://drakkos.co.uk
So if we wanted damage done continuously while the effect was active, that's very
easily done – the function we tell the effect to trigger comes with the same
parameters as for beginning and end:
mixed beginning( object player, mixed args, int id ) {
tell_object (player, "Argh! You must have put your hands in some "
"stinging nettles.\n");
player->adjust_hp (-1 * args);
player->submit_ee( "damage_me", 20, EE_CONTINUOUS );
return args;
}
This is the code that starts the effect timer running, and then we simply
implement our damage_me function:
void damage_me(object player, mixed args, int id ) {
player->adjust_hp (-1 * random (args));
tell_object (player, "Oooh, that stings!\n");
}
Every twenty seconds now, we'll trigger the damage_me function and that will do a
random amount of damage to the player based on how large an initial argument
we set. It will do this until the player dies, which is perhaps not exactly fair. To
make it a little less cruel, we combine this with a submit_ee that has the interval
set to be EE_REMOVE. This doesn't even need to have a function, it'll just cause
the effect to be automatically removed when the tick period has passed.
mixed beginning( object player, mixed args, int id ) {
tell_object (player, "Argh! You must have put your hands in some "
"stinging nettles.\n");
player->adjust_hp (-1 * args);
player->submit_ee( "damage_me", 20, EE_CONTINUOUS );
player->submit_ee( 0, args, EE_REMOVE);
return args;
}
Now we have an effect that is time limited by the initial argument too. If we
wanted, we could make the argument more granular so that we added an array
instead of a single integer, but the theory is the same.
Now the last thing we need to do to this effect is make it merge properly. It's
already handling the possibility of a player being affected by a stronger version of
the effect (remember, whatever we return for merge_effect becomes the new
argument of the effect), but we should also make sure it impacts properly on the
duration. That's easy to do with the expected_tt function, like so:
mixed merge_effect( object player, mixed old_args, mixed new_args, int id ) {
int time_left = player->expected_tt();
time_left += new_args;
player->submit_ee (0, time_left, EE_REMOVE);
if (old_args > new_args) {
return old_args;
}
return new_args;
}
385
Epitaph Online
http://drakkos.co.uk
Only one EE_REMOVE is ever active on a player for a particular effect, so when we
submit our new one we overwrite the old one. Now, each new application of the
nettles effect will increase its duration, but not cumulatively impact on its
strength. For a real effect, we probably don't want this either (some set ceiling is
usually appropriate, although we don't always honour that), but it'll do for us.
The next step is to write some code that adds this simple effect to a player. We go
to Zombieville_02 to do this. First we modify the exit to the trail so it isn't
obvious, and set up the rest of the things we need:
#include "path.h"
inherit INHERITS + "outside_room";
void setup() {
set_short ("skeleton room");
add_property ("determinate", "a ");
set_long ("This is a skeleton room.\n");
set_light (100);
add_item ("brush", "It looks unpleasant, full of nettles and "
"other horrible stinging things.");
add_item (({"nettle", "horrible stinging thing"}), "You wouldn't "
"want to take a widdle in that lot.");
set_light (100);
add_exit ("north", ROOMS + "zombieville_03", "road");
add_exit ("southwest", ROOMS + "zombieville_01", "road");
add_exit ("southeast", ROOMS + "zombieville_08", "road");
modify_exit ("southeast", ({"obvious", 0}));
}
We're going to handle the searching a little differently here too, because we want
something that is unique to happen - our framework for scavenging is for when
we want to find items, and that's not what we want to do here. We want an exit to
be revealed based on the result of our searching. Luckily, that too is easy to do we put a function called do_search in our room, and the searching system will do
the rest for us:
int do_search (string str) {
int found;
int success;
success = TASK_MAPPER->perform_task (this_player(),
TASKMAP_NOTICE_SOMETHING,
TTSM_DIFF_INCREDIBLY_EASY, 0, TM_FREE);
switch (success) {
case TASKER_AWARD:
tell_object (this_player(), "%^YELLOW%^%^BOLD%^You feel a little more "
"perceptive.\n%^RESET%^");
case TASKER_SUCCEED:
tell_object (this_player(), "You see a trail leading through "
"the brush to the southeast.\n");
found = 1;
break;
386
Epitaph Online
http://drakkos.co.uk
case TASKER_FAIL:
found = 0;
break;
default:
}
if (random (2)) {
this_player()->add_effect (EFFECTS + "nettles", 100 +
random (100));
}
if (found) {
return 1;
}
else {
return -1;
}
}
All of this should be pretty straightforward at this point - the only thing that is
new is the return values of the function. 1 indicates 'this search was successfully
handled, thanks', and -1 indicates 'Nothing was found, go on with the rest of the
normal search'. If you return a 0, it won't do the normal search messages so you
can provide your own.
It is the add_effect method that provides us with a mechanism for applying the
effect, and that method is common to all objects that inherit
/mudlib/inherits/object along the way. As such, effects can exist on inanimate
objects as easily as they can on players, and the mechanisms for dealing with
them are identical.
Working with Effects
Coding effects is one part of the problem. The next is how we can work with
effects that are already on players. For example, we may wish to have different
courses of action available to people who are afflicted with particular effects.
The first thing we commonly want to do is determine if a player has a particular
effect on them. We do this with the effects_matching function to which we pass
the classification of effect for which we want to look. As a result, we get an array
of all the matching effect numbers (enums, remember), or an empty array if no
effects match the classification.
Let's go back to our search function – let's tell players afflicted with our effect that
it hurts too much for them to search in the room:
int do_search (string str) {
int found;
int success;
int *effects;
effects = this_player()->effects_matching ("body.nettles");
if (sizeof (effects)) {
387
Epitaph Online
http://drakkos.co.uk
tell_object (this_player(), "You are still stinging from your "
"brush with the nettles, and you can't bring yourself to search "
"too effectively.\n");
return 0;
}
// Code as usual
}
The next thing we often want to do is remove effects based on some external
factor. An old folk remedy to nettles claims that the plant 'horsetail' is effective for
removing the stinging sensation. Let's add some horsetail here and have its
impact be to remove the effect if it exists. We use delete_effect to handle that.
First, the add_item:
add_item ("horsetail", (: horse_tail_function :));
And then the function that does the magic:
string horse_tail_function() {
int *effects;
string ret;
ret = "Yes, there is some horsetail here.
";
effects = this_player()->effects_matching ("body.nettles");
if (sizeof (effects)) {
ret += "Throbbing from the stinging nettles, you clutch it "
"thankfully.";
for (int i = 0; i < sizeof (effects); i++) {
this_player()->delete_effect (effects[i]);
}
}
return ret;
}
Note that we delete the effects in a loop – remember an effect without a
merge_effect will result in more than one effect being applied to the object, and
we can't guarantee that the effects we are working with have only one instance set
on them. Whenever deleting an effect then, we do it properly – we get rid of all of
them.
Now, the thing about folk cures is that they are horribly ineffective. Really, our
horsetail should only reduce the impact of the effect, rather than remove it
entirely. To do that, we need to know what the value of the effect actually is. We
can get the current argument with arg_of, and we can change its value with
set_arg_of. Both of these need us to provide the enum of the effect we wish to
query or modify, like so:
string horse_tail_function() {
int *effects;
388
Epitaph Online
http://drakkos.co.uk
string ret;
int val;
ret = "Yes, there is some horsetail here.
";
effects = this_player()->effects_matching ("body.nettles");
if (sizeof (effects)) {
ret += "Throbbing from the stinging nettles, you clutch it "
"thankfully.";
for (int i = 0; i < sizeof (effects); i++) {
val = this_player()->arg_of (effects[i]);
val = val - 25;
if (val <= 0) {
this_player()->delete_effect (effects[i]);
}
else {
this_player()->set_arg_of(effects[i], val);
}
}
}
}
For complicated effects, the arguments may well be more complex than just an
integer, but the principle is identical. The arg_of function gives us what the
argument of the effect is, and set_arg_of allows us to change it.
Bits and Pieces
There are some other details that we need to talk about before you're ready to
make use of effects properly. The first is – when you're writing an effect, you
cannot use this_player. It is hardly ever going to be what you want it to be.
Instead, use the object reference provided as a first parameter to all the methods
– this ensures that you're working with the object to which the effect is attached.
Sometimes we want effects that exist indefinitely – it's entirely possible that we
may want an effect that doesn't have any persistent behaviour associated with it
(as in, no events get scheduled). The effects management code can detect effects
that have no pending events, and will often remove them. If we are creating a
genuinely indefinite effect, we need to stop it doing this by adding a
query_indefinite function:
int query_indefinite() {
return 1;
}
Upon death, all effects are removed from a player. If we have an effect that should
persist through death, we also include a survive_death function:
int survive_death() {
return 1;
}
389
Epitaph Online
http://drakkos.co.uk
If we are creating an effect that a player may be able to cure (with a spell, ritual,
or such), we can define a test_remove function to control whether our effect is
actually impacted. It takes four arguments – the object on which the effect is
active, the argument of the effect, the ID of the effect (ignore this), and the bonus
with which you should work with to see if an effect should be removed. If we
return a non-zero value, the effect gets deleted when the appropriate trigger
condition occurs. If we return 0, the effect remains in place:
int test_remove(object player, mixed arg, int id, int skill_bonus) {
if (skill_bonus > arg) {
return 1;
}
return 0;
}
Finally, remember that many of the effects that you will read examples of also use
shadows. We don't do that kind of thing any more – shadows were very powerful,
but came at considerable cost to the MUD's efficiency. If there is functionality you
want to create that would require a shadow object, don't go ahead and write one –
instead, consult a more experienced creator who might be able to put forward an
alternate course of action.
Conclusion
Effects are a tremendously useful way of creating temporary conditions that get
attached to objects in the game. With then we can create all kinds of sophisticated
behaviours such as poisons, buffs, debuffs, and all sorts of things in between.
Here we have created a relatively simple effect, but with a little modification it
would be possible to change it into a powerful ‘heal over time' spell, or a more
comprehensive ‘damage over time' that incorporated all sorts of deleterious
effects. Have a read over what we have available in /mudlib/effects/ and see the
kind of things that you can do with a little bit of imagination.
390
Epitaph Online
http://drakkos.co.uk
Achieving Your Potential
Introduction
Achievements are a core mechanism for Epitaph MUD - a way to get an extra
degree of 'oomph' out of your code without you needing to actually write anything
new. Achievements give you a way of focusing attention on the unique features of
your development, and rewarding those players who participate most fully in the
gaming experience you have provided. Unlike quests, an achievement is very
simple to write, and often requires very little code beyond a few lines to update a
counter or trigger a comparison.
Unfortunately, achievements are not things that can be integrated into Zombieville
because of the way they are made active in the achievements handler. All we can
do in this chapter is go over the concept. This is then largely a self-contained
chapter on how to use our achievements system, with no reference to Zombieville.
Indeed, that's how the chapter first existed when it was just a document on a
webpage.
Achievements and Quests
So, what is an achievement? For those who are not familiar with these in other
games, they may seem just another way of adding quests to the game. This
perception is especially muddled by the fact we have had no consistent
conceptual architecture when we were developing quests in the past. We have
always operated, vaguely, under the guiding principle that a quest should be a
puzzle – it is this principle that underlines most of our framing of the rules for
quests.
Achievements are rewards for participating and excelling in the game. They are
publicly announced (when they are above a certain level), and available for
browsing. They have no requirement to be IC – they can be a combination of IC
and OOC elements. They can be for achieving certain benchmarks in the game
(hitting certain guild level targets), or for passing some threshold of participation
(behead 100,000 people as an example)
However, we have numerous quests that aren't actually puzzles in any sense. For
example, we have a quest to send a certain number of flowers, another to buy a
certain number of town crier messages, and so forth. These would correctly be
implemented as achievements (except they predate achievements by Quite Some
Time), and it is largely the existence of quests like this that muddy the definitional
parameters.
So, let's outline our philosophies on these two gameplay mechanics.
Quests are:
391
Epitaph Online



http://drakkos.co.uk
Puzzles
Entirely in-character
Subject to our rules on secrecy and spoilers
Achievements are:



Participation incentives
A combination of IC and OOC elements
In the public domain for discussion
And, let's not forget that, from a development perspective, achievements are
tremendously easier to implement than even a simple quest.
How Often Should I Add Achievements?
I would like people to be pretty free in handing out achievements. They are an
ideal way of focusing attention on game features that may not have had a lot of
publicity, a great way of providing XP to newbies, and an equally good way of
rewarding people for playing in ways that do not get proportionately rewarded by
our rather combat heavy advancement system.
However...
We don't want to be ridiculous about it. There is a temptation to put in a dozen
achievements for each minor development to make sure people pay enough
attention. That alas just devalues the rest of them. Imagine someone for example
adding a street with five shops, each with a small achievement to go with them
where the achievement is 'Buy $X of stuff from this shop'. That's a pretty
transparent attempt to curry interest. Instead, a single achievement of 'buy $X of
goods from these shops' is more appropriate. Even then it's questionable, since
it's not really a fun achievement.
For complex subsystems in a development you may want to add one or two
achievements specifically focused on that subsystem directly, in addition to the
general area achievements. So you may have 'be punished for every faction crime'
as an achievement to encourage participation in your subsystem. Lord knows we
have enough subsystems that don't get used enough.
Basically, I'm saying that because APs are so easy to give out, we should have a bit
of discipline in what we chose to give them for, and not overdo it. Achievements
should actually mean something if they are to be worth the name 'achievements'
392
Epitaph Online
http://drakkos.co.uk
My First Achievement
Okay, let's start developing our first achievement. To begin with, we need to
become familiar with the format of an achievement file (a .ach file). These are
located in one of two places:


/data/achievements/, for achievements that are mud-wide
/d/<domain>/achievements/ for achievements that are domain specific.
For achievements within a particular domain, it is the domain administration of
that domain who has the authority for approving or rejecting achievements. For
/data/achievements/, you should give me (Drakkos) a nudge. If I am not available,
find a fellow creator with sufficient seniority (the rule is – if you have to ask if you
have the seniority to approve something, then you don't have it).
Let's take a look at a simple achievement - one for killing ten zombies:
::item "killing:plight of the living dead"::
::->name:: "plight of the living dead"
::->story:: "put a few of them down"
::->level:: ACHIEVEMENT_MINOR
::->criteria:: ([
({"killed zombie", "Zombies killed"}) : 10
])
::->category:: ({"killing"})
::->instructions:: "This achievement is awarded upon killing 10 zombies "
"of any type."
The first part of the achievement (the 'item' bit) is used internally in the data
compiler to build a mapping of achievements in a particular domain. You don't
need to worry about this at all, just as long as you're sure that this value does not
duplicate anything elsewhere in the system. By convention, we use the domain
responsible followed by the name of the achievement for this. This is an internal
value – nobody ever sees this. The rest of the information you set is in the public
domain – it'll all be viewable by players in one form or another.
Name is the title your achievement is going to have – this is what will appear when
people browse or achieve it. Story is the little 'jokey' description of what people
did to get the achievement. This one, for example, will appear in the player's
achievements list as:
Plight of the living dead, in which you put a few of them down.
You do not put in the 'in which you' bit – that's added automatically to provide a
measure of consistency with the stories we use for quests.
The level is how much of an achievement this actually is – the values this can be
set to are defined in achievements.h. This directly impacts on the amount of XP
an achievement awards.
393
Epitaph Online
http://drakkos.co.uk
We'll come back to the criteria bit – it's the most important part of your
achievement file.
Category is, unsurprisingly, what categories this achievement falls under. These
are viewable on the website or in the MUD using the 'achievements' command.
Finally, the instructions contain the detailed explanation of the achievement. This
is available through the website or through the 'achievements details' command.
Our philosophy is that there is no secrecy on achievements, so be as detailed as
you can with this.
You should be familiar with the format by now - it's just a data file, like so many
files on Epitaph.
Criteria
Okay, but what about that criteria part? That looked a bit more complicated!
The criteria is the thing that drives your achievement – it's the bit that tells the
achievements handler whether or not a player has passed the threshold you have
set for it.
Each player, via the achievements handler, has a multi-purpose mapping of – well,
let's call them 'criteria values' that you can manipulate via code. There is no
restriction on these – you can call them whatever you like. The criteria of an
achievement is set as a mapping of criteria values and the thresholds they must
have for the achievement to be granted:
::->criteria:: ([
({"killed zombie", "Zombies killed"}) : 10
])
The key of this is an array - the first element is the internal value used for the
achievements handler, and the second is that the text players will see when they
browse the achievement - it's the bit linked to their achievement progress.
For this achievement, it is granted when the 'killed zombie' criteria value is ten or
greater for a particular player. The achievements handler has no knowledge in
advance of what these values are going to be, and it has no idea when this value
should be changed – this is where the link between your code and the
achievements handler is needed. You tell the achievements handler when it is to
change this value, and the handler does the rest. You do this through the player
function adjust_player_value. To ensure interoperability of game systems such
as the faction handler, alignment system, goal handler and so on, we re-use these
codes across the game. When you call adjust_player_value on a player, it will
update it in all the appropriate handlers. When a player kills a zombie then, there
is a line of code like this:
player->adjust_player_value ("killed zombie", 1);
When this is done, all the handlers update their 'killed zombie' counter, and when
awards are appropriate, it marks the appropriate thing as completed - when your
394
Epitaph Online
http://drakkos.co.uk
killed zombie value reaches 10, you'll be granted the achievement.
That, in its entirety, defines a simple game achievement. An achievement file (the
.ach file) and the code that adjusts the value appropriately. Absolutely everything
else is done for you.
Criteria Matching
Criteria are set as a mapping because you can have multiple different values that
need to be hit for an achievement to be awarded. For example, we could have the
following:
::->criteria:: ([
({"killed zombie, "Zombies Killed"}) : 5,
({"killed survivor", "Survivors Killed"}) : 10,
])
With this as a criteria, the player must have the value of five or greater for their
killed zombie value, and ten or more for their killed survivor value.
This may not be exactly what you want – you may wish to provide two or more
ways to gain this achievement. The default matching routine for criteria is AND –
they get the achievement if they meet all of the criteria values. However, you can
also set it to use an OR criteria (they get it if they match any of the criteria value)
by adding this to your .ach file:
::->match_criteria_on:: CRITERIA_OR
Alas, you cannot mix and match these (you can't do a compound achievement in
which you have to do A and B or C) – if you're getting into that kind of complexity,
it's probably becoming more quest-like than anything else.
You may also wish to make use of arrays within your criteria. For example, let's
say you wanted an achievement that added a name to an array each time you ate a
corpse, and you wanted to grant an achievement when someone had eaten the
right four people. You can do this in your criteria:
::->criteria:: ([
({"Right people eaten", "people eaten"}) :
({"drakkos", "eek", "hambone", "ploosk", "haug"})
])
And then when it comes time to adjust the player's achievement values, rather
than using adjust_player_value, you use instead add_player_value_array.
player->add_player_value_array ("people eaten", "drakkos");
Arrays in an achievement have a maximum size, based on the largest comparison
size set in related arrays – this is to keep the memory requirements down and to
make sure people don't try to keep an array of every room a player has visited for
395
Epitaph Online
http://drakkos.co.uk
their 'visit every room in the world' achievement. The size of an array will default
to ten, and if you start adding to an achievement array that already has the
maximum number of elements, it will lose the first one and add the new one to
the end.
By default, this method will not allow duplicates in an array. If the method call
above was called three times, there would still only be an array containing the
name "drakkos".
You can force it to have duplicates by adding a last parameter of 1:
player->add_player_value_array ("people eaten", "drakkos", 1);
Call this three times and you end up with an array that contains the name
'drakkos' three times:
({"drakkos", "drakkos", "drakkos"})
You can also remove things from the array with the
remove_player_value_array call:
player->remove_player_value_array ("people eaten", "drakkos");
This will remove one instance of the value, but you can force it remove all
instances by adding a 1 to the parameter list;
player->remove_player_value_array ("people eaten", "drakkos", 1);
By default, the handler will attempt to match on array contents – so the player's
criteria value has to have in it all of the elements indicated by the criteria.
However, you have two other options – you can match on the size of the arrays by
adding the following to your achievements file:
::->compare_arrays_with:: CRITERIA_SIZEOF
Or you can check to see if the player's array contains a specific value:
::->compare_arrays_with:: CRITERIA_MEMBER_ARRAY
Let's look at how that would work in different situations:
::->compare_arrays_with:: CRITERIA_ARRAY_MATCH
::->criteria:: ([
({"Right people eaten", "people eaten"}) : ({"drakkos", "eek", "hambone"})
])
This achievement will be granted if and only if the array associated with the
'people eaten' value contains the four specific names mentioned.
::->compare_arrays_with:: CRITERIA_SIZEOF
::->criteria:: ([
({"Number of people eaten", "people eaten"}) : 10
])
396
Epitaph Online
http://drakkos.co.uk
This achievement will be granted if the people eaten array contains ten elements.
It doesn't matter what those elements are.
::->compare_arrays_with:: CRITERIA_MEMBER_ARRAY
::->criteria:: ([
({"Right person ate", "people eaten"}) : "drakkos"
])
This achievement will be granted if and only if it contains the value 'drakkos'.
What about if we mix it up a bit?
::->match_criteria_on: CRITERIA_AND
::->compare_arrays_with:: CRITERIA_MEMBER_ARRAY
::->criteria:: ([
({"Right people eaten", "people eaten"}) :
({"drakkos", "fole", "garrion", "eek"})
({"Right amount of people eaten", "number of people eaten"}) :
100
])
This achievement will be granted only if the people eaten array contains the four
specific names and the number of people eaten criteria is 100 or higher.
You can create some pretty sophisticated achievements in this way by mixing and
matching criteria. Alas, you can only set one way of comparing arrays as of the
time of writing – anything more complicated is once again verging into territory
best explored by quests.
A Little More Complicated
Ah, you may say – a lot of the achievements in the game are more complicated
than that!
This is true, but these are achievements that hook into game handlers or call
functions on objects to generate their criteria values. For most achievements, they
should absolutely be as simple as indicated above.
However, there is nothing to stop you using a function pointer as your criteria if
you want to do something a little more complicated. However, if you do this it
needs you to trigger the checking of achievements a little differently.
When you provide a specific criteria value (such as 'killed zombie'), the
achievements handler can work out which of these belong to which achievements.
However, if a criteria is a function it has no way of knowing what achievements
can belong to that pointer. If the function pointer references an external object
(such as a handler), the achievements handler likewise has no way of knowing
when the internal state of another object changes. Thus, we need to explicitly tell
the achievements handler when the internal state of other objects have been
modified.
397
Epitaph Online
http://drakkos.co.uk
Note – this_player() in an achievements criteria is a no no. Achievements can be
checked while people are not online, or through the web, or in situations where
this_player is 0. Likewise, find_player is not to be used in achievement criteria for
very similar reasons.
If you wish to check the value of a function defined on a player, you can either use
the player handler (which has a number of useful methods), or the method
call_function_on_player in the achievements handler – this will ensure the
player is online and return the cached value of a criteria if no player can be found.
Please, do not attempt to call functions on players directly!
The call_function_on_player method takes two parameters - the name of the
player, and and array containing the function to call and any parameters that go
along with it.
Let's look at a simple example of an achievement using a function pointer:
::item "mudlib:stayed the course"::
::->name:: "stayed the course"
::->story:: "stayed the course, you held the line, you kept it all together."
::->level:: ACHIEVEMENT_MINOR
::->creator:: 1
::->first_person:: "maestro"
::->criteria:: ([
(: (time() - ACHIEVEMENTS_HANDLER->call_function_on_player ($1->name,
({"query_character_start"}))) / (60 * 60 * 24) :) :
180,
])
::->category:: ({"social", "age"})
::->instructions:: "This achievement is awarded upon having a player account "
"of six months or older. If you have refreshed, the time starts "
"counting from the time of your most recent refresh."
All of this is stuff we've talked about before, with the exception of the criteria:
::->criteria:: ([
(: (time() - ACHIEVEMENTS_HANDLER->call_function_on_player ($1->name,
({"query_character_start"}))) / (60 * 60 * 24) :) :
180,
])
That is a pretty dense line of code, so let's extract bits of it so that we can see
what's going on. First, let's look at the key and the value of the criteria mapping:
Key
(: (time() - ACHIEVEMENTS_HANDLER->call_function_on_player
($1->name, ({"query_character_start"}))) / (60 * 60 * 24) :)
Value
180
Now, the (60 * 60 * 24) calculation gives us a number that is (roughly) equivalent
to the number of seconds in a day, so we can unroll this function a bit farther:
Key
Value
398
Epitaph Online
http://drakkos.co.uk
(: (time() - ACHIEVEMENTS_HANDLER->call_function_on_player
($1->name, ({"query_character_start"}))) / 86400 :)
180
The call_function_on_player says 'hey, call query_character_start on the player if
they are online, or give me back their last cached value if they are not. For me,
that value is 1261078145, so let's slot that in:
Key
(: (time() - 1261078145 / 86400 :)
Value
180
If the result of this evaluation is greater than or equal to 180, then the
achievement will be granted.
The first parameter passed to the function pointer is the player details class
relating to the player we are checking. The full definition of this class is in
achievements.h, but the name parameter of this is usually all you'll need:
$1->name
You may find upon doing this that you get an error relating to achievements not
being disambiguated. Don't worry about what this means – you fix it by adding
this fairly ugly piece of code;
((class player_entry)$1)->name
You may only get this error if you are using classes in a header file used by the
achievements system.
Anyway, that's an achievement using function pointers. However, the
achievements handler has no way of knowing when it should check this, so
checking it has to be triggered elsewhere. There are two ways of doing this.
The first is for when achievements belong to a common category and should all
be checked at the same time – for example, the MUD continually (on a delay)
updates how long you've been online. When we do that, we say 'And now you've
done that, check to see if this now warrants an achievement:
ACHIEVEMENTS_HANDLER->check_achievement_by_category ("player name",
({"age"}), 0);
The 0 refers to whether or not these are achievements being granted by the
legacy command – if it is set to 1, it will suppress all informs except those that go
to the player.
If you have a smaller subset of achievements you want to award, you can check
specific achievements:
ACHIEVEMENTS_HANDLER->check_achievements ("player name", ({"achievements",
"to", "check"}), 0);
These calls go wherever the internal state of the object being checked changes –
399
Epitaph Online
http://drakkos.co.uk
so for the age ones, they go into the player heart beat (although they don't get
triggered every heart beat), for the faction reputation ones they go wherever the
state of faction standing is altered, and so on.
Other Achievement Settings
Titles can go along with achievements, but these (with rare exceptions) should go
along with achievements of the LARGE level or higher. Check with your domain
administration to see whether or not your achievement can grant a title.
All you have to do to provide a title is to indicate it in your achievement file:
::->title:: ({"awesome"})
f you want to temporarily switch off an achievement, use the inactive flag. Don't
delete the achievement itself, it won't make as much of a difference as you'd like:
::->inactive:: 1
If you want to set the achievement as first obtained by Maestro (which we do when
we can't know who was the first person to get it), then we do the following:
::->first_person:: "Maestro"
If it's in playtesting, then:
::->playtester:: 1
If you want to give a custom message to the player getting the achievement:
::->message:: "You rock!\n";
And if you want to register a callback for when an achievement is completed:
::->callback:: (: load_object (MY_HANDLER)->register_awesome ($1->name) :)
There are other settings too, but at this point you are more than competent
enough to go hunting for that functionality yourself.
I've written an achievement! What now?
Congratulations!
So, if you're sure you've got permission to make the achievement active, all you do
is save your .ach file into the appropriate directory (/data/achievements/ for
mudlib achievements, and the appropriate achievements sub-directory of another
domain if it's domain specific).
Then, you use the rehash command to make that achievement active - the rehash
command will make sure that the achievement works and won't destroy the
400
Epitaph Online
http://drakkos.co.uk
handler (which a badly coded achievement can do):
rehash achievements
If you get an error message doing this, you need to fix your achievement before
making it live.
A second or so later, there will be an announcement to the MUD indicating that a
new achievement has entered the game. Then you just sit back and bask in the
loving, warming glow of the warming love of our player-base.
Ack, it sucks! How do I get rid of it?
You may have tried to delete the .ach file and found it didn't make much
difference – that's because the handler stores details about your achievement so
that it can make a note of when it is achieved and how many times and so on. If
you are sure you want that achievement to go away, you can do the following call:
call clean_achievement ("my achievement")
/mudlib/handlers/achievements_handler
And bam, away it will go!
Achievement Levels
It's not necessarily easy to work out what level your achievement should be,
because there are all sorts of factors. The rough mapping I use to determine the
level is as follows:
Achievement
Levels
Minor
Points
Notes
1
Small
2
Moderate
Large
Huge
Massive
Epic
Legendary
Mythic
3
4
5
6
7
8
9
Give out like candy. At most, thirty minutes of effort to
obtain.
An hour or so of effort dedicated to the achievement, or
a longer period of time if the achievement is granted by
doing what people would do anyway. Killing 100 o a
specific kind of NPC might take an hour or so to do and
be a small achievement, but getting a common skill to
level 150 might take much longer – but it's something
you'd be doing anyway
Half a day or so of effort.
A day or two of effort
Three or four days of sustained effort
Several weeks of sustained effort
A year of sustained effort
Several years of sustained effort
A decade of sustained effort
Time invested is not a straight-jacket and very difficult to measure. It's just
401
Epitaph Online
http://drakkos.co.uk
roughly how big an effort you can realistically expect of people when setting your
criteria. You also need to factor in things like cost in money, cost in skills, risk,
etc, etc. Feel free to nudge me if you want some suggestions on where a
particular achievement should sit.
As to the level, feel free to adjust it up or down as needed, although if you're
adjusting it two or more levels you should consider why you feel the need to do
that. Have a solid reason like 'This is really risky', or 'it loses someone months of
faction reputation', or 'it's moderate even though it takes a year, because it's an
achievement you work towards even if you're not paying attention'. For example,
login time is something you accrue regardless and is an achievement in the
system, as is the time since your first login. Despite the guideline being a decade
for mythic, it actually needs 20 years to have passed for 'Dawn of the World' to be
achieved because the time passes without you doing anything
Conclusion
Achievements are a powerful game-play mechanic, and they have a tremendous
impact on the kind of game that we have. From a creating perspective, they excel
at adding value to the code that we have with a minimum of creator effort. It's
well worth spending a bit of time thinking where you could enhance your domain
by drawing people to the right kind of locations through achievements.
402
Epitaph Online
http://drakkos.co.uk
So Here We Are Again...
Introduction
I guess it must be fate. We have reached the end of our journey together. There
are no more mountains to climb. No more roads to travel. We must simply
embrace and wish each other a long, healthy life.
I haven't told you all there is to know about LPC. Oh, far from it. What I have done
though, and I hope successfully, is give you the grounding you need to be able to
take control of your own learning. Really, that's all I can hope for – that as a result
of the intimate candle-lit discussions that we have shared, you have in some way
learned, perhaps by osmosis, the skills you need to know where to look for
further information.
What's On Your Mind?
Zombieville has been quite a complicated development, and it has been tied quite
tightly into the discussion of several important programming concepts. It might
be worth spending a few moments talking about what you have actually been
exposed to in the course of this document.
First of all, we learned about inheritance and how it can be used to build a
framework for an easily modified development. We made use of this knowledge
throughout our code, making sure we had our own bespoke inherits for every
kind of object we used.
Then we talked about data representation, and the uses of the class data-type.
Data representation is incredibly important and you should spend a lot of time
thinking about how you approach this in any even moderately complex
development.
We introduced the topic of add_commands, and the various syntaxes and patterns
that go with it. This opens up a world of interaction possibilities within our rooms
and objects. This allowed us to develop our first quest, and that in turn led to our
discussion of the quest handler and the library handler, as well as the quest utility
inherits that can be used to simplify data management.
Inherits are one half of a code architecture – the other half comes from handlers,
and we built one of these in the process of developing Zombieville.
We talked about events, and how we can trap these to provide responsive
behaviour in our NPCs, and how data persistence can be implemented on the
MUD. We also talked about the NPC behaviours, bosses, and boss powers that you
can use to make your NPCs throw some interesting attacks out there when they
find themselves in combat.
403
Epitaph Online
http://drakkos.co.uk
As part of our knitwear shop, we made an item that had to save its state between
logins, and that lead us to the discussion about the auto loading system. We
wrote an effect. We learned about achievements, and we talked about function
pointers. We've talked about an awful lot in this material, and all of it is important
for you to know as a fully rounded Epitaph creator.
This does not complete your toolbox – there's all sorts of things we haven't talked
about. We haven't talked about terrains, or socket programming, or coding for the
web - you're well placed now to learn about all of that stuff yourself. There is
always more to learn, but now you will not only know enough to code some very
interesting objects, you'll also have the necessary background information to be
able to understand new and interesting code that comes your way.
Help Us to Help You
LPC is a niche language, and it's not especially well documented. The same thing
can be said for large portions of our Mudlib. Here be dragons, as they say. There
are all sorts of clever features and little tricks that people know how to do, and
every now and again you'll find one that throws you. There's nothing I can do to
get around that, except perhaps spend the next ten years of my life writing more
and more of this kind of thing. I don't intend to do that.
However, there's no reason that you can't help yourself and the people around you
as you make your way through the world of being a Epitaph creator. Whenever you
discover an undocumented feature, then document it. When you've spent a couple
of hours puzzling over a bizarre bug, then tell people about it. We're all in this
together – don't think that your knowledge is in any way commonplace. You may
be the only one who knows what you know, or the only one who spent time
working out how that weird situation manifested itself.
We have a wiki that is a great place to record little essays on things that you've
discovered, and there is always the learning board, or the Epitaph Oracle. More
than anything else, if you're doing something strange or complicated, then
document it! Better still, do it in a less complicated way. Very few creators are still
around from the very first days of the MUD. Along the way, we lost an awful lot of
knowledge – every day we live with the consequences of that in the shape of
design decisions that were made, or strange coding syntaxes that were
implemented.
In ten years time, the MUD will hopefully still be here – you may not be. Part of
your legacy should be what you have contributed to the knowledge-base that we
collaboratively build.
Where Do You Go From Here?
Now that you've finished with this material, where do you go? What should you be
doing next to move on to the next level of being a Epitaph creator?
404
Epitaph Online
http://drakkos.co.uk
I am a big believer that the only way you get better is to set yourself a task that
you don't know how to complete. Don't stay inside your comfort zone – try to find
something new and exciting in every project you work with. There is very little
that cannot be achieved within the Epitaph Mudlib and the framework that LPC
provides, and the struggle to do things that you have never done before is where
your real learning begins. You don't learn how to code from a book, you learn by
sitting down and puzzling over a difficult problem.
You've got access to a lot of code as a Epitaph creator. Perhaps not access to
change it, but you can certainly read code without any difficulty. Reading the huge
amount of code we have in the various domains and Mudlib directories will
introduce you to new and quirky (and not so quirky) ways of building complex
objects. From this point on though, it's all on you.
There are several more general resources I can recommend for those who want to
become better coders. The language you use is just a frame on which you hang
your development – all programming is essentially the same thing, at least within
particular families of code. Come speak to me if you have specific requests for
things you'd like to learn more about.
Conclusion
So long, farewell, adieu. This text has been a long time coming. LPC For Dummies
one, the first edition, was written in October of the year 2000. I'd been a creator
for about a year and a half. People liked it though, and I was constantly asked
about a sequel that would address the more complicated parts of LPC
development. I've been promising it ever since then, and I am delighted to finally
be able to provide it.
My plans for Intermediate LPC changed a lot over the years. Really now it's one of
four tightly related works. Together they form a kind of informal ‘Epitaph Creator
Manual'. A lot of material that was originally planned for Introductory LPC 2 made
its way into other works, and you should consider the these texts to be four
separate sections of one larger book. I hope, in your travels, you find it of use to
you.
405
Epitaph Online
http://drakkos.co.uk
Section Four
Working with
Others
406
Epitaph Online
http://drakkos.co.uk
People Skills
Introduction
I know it sounds horribly touchy-feely – we're game developers, not teenagers on
a camping trip. However, the most vital skill for life that you'll pick up from being
a Epitaph creator is how to work with other people. Absolutely everything we do is
a collaborative exercise.
In terms of your immediate environment, you are part of a team in your own
domain. However, before too long you start to collaborate within the larger
context of creatordom as a whole. At that point, your ability to work with others in
a large-scale development environment is perhaps your most valuable asset.
Whole New Skills
It's very rare that developers work within as close quarters as we do on Epitaph –
thus, even those with considerable coding experience are going to find this a
largely unique experience. There are things about multi-developer environments
you just don't learn until you start working in one.
I teach software engineering at my local university. The students I teach are
competent coders, who have even got a little bit of group-work under their belts.
None of them appreciate the intricacies of environments such as Epitaph because
you simply have to be part of it. The things that software engineering courses
teach in the abstract are things you are going to learn about first hand.
It's important to provide some caveats here. First of all, working well with others
doesn't mean you have to like the people you work with. It's always better if you
do, but perfectly satisfactory collaboration can occur even when the participants
hate each other. Gilbert and Sullivan for example had a notoriously quarrelsome
relationship, but it didn't stop them penning some enduring popular works.
Actually liking people isn't necessary - successful collaborations can be born from
affection, trust, or respect. Ideally you have all three, but one is enough to build a
working relationship. The problem comes of course when none of these are
present, but those circumstances are thankfully quite rare.
It doesn't matter if you like the people you work with, it only matters that you can
work effectively with them. We can't force anyone to feel a different way about
another person than they actually do, nor would we want to.
Part of the skill-set you need to build as a creator is the ability to successfully
collaborate. That involves a whole lot of concepts that are new to most people – it
involves understanding a complex and dynamic social context, as well as
407
Epitaph Online
http://drakkos.co.uk
understanding the software development process. It involves becoming familiar
with technologies that are often for rather alien purposes. In short, it's learning a
whole lot of entirely new skills.
People tend to look down on MUDs as development environments because of the
stigma attached. Most MUDs are vanity affairs in which a handful of coders put
together an ad-hoc, unprofessional game based on some stock code-base. Such
games rarely have more than a dozen or so players, and those players tend to be
drawn from already formed social circles.
Epitaph is not one of those MUDs - we are a derivative of an extremely large and
successful MUD, and many of the developers here (myself included) have moved
here from Discworld to pursue our development agenda. Our game is
tremendously complex, well structured, and with an emphasis on the
programminmg architecture that underlies the game.
However we are also a volunteer environment, and that introduces a whole range
of new issues. We don't expect people to know how to code. We don't expect
people to understand formal software development. We don't expect people to
know about the complex etiquette that goes along with multi-developer
environments. Over the years, that has led to an adoption of code written to
dozens of different standards, in dozens of different styles. That causes many
problems. Most of this code we have inherited as part of the DWMUD mudlib
from which we derive, and refactoring (see later in this book) is a constant activity
of our developers.
It's hoped that this material makes you understand the importance of some of the
fairly abstract things we tend to insist on here on Epitaph, and why they are not
arbitrary exercises in nit-picking. There's a good reason why we ask you to do all
of the things we ask you to do.
Standard Standards
This is a rather grand heading for something we don't actually have...
Within Epitaph, we all realise that those who come as developers do so as
volunteers. That means we have fairly limited leverage in forcing a particular
agenda. To be sure, domain leaders have the authority to hire and fire within their
own domains, but we much prefer to have people working with us than not. As
such, things like our style guidelines are only inconsistently followed.
It's my hope that, after reading through this material, you understand why we ask
for these things, and that through knowing the intention you'll actually be
motivated to follow the standards. It's too late for a lot of people, but if you're just
starting out with us it's a fantastic opportunity to get into the habit of writing
code that is structurally clean.
I would like to add a word of warning here – the things that I am going to talk
about are surprisingly emotive issues. You will find creators trying to insist that
408
Epitaph Online
http://drakkos.co.uk
their particular formatting style is the best, sometimes 'humorously', sometimes
not. This is an extremely unhelpful situation, and I would ask you to ignore any of
these comments.
It honestly doesn't matter what standard of code that is adopted, the only thing
that matters is that everyone uses it. There is virtually no difference in code
readability from one standard to another, but it dips dramatically when everyone
is using their own standard. We compromise a little so that we all have a more
pleasant development experience.
I am in no way saying that the style of coding that is outlined in these documents
is the best way to layout code. I am making no value judgements at all – however,
we need to decide on one standard and this is the one we're going to use. We'll
talk about that in the next chapter.
Professionalism
Volunteers we may be, but we do like on the whole to maintain at least a veneer of
professionalism. Some of us are worse than others at that, but it's an ideal to
which everyone should aspire.
That means, your personal issues with someone shouldn't get in the way of you
fulfilling your obligations as a creator. If you and another person are working on a
project, then you have to put aside your disagreements enough to allow a working
relationship to emerge.
As far as is possible, you should keep personal issues off of the public channels. If
you feel you need to tear into someone for their (in your opinion) gross
incompetence then do it in tells. Otherwise it's just awkward for everyone. Failure
to do this is only going to get you a reputation as someone who doesn't ‘play
nicely' with others, and if that persists the only real option is for you to be
removed as the obstacle you are. This is an unusual step taken in rare situations,
but there are precedents of people who just could not get along with anyone who
are no longer creators.
It's often harder with some people than it is with others… in life, there are just
people who rub you up the wrong way no matter what they say or do. Your best
bet in such circumstances is to simply try and maintain a degree of civility when
interaction is required, and avoid them otherwise.
In situations in which you simply cannot resolve your differences, it's worth
looking for a mediator – ideally someone of higher rank than both so as to allow
for ‘binding agreements'. If your problem is with someone in your domain
administration, you should arrange for a discussion between all members of the
domain administration team to see how the situation can be resolved.
In all cases, you want to provide a framework for constructive engagement with
your colleagues. Where that can't be achieved, you need to find a way to simply be
around them. Nothing sows more disharmony into your fellow creators than a
409
Epitaph Online
http://drakkos.co.uk
persistent and public slanging match. The creator channel, and the boards, are
not the place for that kind of thing.
Conclusion
We're going to cover a lot of ground in the chapters of this material, including
ground that will be entirely new even for a lot of experienced developers. In all
cases, I am going to ask you to engage with the material and not dismiss it as an
irrelevance. As I have mentioned above, there is a reason why we ask you to code
to a specific style. Although we can't really force it in the same way that can be
done when people are being paid for their effort, it makes the MUD a much nicer
place for us all to develop if we can rely on a little professional courtesy from our
colleagues.
410
Epitaph Online
http://drakkos.co.uk
Code Layout
Introduction
There is nothing that will make your code more readable than having a clean
layout. Inversely, there is nothing that will make your code less readable than
having a bad layout. There is a set coding standard than we have on Epitaph, and
while it has been inconsistently applied over the years it is something you should
try to get into the habit of before you are Too Set In Your Ways.
This is a surprisingly emotive issue for some people – for some reason, people
seem to invest a lot of their own self-worth in the choices they make in terms of
laying out code. The truth is, it doesn't matter in the least which standard you
choose to use – they're all equally readable. The only thing that matters is that
everyone uses the same standard. Don't be one of those creators who stubbornly
refuse to compromise on this point – it's stupid, and actively unhelpful. Likewise,
don't pay attention to those who try to force their own standard upon you. Just
roll your eyes and move on.
Code Formatting
This is going to be a rather dull section, but it's important that we talk about it.
I've already said this but I'm going to say it again – having a clean style for code is
the most important thing you can do to make your code readable. The standard
that we apply is as follows:







Indent two spaces per level of coding structure.
Lines of code no longer than 79 columns.
The opening brace of a structure is placed on the same line as the structure
to which it belongs.
All defines should be in UPPER CASE.
Functions are all in lower case, with an underscore separating words. In
java, a function might be thisIsAFunction. In LPC, that would be
this_is_a_function.
All for loops and if statement to have opening and closing braces, even if
they are not syntactically required.
Use spaces, never tabs!
That's all – it's not much to remember, and once you get into the habit of it you'll
do it subconsciously. When I started coding on Epitaph, my own personal standard
was contrary to all of these. As time went by, I migrated towards the Epitaph
standard because it made things much easier for everyone involved, and it came
as no real cost to me.
Let's look at two bits of code, one without formatting, and the other formatted to
Epitaph standards. First, without formatting:
411
Epitaph Online
http://drakkos.co.uk
void this_is_a_function() { for (int i = 0; i < 100; i++) {if (i % 2 == 0)
{tell_object(this_player(), i + " is an even number.\n");}else
{tell_object(this_player(), i + " is an odd number.\n");}}}
And then formatted to our internal standards:
void this_is_a_function() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
tell_object(this_player(), i + " is an even number.\n");
}
else {
tell_object(this_player(), i + " is an odd number.\n");
}
}
}
Hopefully the latter example is obviously more readable. There are also coding
clues given for you – indenting to a different level depending on the depth of the
structure gives you an instant visual hint as to where opening and closing braces
should go. It demonstrates ownership – you know that the if statement belongs to
the for loop, because that's what the indentation shows.
Look at those two samples again, slightly altered:
void this_is_a_function()
{ for (int i = 0; i < 100; i++)
{
0)
{ tell_object(this_player(), i + " is an even number.\n");
tell_object(this_player(), i + " is an odd number.\n"); } }
if (i % 2 ==
} else
{
This code won't work, and it's not immediately apparent why. On the other hand,
if we reformat it:
void this_is_a_function() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
tell_object(this_player(), i + " is an even number.\n");
}
else {
tell_object(this_player(), i + " is an odd number.\n");
}
}
The eye is instantly drawn in the second example to the fact a closing brace
doesn't exist where we would expect it to. The layout actually makes it easier to
code.
This additional readability can be lost when multiple people with different coding
styles work together on the same file:
void this_is_a_function() {
for (int i = 0; i < 100; i++)
{
if (i % 2 == 0) {
tell_object(this_player(), i + " is an even number.\n");
}
else
412
Epitaph Online
http://drakkos.co.uk
tell_object(this_player(), i + " is an odd number.\n");
tell_object (this_player(), "This is a line that appears on "
"every number!\n");
}
}
Rather than this code being easy to use because standards have been applied, it
becomes harder to read because inconsistent standards have been applied.
Now that we've seen the difference the formatting makes, we'll talk about each of
these rules in turn and why they are in place.
Indent Two Spaces Per Level of Coding Structure
There's no magic formula as to why two spaces is best, other than it gives you
slightly more screen real-estate to work with while still showing the relationship
between coding structures. I will emphasise something I said before – people will
try to convince you that the number of spaces they use is a better way of laying
out code than the number of spaces this document tells you to use. Pay these
people no heed, for they are deeply tedious. There is no real difference between
two spaces, three spaces, or four spaces. The only thing that matters is that all
agree to use the same level of indentation.
Lines Of Code No Longer Than 79 Columns
This is a readability issue – many people are still working within the 79 columns
of a standard telnet display. If you have code that goes over that line length, it is
virtually unreadable.
For example:
add_conditional_clone_item ("christmas tree", "There is a bit of a bustle,
an
d $N is "
"erected by happy little elves.",
(: SPECIAL_EVENT_HANDLER->query_current_special_event ("christmas") :),
(: $2->add_property ("there", "in the corner of the room") :),
"There is a bit of a bustle, and $N is packed away by subdued little
elves.
");
Sadly, that's from a piece of my own code...
It makes even room descriptions hard to read, so imagine what it does for
complicated code structures.
The Opening Brace of a Structure
Once again, there's no reason why this should magically be so – it's just that in
order for there to be a standard, everyone has to do the same thing. As with the
two space rule, it provides a little extra real estate on the screen when viewing
413
Epitaph Online
http://drakkos.co.uk
things in the very restricted environment of the MUD.
All Defines Are In Upper-Case
Having an ‘at a glance' way to tell which values have been defined and which are
drawn elsewhere from the code aids tremendously in readability. Moreover, when
defines aren't in all upper case, it dramatically detracts from readability because
everyone expects the alternative. Violating that assumption has a measurable
impact on code comprehension.
Functions Are In Lower Case
This is usually a convention of the language rather than a convention of Epitaph
particularly. The stylistic conventions obeyed as part of the internals of the
programming language define how our functions are to look. The MUD's efuns for
example use this_kind_of_standard and so that's what we use for our own code.
Otherwise we need to make a mental check each time we use a function – ‘is this
an efun, an sfun, or an lfun?' and choose the formatting accordingly.
All Structures To Have Opening and Closing Braces
LPC allows you to omit these on for, while and if structures if you have a single
line of code to be executed:
for (int i = 0; i < 100; i++)
tell_object (this_player(), "The number is " + i);
This is syntactically correct, and it will work as intended. However, when you come
back to add more complex functionality you need to remember to put the braces
in, and those who work with your code need to be observant enough to notice
that you haven't already put them in place. You gain nothing from omitting them –
it's like using an indicator in a car, you should use it even if you're alone on the
road because it's easier overall when such an activity is an unconscious rather
than conscious decision.
This is an example of an area in which competing formatting standards will
actually cause a decrease in readability. Imagine person one, who indents to two
spaces and doesn't use opening and closing braces. then person two, who indents
to four spaces:
for (int i = 0; i < 100; i++)
tell_object (this_player(), "The number is " + i);
tell_object (this_player(), "Some stuff\n");
The visual clue here suggests that these two statements are part of the same for
loop. In fact, only the first belongs. This kind of ambiguity can be reduced by
simple layout and clarity of expression:
for (int i = 0; i < 100; i++) {
tell_object (this_player(), "The number is " + i);
414
Epitaph Online
http://drakkos.co.uk
}
tell_object (this_player(), "Some stuff\n");
Again, it costs you nothing to put in the braces, so you should get into the habit
of it being a ‘muscle memory' thing rather than a conscious choice.
No tabs
If you put a tab in your code, it creates a very ugly visual artifact when you read it
on the MUD – it gets interpreted as <TAB>:
bookcase->set_long( "This bookcase is made from oak and "
"varnished to bring out the glow. It has 2 shelves, "
<TAB>"upon which you can see some books, and other objects.\n" );
Bleuch. That's horrible!
Instead, use spaces, never use tabs. Luckily, if you are using a good text editor
you can get the best of both worlds – you can tab as much as you like, and the
editor will simply interpret it as a set number of spaces.
Make It Easy On Yourself
Provided you have a good text editor, a lot of this can be handled for you. We'll
use Ultraedit as our example of this. Other editors will undoubtedly have similar
facilities, but these are left for you to discover.
First of all, we want to remove the tabs from our code. This is the most important
first step to take. Go to Advanced->Configuration, and that will open up the
configurations editor. Navigate to ‘Word Wrap/Tab Settings':
415
Epitaph Online
http://drakkos.co.uk
Three of our rules can be automated for you – make sure ‘use spaces in place of
tabs' is selected. Notice here that we can set the Tab Stop value and the Indent
Spaces value – set both of these to two. Finally, you can also automate adherence
to line lengths by setting the wrap method. Ultraedit will thus do a big chunk of
the work for you, without you needing to worry about it all.
Conclusion
Standards are a good thing, and it would be tremendously helpful if you could get
into the habit of writing your code to them. Over the years we have had an
inconsistent approach to formatting, varying across domains, individuals and even
time periods. While it may have been more convenient for single people, we are a
team trying to achieve a collaborative goal. Everyone has to be willing to
compromise on this to make the whole project work together better. In the case
of code layout, you're not even being asked to compromise much – no matter how
fond you are of your own particular style, it is not so much better than any other
style that it justifies the lack of clarity that comes from inconsistency.
Additionally, please ignore those people who try to force you to deviate from
these standards with mockery, or bizarrely strident advocacy. There is nothing Big
and Clever about trying to undermine any effort to increase consistency of code
across a massive developer-base. In real world coding environments, you code to
the set standard or you lose your job – that's not an option we like to consider
here on Epitaph – the best we can do is appeal to your presumed desire to be a
useful, valuable member of a highly integrated and collaborative team.
416
Epitaph Online
http://drakkos.co.uk
Collaboration
Introduction
We are big on collaboration on Epitaph. At least in theory. In actuality, everyone
has their own particular approach to how and when they collaborate. However,
game development is an inherently collaborative endeavour, as the things that
you make available in the game will have an impact on many other things. If you
make a bank available, it alters the flow of money for all domains. If you add a
vault, it affects game performance. The connections are incredibly complex.
We have several good tools in place for enhancing collaboration, but they are for
naught if the will to collaborate is not present. In this chapter we'll talk a bit about
the collaboration styles you will tend to encounter, both here and in 'real life'
environments.
The Social Context of Collaboration
The social context of an environment is one of the key elements in fostering an
atmosphere that supports collaboration. Every context has its own particular
features.
Epitaph has a strong tradition of meritocracy in advancement, and this
meritocracy is usually demonstrated through a system of emergent authorial
leadership. In essence, you progress by showing yourself to be a 'safe pair of
hands' on the basis of the projects you are involved with and the contribution you
make. In addition to this is the value ascribed to seniority – combined with the
authorial leadership, authority accrues to those who have been around sufficiently
long to be considered 'tribal elders'. This pattern is also reflected in the
playerbase, where a distinction is made between 'newbies', 'midbies' and 'oldbies'.
The combination of these two social dynamics is common to many collaborative,
volunteer endeavours.
Collaboration is enhanced by the further tradition of 'ownerless code'. Code on
Epitaph does not belong to any particular creator, although one creator may take
a greater or lesser interest in its upkeep and maintenance. Code instead belongs
to a domain in the first instance, and the MUD as a whole in the second. It is not
only possible for another creator to modify code you have written, it is actually an
active part of the development context. Everyone owns the code in their domain,
and there are creators who have wider responsibilities that work across domains.
It's important that you understand your code is Communal Property, otherwise
you'll find it very difficult to cope – especially if your code is important enough for
people to take an interest in.
A sense of shared responsibility over code, and a tradition of authorial leadership,
are important traits in a successful collaborative environment. These features are
apparent in one of the most successful of modern collaborations – Wikipedia.
417
Epitaph Online
http://drakkos.co.uk
However, the social context is modified by the people who are involved, and some
people simply do not collaborate. Our environment facilitates collaboration but
doesn't mandate it – you don't need to take anyone's views on board while doing
your development with the exception of those of your domain administration.
Some people work best like this, but it's not a mindset which we like to
encourage.
There is often a generational gap that comes along with willingness to
collaborate. Older developers may be less willing to engage in such a process,
because they perceive development as a solitary effort. This is not something that
is especially pronounced in Epitaph, but it can be observed in other environments.
Younger developers now grow up in an atmosphere of extreme collaboration,
brought on by a culture of social networking and the prevalence of shared wiki
tools. Older developers are less familiar with this as a mindset, and so are often
somewhat resistant to broad and indiscriminate collaboration, preferring to
collaborate instead with a few hand-selected and trusted colleagues.
Different people have different ideas about what collaboration actually means.
Does it mean the intense collaboration of something like Wikipedia where changes
are small but accumulate with the weight of an avalanche? Or does it mean that
one person writes something, and another person writes a bit, and the first
person writes a bit more – essentially serial development. Or does it mean that
both individuals make their own attempt, and then the best of these two attempts
are merged together? All of these describe different, but perfectly acceptable,
models of collaboration.
You also tend to encounter one or two people who actively disapprove of
collaboration. It's not just that they don't collaborate themselves, but they actively
despair of collaboration in general. Authors such as Jaron Lanier have written of
an encroaching 'digital Maoism' in which individual ability is swamped by the
mediocrity of averages.
When building a social context, it is important to start with the people. For
collaboration to occur, first and foremost there must be a will to collaborate.
Some environments are set up in such a way that collaboration quite simply will
not happen. In a now-famous paper, Wanda Orlikowski discusses an attempt to
introduce a groupware product to a team of consultants; the implementation
failed, due to endemic social issues stemming from competition and no tradition
of mutual trust, as well as a deep lack of communication as to what the tool was
for.
The mindset many developers adopt when introducing collaboration technology is
what I like to refer to as the Field Of Dreams mindset – 'if we build it, they will
come'. Experimental evidence however shows that this is hardly ever true – people
have to see a need before they make use of the tool. Collaboration tools work only
if they make existing social processes easier to mediate.
Development in Volunteer Environments
418
Epitaph Online
http://drakkos.co.uk
Why do people choose to give their time and effort, for free, to a cause like
Epitaph? Everyone is going to have their own reasons for this, but there are certain
commonalities within different projects.
Many people report 'altruism' as a reason for participation. Whether altruism
actually exists or not is a philosophical quandary, but what can't be denied is that
people often feel a pull towards a cause in which they believe. Presumably you
enjoyed your time playing Epitaph, and felt sufficient draw to the game that you
wanted to devote your time to making it better. This is something reported often
in open source communities.
However, there are many additional benefits that come from participating in a
project like this. For one, you develop many skills that are genuinely marketable.
A number of Epitaph creators have profitably included their development
experience on their CVs when applying for jobs, and attribute at least some
measure of their success in those interviews to the skills they have developed
here. Many professional developers despair of the lack of attention paid in
university educations to 'operational skills' such as dealing with source control –
Epitaph gives developers exposure to the complexities of working within a
codebase of quite staggering complexity and size. That's something that sets you
apart right away from many other developers.
There is an interesting element to how people choose to join as creators in the
first place – unlike most volunteer coding movements, Epitaph does not
incorporate simply any change from any interested developer. Instead, Epitaph is
a 'hybrid open source' environment in which the game files are secret, the driver
is freely available, and public releases are made of the core mudlib. Self-selection
of contributions is a big feature of environments such as Linux or Apache, but it is
not reflected in our approach to development. Developers self-select in so far as
they choose to apply, but the process is much more like applying for a job than it
is developing for Linux as a movement. This has issues of scale that often
manifest themselves – when a domain leader is absent, a domain can grind to a
halt.
The administration of a domain is responsible for the ultimate vetting of quality.
The playtesters domain is an opt-in service for those domain leaders who wish to
make use of it, but the exact system for determining whether or not a
development is to go into the game varies from domain to domain. Some domains
make extensive use of peer review (as Forn did during its development of Genua),
while others have more informal processes in place.
What Are The Benefits of Collaboration?
There are several benefits that come from collaboration. Linus Torvalds, the man
responsible for building the first version of the Linux kernel, is credited by Eric
Raymond with formulating Linus' Law. This states, informally, that 'given enough
eyeballs, all bugs are shallow'. Collaboration allows us to harness the different
skills and abilities of many people – we end up with the whole being more than
the sum of its parts.
419
Epitaph Online
http://drakkos.co.uk
It's often surprising the wealth and depth of experience that is available when you
widen the parameters of your search. In any room of average people, you'll find
individuals with the strangest combination of skills – some because of their
occupation, and some because of their hobbies. The combination of skills that
people develop over the course of their lives lends a unique perspective to their
views and opinions – everyone views the world through the lens of their own
experience.
Aggregating the views of people with multiple sets of skills and abilities can lead
to results that are better than any single individual is capable of producing. The
book 'Wisdom of the Crowds' by James Surioweicki is an extremely interesting
discussion of this, and it is very relevant to the idea of intense collaboration in
programming environments.
Collaboration Tools on Epitaph
We have numerous tools for persistent communication and collaboration within
Epitaph. At the simplest level is the internal mudmail and board system – these
allow for communication, and at the core that's what collaboration is all about.
However we also have two tools that fall into the more modern category of
'collaboration software'. The first of these is our extensive wiki system – we use
the mediawiki engine for this. Everyone shares a wiki on Epitaph, so you can get a
good cross-domain and project overview of what's going on in the game.
There is a point of critical mass that needs to be reached for such tools to get
momentum. Simply using a tool, even if no-one else is using it, can generate
interest, and that can in turn generate further contributions. All it takes is one
person to get the necessary traction. Encourage people to read your contributions
– direct them to your wiki page when you're asked questions. Get people to look,
and you might just get people to join in.
The Wiki may be found at
http://survivor.imaginary-realities.com/wiki/index.php?title=Main_Page. Have a
read through - you may be surprised at what you find.
The second tool we have is our bespoke knowledge management software – the
Epitaph Oracle. This is a system for collaboratively eliciting the considerable
knowledge and expertise of our creator base. I would encourage anyone who has
a question, no matter what the question may be, to check Oracle to see if the
information is available, and then ask the question if it is not. Everyone who asks
a question is engaged in the task of knowledge elicitation – you are helping to
make information available for all those who follow.
The Epitaph Oracle may be found at
http://drakkos.co.uk/secure/oracle/oracle.c?base=creator.
Please contribute anything you think might be of interest, and ask any questions
that come to mind.
420
Epitaph Online
http://drakkos.co.uk
A Suggested Collaboration Process
First of all, your task is gathering ideas. You'll undoubtedly have many of these
yourself, so create a wiki page for your project and outline them. You'll find there
are people in the creatorbase who read every change made to the wiki (I'm one of
them, I'm a wikiholic) so even if no feedback is received it doesn't mean your
contribution hasn't been read. Update the page as thoughts occur to you – it can
be a useful project planning document for you and for your domain leader.
For a domain leader, keeping track of where each project is and how complete it
is is a complex task. The best thing that you as a developer can do to ease this
task is to keep your own developer page up to date - that can be tremendously
helpful. If you are developing a specific area, you might want to consider making
an abstract of the area available on your domain wiki, outlining features and
quests. Small areas may not lend themselves well to this, but it's great when you
can make use of it.
Once you've got your wiki page up and running, try making a post to your domain
board outlining what your plan is and where the wiki page can be found. A simple
request for 'any thoughts people may have' can elicit many useful suggestions
(though sometimes, no suggestions at all). Occasionally you will find that a
project has interest to someone beyond the people you would normally expect,
and that can lead to profitable, albeit unexpected, collaboration. As an example of
this, if you were coding an island full of pirates, it would certainly be of interest to
me as part of the development of the piracy system.
If anyone expresses a particular interest, have a chat with them to see the level of
their interest. You may find people who are interested in supporting particular
parts of the development, or who are interested in hooking in code of their own.
All of this is a great opportunity to make your area richer than it would be when
developed from the perspective of an individual.
Check with your domain administration to see if you can canvass the playtesters
for suggestions. The ptforum board is a great place to see what ideas they may
have for things they would like to see, or things that they definitely wouldn't like
to see. The more perspectives you can solicit, the more of a pool of good ideas
you'll have to choose from.
It's important to note here that this doesn't mean you abdicate ownership of your
project – you are soliciting feedback, but that doesn't mean you're obligated to
use it all. It's just that getting a wider range of perspectives will give you a much
better foundation from which to develop your thinking.
421
Epitaph Online
http://drakkos.co.uk
Conclusion
Collaboration is an entirely social problem – you should not confuse the tools with
the concept. Our tools exist to support existing social dynamics, not supplant
them.
Collaboration is an important part of what we do on Epitaph – everything impacts
on everything else in very complex and complicated ways. Our unique cultural
makeup has led to the emergence of certain organisational norms – a shared
ownership of code, authorial leadership based on meritorious contribution, and a
general respect for those who have contributed long enough to have become
'tribal elders'. All of these help support an environment in which collaboration can
flourish, but it still requires critical mass for it to be effective.
You can either be a barrier to collaboration by looking inwards, or you can be a
spark for further collaboration by doing your best to solicit feedback.
422
Epitaph Online
http://drakkos.co.uk
Social Capital
Introduction
Social Capital is the glue that keeps a society together. As a term, it refers to the
reserves of trust, respect, collegiality and norms of reciprocity that exist in social
networks. It's a measure gaining considerable traction in sociological and
economic debate – while it can't be quantified, it provides valuable qualitative
analysis of the level of function and dysfunction in an organisational environment.
What we're going to talk about in this chapter is how social capital is built in a
online technical context like Epitaph. The creator-base has a rather extreme
reputation for 'creator politics', but in the main this stems from a handful of
isolated but extreme problems, rather than being a systemic feature of the
environment.
Creator Politics and MUDs
We're a new MUD, and thus we haven't had enough time to blot our copybook as
far as all the drama and upsets go. As such, these comments will centre on my
experience at a certain other MUD.
The fact is that creator politics in most successful MUDs are nowhere near as
endemic as the popular player perceptions would indicate. Player perceptions are
distorted by the handful of disproportionately loud examples of ex-creators who
were either fired, or resigned, because of their inability to integrate into our
working environment. Those who complain the loudest about not being promoted
because of 'politics' are those who, invariably, have not played their part in
engaging in the collaborative process of building a lasting reserve of social
capital.
That's not to say that there are no politics – as soon as you put more than one
person in a room, politics suddenly happen. Politics is the word we give to the
necessary friction and abrasion that comes from people having multiple, perfectly
valid, viewpoints.
Many outlandish claims are made about creator politics. There are claims of
institutionalised nepotism, lasting grudges, and projects that have been killed
because the wrong person was involved with them. While there will be examples
of each and every one of these, they are not widespread – they stick out precisely
because they are not widespread. Follow the gingerbread trail of rumours to their
sources, and you'll find that a small number of bitter ex-creators are responsible
for their perpetration. These creators, without exception, found it difficult to work
as a creator because of their own unwillingness to engage fully in the process.
So, put these complaints in perspective. The politics amongst Epitaph creators are
no more cut-throat than the politics you will encounter amongst any group of
people. If you are mature enough to try and work with other people, you'll find
423
Epitaph Online
http://drakkos.co.uk
they are willing to try and work with you.
The Ten Commandments Of Egoless Programming
Way back in 1971, a guy called Jerry Weinberg wrote a tremendously influential
book – the Psychology of Computer Programming. In this book, he outlined a
mindset he termed 'egoless programming' as a way to deal with the often emotive
issue of ensuring quality in software development projects. His system revolved
around the ten commandments that tend to lead to a more positive, collegial
relationship between software developers. As a 'consciousness-raising' exercise I
would like to outline them here because they serve as a useful set of precepts for
how trust and respect flow in development. The commandments are his, although
the commentary is mine.
Understand and Accept You Will Make Mistake
We all make mistakes – some of us more than others. The consequences of these
mistakes may be minor, or they may be a major inconvenience to the entire userbase of the MUD. I once made a particularly bone-headed error that locked the
MUD up tight for a good hour, something that would have been impossible to do
if I hadn't explicitly switched off the sanity checking built into the driver. You will
make mistakes – learn from them, and move on.
You Are Not Your Code
When people criticise your code, they are not criticising you – at least, they
shouldn't be criticising you. If they are, then it's a problem with them personally.
Constructive criticism is very valuable – it's how you learn from people with a little
more experience. In order to accept constructive criticism, though, you need to
divorce yourself from the code you have written – you need to be able to take a
dispassionate view and say 'Ah, yes – it does indeed have defects I need to
address'.
No Matter How Much "Karate" You Know, Someone Else Will Always Know More.
It doesn't matter how good you are – there's always someone better. There's one
person in the world for whom that isn't true, and that person is perpetually
looking over their shoulder for the day that it is. Moreover, everyone has their
own particular areas of expertise – even if you consider yourself the Top Guru in a
particular area, someone else is going to know more in another. Creating on
Epitaph requires a very odd blend of skills, and some people have these skills to
greater or lesser extents. Even if you know you're good, don't let it go to your
head – the chances are you're not as good as you think you are
Don't Rewrite Code Without Consultation
We don't encourage creators to 'own' code on Epitaph – code is a communal
424
Epitaph Online
http://drakkos.co.uk
resource. However, that doesn't mean you can write code without regard for other
people. This is especially true when you are working with lower level inherits and
critical handlers – there is an etiquette that goes with rewriting code, and it is vital
you adhere to it. If you are going to do some serious remodelling of important
code, then make sure you consult with the people who are likely to be affected.
Treat People Who Know Less Than You with Respect, Deference, And Patience
I have my doubts about the choice of wording with regards to 'deference', but the
general rule is worth keeping in mind. Everyone was a beginner at one point, and
if those to whom we had turned for help had mocked and dismissed our queries,
the chances are none of us would be here at all. It's an act of considerable
courage to ask for help, and it's in everyone's best interests for every creator on
Epitaph to be as good as they can be. Encouraging and constructively engaging
with creators who have queries is the best way to foster an atmosphere in which
self-evaluation and improvement is possible.
Additionally, the very act of asking a question can add value to the creator-base. A
good question will tax the understanding of the teacher as well as the student;
the teacher gains a little extra clarity, and the student gains the understanding
they desire. If questions are asked through the Oracle system, then a good
question is worth its weight in gold to the creators who come after you.
The Only Constant In the World Is Change
On Epitaph, we are not beholden to the whims of our clients. Our clients are the
players of the game, and they play the game which we provide. In other
organisations this is not the case – when a client asks you to make a change, you
make it.
However, even though we are the ones setting the development agenda, we all
still need to learn to deal with changes in fundamental coding tools and systems.
Changes elsewhere in the game will impact on the code you are writing, and you
must be willing and able to adapt your code to deal with emerging situations.
The Only True Authority Stems From Knowledge, Not From Position
Those who have attained higher rank in the creatorbase have usually done so on
the basis of their contributions to the game. While you should accord people the
appropriate level of respect, you shouldn't confuse position with authority. The
newest creator may have more knowledge of a particular area of the game than
the most senior creator, and you shouldn't allow the position of the latter to
override the expertise of the former.
Fight For What You Believe In, But Accept Defeat Gracefully
Everyone has a different view on what's important in the game, and everyone has
425
Epitaph Online
http://drakkos.co.uk
a different view on what will improve the game. It is important that you fight for
what you feel to be right, but you also have to realise that the authority for
making the ultimate decision usually does not reside with you. In such
circumstances, it's important to let go of the debate and accept the conclusion,
even if you personally disagree with it. We've all had to live with the consequences
of decisions that we didn't like.
Don't Be the "Guy In The Room"
The "Guy In The Room" is the one who doesn't engage with the rest of his or her
team. The Guy has a project, and writes the code for that project without
collaboration or input from others. The "Guy In The Room" doesn't know what's
going on and isn't really a part of the team. Not knowing the broader context in
which code is being developed and deployed is a major disadvantage in an
environment like Epitaph, and you'll end up doing yourself more harm than good.
Critique Code Instead Of People – Be Kind To The Coder, Not To The Code
Too many people don't understand what 'constructive' means. Additionally, too
many people deliver criticism without even a basic understanding of human
psychology – there is a reason why such people very rarely manage to convince
others of the worth of their remarks. Critiques should focus on the code, and not
the coder. 'You write code that is full of problems' is an attack on a person, and
that's never going to get someone on side. 'There are some problems that need to
be resolved with this code' focuses the remarks where they belong. Of course, if
Commandment One is not being observed, it's not going to make a lot of
difference.
The number of people who don't understand the most productive way to deliver
criticism is staggering. In the main, it's how you do it rather than what you say. To
begin with, concentrate on everything that is right about the artifact in question –
start with the positive, and then introduce a discussion of the negatives. If
necessary, interleave positive and negative feedback to ensure you're not simply
giving a laundry list of flaws. The important thing about positive feedback is to
front-load it – it's counterproductive to start off with negative feedback since it
just puts people on the defensive, and even if there are positive comments at the
end, you've already lost the chance to win someone around to your way of
thinking.
Inability to deliver criticism correctly is the leading reason why some people just
can't get others to listen to their feedback.
Trust and Common Ground
Perhaps one of the trickiest aspects of this system is that it requires you to be
able to trust and respect your colleagues. Trust is the alchemical property that
makes sure teams keep functioning even when there are breakdowns in
communication, conflicts of interest, or simple personality clashes.
426
Epitaph Online
http://drakkos.co.uk
Invariably there will be people you trust more than others, and with whom you
feel these commandments can usefully apply. Conversely, there will be people you
don't trust, either in terms of their personality or their competence – the thought
of following these commandments with these people would be laughable.
However, once trust is built it can work effectively to bridge the day to day
problems that collaborative development will introduce.
There are several ways in which trust can be built in any organisational
environment. They are based on a genuine willingness of all participants to work
towards a common understanding.
First of all, trust is built on common ground – this is the set of principles and
issues on which you and your interlocutor can agree. This involves a certain
amount of give and take, and a willingness for each to see things from the other's
perspective. Unwillingness to build common ground on the part of one participant
is a sign that they are either not interested in — or not capable of — participating
in a constructive dialogue.
Common ground tends to increase on the basis of familiarity with individuals, and
familiarity with a particular environment. It is particularly strengthened when it
involves shared experiences or a shared background. Common ground requires a
willingness for individuals to compromise – in organisations, it's based on the
willingness of an individual to be integrated into a complex community, rather
than being an outsider unwilling to consider more productive engagement. Most
organisational culture is a manifestation of common ground within a particular
operational context – the 'work songs' of IBM in the fifties were comical, but they
served their purpose in creating (an arguably somewhat dangerous level of)
common ground.
Common ground can be maintained by simply 'keeping people in the loop'. That
doesn't mean that everyone has to be updated about every little detail of your
project, but it helps if you touch base with the right people at the right time. This
helps resolve ambiguities about what people are doing, and prevents small
problems becoming larger conflicts. Conversely, conflict causes people to
withdraw from the process of building common ground, creating a self-reinforcing
cycle of disharmony.
It may sound trivial, but common ground requires constant calibration. It's not
something that is achieved and then you move on, it's something you continually
work towards.
Even when common ground disappears, trust is often enough to ensure the
resilience of a team of people over the short term. Where there is no trust, our
assumptions of motivation are always flavoured negatively – we assume people
are doing things for the worst reasons, rather than for the best. When there is no
trust, there is no will to collaborate, and this strikes at the heart of the way our
environment works. When there is no trust, mistakes are hidden rather than
brought out for everyone to help resolve. When there is no trust, people are less
willing to say 'I don't know', and that is no good for anyone.
427
Epitaph Online
http://drakkos.co.uk
So, if we know how common ground is built, how do we build trust? Sadly, this
can be much harder to do in an online rather than an offline environment. Trust is
built as a consequence of informal social interaction – coffee breaks, having lunch
together, and idle chit chat. We do have channels for discussion, but our
environment poses numerous extra challenges.
For one thing, there is an inherent ambiguity in the medium. If you ask me a
question, and I don't respond, why is that? Is it because I'm not actually at my
keyboard? Am I actively ignoring you? Did it just slip past as I blinked during a
wave of debug spam? It's hard to tell.
Likewise, there is none of the nuance in written text that comes with face to face
communication. The words themselves are only a part of what we project when we
talk to someone – tone of voice, facial expression, and body language are all vital
in decoding the actual intent of the words. We have smilies in text, but they do
not do nearly enough to bridge the gap. Where trust already exists, one can read
the best intentions into communication. Where trust doesn't exist, we can read
the worst.
One of the easiest ways to completely demolish any initiative to build trust is to
talk behind someone's back rather than bring it up directly – people almost always
hear the backroom gossip anyway, and when they do it lowers you in their
perception. My own personal rule for this is 'Never say in private what you are
unwilling to say in public'. While that may get me a reputation for being 'A Bit Of A
Dick', at least people are sure that if I have a problem with them, I will bring it up
directly rather than passive-aggressively.
Additionally, the simple nature of our distributed developer base is problematic –
it's often not possible to get immediate feedback on such communication because
it was written asynchronously – we were sleeping when it was written, and now
the person who wrote it is asleep while we read.
Keeping the channels of communication open are the best way to build trust – just
talk to people. It doesn't have to be about the game, although if you have a
project that's exciting you can go a long way by communicating that excitement.
In our particular environment, individual initiative is hugely important, because it's
what allows you to work when there is no instantly available authority – while you
need to touch base with your domain administration, you also don't need to wait
for them to approve every little detail.
Of course, this is a two-way street – you don't build trust if you're the only person
doing it. Everyone needs to be willing to work towards it.
The Trust Triad
In the end, trust boils down to three key elements. If you trust someone, you have
to:

Have the capacity for trust. Some people are so damaged by their life
428
Epitaph Online


http://drakkos.co.uk
experiences that they simply find it impossible to let themselves trust
others.
Have confidence in their competence. The person with whom you are
building a trust relationship has to have demonstrated their capability
within their role.
Have confidence in their intentions. Where ambiguity rules, it's your
confidence in someone's intentions that will carry you through. If you trust
someone, you have to believe that they are actually doing what is best for
the game rather than what is best for themselves.
As to the capacity for trust, that's an internal measure. The only one who can
build that is you, and deeply seated trust issues are well outside the scope of this
material.
The other two are professional measures, and they are driven partly by you, and
partly by the other person. There's a great old saying, 'Never attribute to malice
what can adequately be explained by incompetence' – it's an aphorism of
dysfunctionality in team work. In a team that is functioning properly, you
shouldn't have to rely on either extreme. You'll simply accept the fact that 'people
make mistakes' (Commandment #1), and work to fix it.
Another word for this kind of institutional trust is 'respect'. Notice nowhere does it
say you have to like someone; you just need to have sufficient respect in them
and their abilities that you can assume the best. You can like people and not
respect them, and you can respect people but not like them. The best state, of
course, is when you both like and respect them. If you can only manage one
though, try for respect.
I have often made the statement that 'respect is earned, not given', which
invariably causes disagreement along the lines of 'you should start off respecting
people'. This disagreement, I feel, is due to a lack of common ground as to our
perception of what respect actually means. The absence of respect is not
disrespect. You should, by all means, be civil — even friendly — to people who
you have no cause to respect. Active respect, though, demands a little more from
both parties. It requires adherence to the 'trust triad' outlined above and this can't
be done with someone you've only just met. They need to have proven
themselves, and shown that their intentions can be trusted. That takes time, and a
willingness to actually build that trust, and it needs both parties to engage in that
process.
Conclusion
For all you hear about 'creator politics' in closed social contextx, the fact is people
do try, in the main, to get along. There are always politics – that's what happens
in life. You put people together, and politics is one of the by-products of whatever
activity was the intention. There are examples of systemic distrust between
individuals, and examples of complete breakdowns in communication. If you look
a little deeper though, you can often find that the reason for such situations is
that one or more of the participants have simply withdrawn from the exercise of
429
Epitaph Online
http://drakkos.co.uk
building trust and maintaining common ground.
If you keep this as an active priority in mind when developing, you'll find it easier
to function in our somewhat overwhelming world. People who get on well with
people have a 'superpower' all of their own – they keep the MUD running when
communications have broken down between others.
430
Epitaph Online
http://drakkos.co.uk
The Dark Art of Refactoring
Introduction
A lot of what we do as creators is tidying up code that has already been written.
Technically this is known as 'refactoring' code. It has something of a reputation as
a 'dark art' amongst programmers, mainly because so few genuinely understand
what the process of refactoring involves. It's actually a very simple principle, albeit
with technical complications that go with it.
In this chapter we're going to talk about refactoring – how it's done, why it's done,
and most importantly of all, how it shouldn't be done.
Refactoring
Put in its simplest, most accessible terms – refactoring is the process of turning
bad code into good code, while not impacting on any of that code's functionality.
Refactoring is ideally an invisible process – if you do it right, no-one should know
you did anything at all.
Refactoring is not about adding extra functionality, although it may be a precursor
to this. It's also not about fixing bugs, although bugs may disappear as a
consequence. Many of the oddest random bugs are a result of badly structured
code, and cleaning up the internal architecture of a problematic object can result
in real, observable improvements even though that was not the actual intention.
Good Code
Part of the problem people have with refactoring as a process is that it is
inherently subjective. It's usually pretty easy to identify bad code, but it's much
more difficult to identify good code. Different coders will vary in their opinions as
to what exactly a good piece of code looks like, and this subjectivity is at the
heart of what makes refactoring somewhat non-intuitive. As you grow more
experienced as a developer, it becomes easier to identify code that you personally
class as good - that judgement is built on the basis of experience. 'Ah, yes – I've
worked with code like that before, and it was easy to make my changes'.
Since refactoring is the process of turning bad code into good code, we need to
have a pretty solid grasp of how the code should be improved. You need to be
sure your change won't introduce new problems – there's no point after all
rewriting bad code so it becomes different bad code. We want the quality of the
code to actually increase from our efforts.
Impact of Change
431
Epitaph Online
http://drakkos.co.uk
Before we get to the discussion refactoring properly, let's talk about a concept
that doesn't get enough discussion on Epitaph – the impact of change. We
touched on this very briefly in Intermediate LPC when we talked about visibility
modifiers for methods and variables. In its simplest term, this relates to the
number of objects that will need to be altered if you change core functionality in
another object.
Epitaph has, in the main, two kind of objects that carry with them a potentially
high impact of change. The first are handlers, such as the armoury and the
taskmapper. The impact of change that goes with each will vary with how widely
they are used. To get a clear picture in your mind, think 'What would happen if I
broke this object right now?'. If it's something like the special events handler, it
may go unnoticed for a short while. If it's the taskmaster, everyone will be
complaining in seconds. This gives you a rough measure of the impact of change.
On the other hand, if you break a single room in an area, then that one room
becomes inaccessible. That's not a huge deal. Break the inherit that every room in
that area uses, then the whole area becomes inaccessible. That's a bigger deal.
Break STD_ROOM and the entire game world becomes inaccessible. It is this that
defines the impact of change.
On the whole, you can get by with four categories of object:
Object Impact
Critical
High
Medium
Low
Examples
Core handlers, the lowest level of inherits (the
ones that every single object inherits).
Other mud-wide handlers, and the rest of the
inherits in /mudlib/inherits/.
Local area handlers, area level inherits
Single rooms, single NPCs, single items
That's one measure of impact of change. The other measure is how many objects
make use of the functionality of another object. There is usually a fairly close
overlap between categories.
There are rules that go along with refactoring, and one of these rules is to be very
careful when dealing with objects with high impact of change. Object orientation
as a programming framework also has a number of tricks for making objects as
amenable to change as possible. We'll talk about them in this chapter also.
The Rules
Here are the rules that go with refactoring. Note, these are rules, not guidelines.
A good software development process will obey these rules and have punishments
for transgressing them.


Methods and variables may be made more visible. They may not be made
less visible.
The functionality of public methods cannot change. If a public method does
432
Epitaph Online



http://drakkos.co.uk
X, it should continue to do X (and nothing more or less) after it has been
refactored.
The return type of a method cannot change unless that change is for it to
be less restrictive (from a string to a mixed, for example)
The name of a method or public/protected variable cannot change.
The parameter list of a method must remain the same, or there must be a
translation scheme in place for a change.
These are restrictive conditions, and necessarily so. In a massive code-base like
Epitaph, you have to assume that if something is accessible to other objects, then
some other objects have made use of it. People will have looked at the object and
said 'Wow, the do_groovy_stuff method does exactly what I need' and then made
use of that method in an entirely unrelated piece of code.
By default, all methods and variables in an LPC object are publicly accessible. That
means every object in the game has access to the methods, and also to the
variables (although variables are at least a little protected by the comparatively
primitive object model of LPC). Protected methods and variables are available only
to the object in which they are defined, as well as all subclasses (all classes that,
somewhere along the way, inherit the base class). Private methods and variables
are accessible only to the object in which they are defined – no external objects,
and no subclasses. These are known as visibility modifiers.These also map onto
categories for impact of change:
Visibility Modifier
public
protected
private
Impact of Change
High
Medium
Low
Thus, an example of a forbidden refactoring would be to turn a public method
into a private method. This breaks our first rule – it reduces the visibility of the
method. Any object making use of that method will break as a result of our
refactoring.
We could take a private method and make it public – this increases the visibility,
but is almost never a good idea. There's usually a reason why a method has been
given restricted access rights. This isn't the place for a discussion of proper object
oriented design though.
You can't change the return type of a publicly-accessible method without violating
the rules. This, for example, would be a forbidden refactoring:
int add_nums (int num1, int num2) {
return num1 + num2;
}
Into:
double add_nums (int num1, int num2) {
return to_float (num1 + num2);
}
433
Epitaph Online
http://drakkos.co.uk
Likewise, you can't change the type, order, number or meaning of parameters in a
method unless you provide some way for that change to be transparent to all the
objects making use of it. This would be invalid:
int add_nums (int num1, int num2) {
return num1 + num2;
}
Into
int add_nums (double num1, double num2) {
return to_int (num1 + num2);
}
The rules of refactoring are in place to make it a more pleasant environment for
everyone to work within. If everyone is obeying them, the chances are greatly
reduced of you logging in one morning to find none of your code working the way
it did the night before.
Breaking the Rules
Sometimes it's okay to break the rules. Mostly this comes along with the access
you have to fix the problems that come along. Imagine you are a young turk,
looking to make your name with some great improvements to
/mudlib/mixins/basic/desc.c. Someone gives you access to that code, and you
decide 'Ha, set_short is too limited. I'm going to make it take half a dozen
parameters, all of which will be mandatory'.
That's not allowed, because it would break... well, almost everything. In this
hypothetical situation, the only access you have is to that file directly, not every
file that uses it. The rule is, 'if you break it, you fix it', and if you can't reasonably
fix it, you don't get to break it.
On the other hand, if you have write access to /d/game/united_kingdom/ and you
want to break something in /d/game/united_kingdom/handlers then, with care,
you can break the rules because you're in a position to fix everything that might
be using the code. Sometimes refactoring is a task involving many objects, not
just one object or one method.
Additionally, you can use common sense to tell whether or not anything is likely
to be using the method in question. If you have a method 'check_things' in an
obscure room in an obscure area, then go ahead and change that method if you
need to – you'll only need to fix that one room after all. It's extremely unlikely that
anything else will break as a consequence, even if it is a public method.
Let the impact of change categories guide you – if it's critical or high, don't do it.
You'll break more things that you can realistically fix. If it's medium or low, then
proceed with caution. Just be prepared to fix anything you may break.
434
Epitaph Online
http://drakkos.co.uk
Sometimes, although the situation is rare, someone needs to change something
critical even though it is almost guaranteed to break other objects. When
add_action was removed from the driver, or when type-safe checking was
implemented – this was changing driver code, so it impacted every object in the
game. If something has to go, then it has to go.
There's a process that you go through when this is the case, and it's something
like this:






Announce the deprecation of a piece of functionality.
Provide a list of objects that will need to be fixed.
Post a deadline by which the code must be changed, along with guidance as
to how changes should be implemented. Be on hand to help with this.
When the deadline is reached, give a few days' notice before you make the
changeover. Give a date at which time the change will be made. Don't say
'in the next few days', be specific.
Make the change, even though things will probably break.
Fix the things that broke.
This is a time consuming process, and one that we don't go through very often
because it's a big hassle for everyone involved. It's only permitted in extreme
situations, and if you have to ask yourself whether you have the authority to make
such a change, the answer is you don't.
Refactoring
So, what kind of things do we do in refactoring? There are several, but the most
common things are:




Removing dead code
Making inefficient code more efficient
Making code more readable
Making code more maintainable
Ideally, refactoring is a proactive process – you do it as an ongoing part of
development. In reality, we tend to refactor only when there is a problem with the
code with which we are currently working. Refactoring is normally a first step
towards adding new functionality. When we refactor, we are looking to make the
code look the way it would have done if it were written properly the first time.
Refactoring can be as simple as changing the name of a variable to something
more meaningful; however, if this is a publicly-accessible variable, even that trivial
change can break code.
Sometimes, it's a case of improving the aesthetics of an object. Said object may
work perfectly, but offend the sensibilities when the code is viewed. The
aesthetics of code are important – they are usually a hint as to where refactoring
can profitably be applied. Complex, unwieldy, and ungainly coding structures are
usually there to handle complex logic operations that could potentially be either
extracted or remodelled. However, you shouldn't automatically think 'complex
435
Epitaph Online
http://drakkos.co.uk
code is bad code', especially if the cold is old. Some code isn't ugly, it's battlescarred – it's been thumped to bits by countless rounds of testing, and then
patched up and fixed and put back into the field. Recognising the difference
between ugly code and battle-scarred code gets easier with practise.
Some Common Refactoring Tasks
There are several common tasks that are done to refactor objects. Some of these
are structural, relating to the way in which objects are connected to other objects:




Generalising object functionality.
Specialising object functionality.
Improving encapsulation.
Lowering impact of change
Some of these are related to the code inside objects:







Simplifying internal structures.
Improving variable names.
Simplifying logical comparisons
Substituting one algorithm for another
Consolidating conditionals
Extracting functionality into separate methods.
Reducing inconsistency in naming and parameter ordering
Martin Fowler (http://www.refactoring.com/catalog/index.html) has a list of
common refactoring tasks. Not all apply to Epitaph, but you can get a taste of
what refactoring is all about.
Conclusion
This chapter isn't a technical resource about refactoring, but the outline of a
philosophy for refactoring that can reduce tension amongst your colleagues.
Refactoring is an important and on-going process, one that you will undoubtedly
get involved with at one point or another. Its role in this material is to outline a
set of criteria by which you should refactor – a 'manifesto of courtesy' for how to
make sure you don't inconvenience everyone with your changes.
436
Epitaph Online
http://drakkos.co.uk
Coding Etiquitte
Introduction
Formal codes of etiquette exist to smooth relationships between people by
establishing boundaries of acceptable behaviour. So it is in coding within a multideveloper environment – there is an etiquette that works to reduce friction. As far
as I am aware, no-one has actually formalised this before, so this is my own
clumsy attempt to do for coding what Emily Post did for the 1920s.
It's very easy to step on toes as a developer, and having a little consideration for
your colleagues is the best way to show the necessary respect for their time and
effort. I don't think there's a lot of value in having an exhaustive encyclopedia of
such rules, so I have concentrated only on those of real importance to the
process.
Before You Write Any Code
The first steps you take to ensure you are being polite are taken before you write
any code at all
Check for Duplication of Effort
To begin with, look to see if there has been any duplication of effort. One easy
way to cause Conflict is to say 'Hey, I'm coding this cool thing here', when
someone else is already coding that cool thing elsewhere. Additionally, check to
see if there's any abandoned work that has been done in the past on similar
developments. Sometimes projects stop simply because people don't have enough
time, rather than because they weren't working out. A rescue and adaptation of
old code can be as valuable and efficient as writing the code from scratch.
Make Sure All Involved Parties Are Consulted
An easy way to rub people up the wrong way (and to run the risk of your project
being mothballed) is to not consult the people who need to be consulted. You
can't just say to yourself 'I'm going to write a faction that is full of pirates!',
because the placement of factions is a strategic decision for the game, especially
if they involve something that might potentially impact on a future game feature.
Making sure that the relevant parties in those domains are consulted before you
start coding is the easiest way to avoid potential future conflict – it's hardly ever
the case that you're told 'No, you can't do anything like that', but there may be
conditions, or modifications that are necessary, or perhaps a recasting to fit in
line with future domain objectives. Just a post on a board is appropriate for the
most part - keep people in the loop.
437
Epitaph Online
http://drakkos.co.uk
Ensure a Migration Strategy
If you're remodelling code that's already in the game, or code that's likely to
impact on other developers (such as a change to inherits or handlers), then make
sure you plan in advance to make the change with the smallest possible
'interruption of service'. Make sure everyone knows, ahead of time, what's going
to happen and what that means for their own code. Do it first, because it'll already
be too late if someone points out a problem with your migration strategy after
you have made the changes.
When You Are Writing Code
The biggest area for potential conflict is in the code you actually write – all code
on Epitaph is very tightly interconnected, and you can easily cause problems for
other developers if you write code without care.
Be Wary Of the Impact of Change
As per our discussions on the impact of change, you need to bear this in mind
when you are changing code. If it's going to change the way the code functions,
don't do it unless you've gotten people on board and given warning. The higher
the impact of code, the greater your consultation with others should be. Seriously,
don't be a dick – don't just break other people's code because it's convenient for
you to do so.
Write Your Code Cleanly
There exists, at least in my mind, a bell curve that describes the simplicity of the
code written by developers. I call it the Obfuscation Curve:
Newbie developers write simple code because it's all they know how to do.
However, as the years go by, they accumulate knowledge of new tools,
techniques, and syntax. They then fall into the incredibly common trap of thinking
438
Epitaph Online
http://drakkos.co.uk
that a good developer is defined by the number of tricks they know. These tricks
then tend to make their way into every piece of development, just to show how
'clever' the coder is. Additionally, developers at the midpoint of experience tend
to associate complexity with quality – if the code is clever and does what it is
supposed to do in a highly efficient (albeit inscrutable) way, then it must, by
definition, be good code.
This couldn't be further from the truth.
There is some merit to the idea that good code can be an intellectual exercise –
doing things in new and unusual ways is personally satisfying after all. However,
in a multi-developer environment you are actively retarding development by
making every developer who follows you puzzle out the logic of your code. Your
lapse into egotism is a burden to all. I don't care what you do with your solo
development projects, but when you're working with others, don't do it.
As developers gain further experience, especially after working with other people,
they start to return to the idea of clean, simple code. It's much more maintainable,
much more readable, and the minor efficiency losses are more than compensated
by the ease with which fellow developers can add features or address problems.
Really beautiful code, and there are some lovely examples of this, is elegant.
Elegance demands simplicity of expression. The developers with the best Code Fu
write elegant code, not complicated code.
As a counterpoint to this, I don't mean to say that you need to write code that
even the newest creator can understand. You can make available to yourself the
full vocabulary of the programming language – when I talk of readability, I mean
readability to a fellow developer of reasonable literacy with the language.
For example, the following code is needlessly obfuscated:
if((check = ::move(dest, messin, messout)) != MOVE_OK)
return check;
You can follow what it does, but it doesn't need to do it so awkwardly. There's
nothing lost by being explicit in the code:
check = ::move (dest, messin, messout);
if (check != MOVE_OK) {
return check;
}
You lose nothing, and you gain readability in exchange.
To show that I'm not simply having a go at other people here, I'll include an
example from my own code:
tot = map (filter (property_list, (: member_array ($1->street_name,
$(monopoly_sets)[$(m)]) != -1 :)), (: $1->houses :));
439
Epitaph Online
http://drakkos.co.uk
Sure, it's nice Code-Fu, but it was written eight years ago when I too confused
complexity for cleverness. But I bet it would take you quite some time to work out
exactly what this line of code is supposed to be doing. I'll give you a hint, though,
it's doing something quite simple.
Document Extensively
Good code is its own documentation. I am in no way a proponent of 'commenting
metrics', whereby X lines of code must have Y lines of comments. As long as you
pick meaningful variable names and don't over-complicate it, you'll find that your
code is readable enough. Everyone has a different opinion about readability,
though. I've occasionally found that someone's gone to the trouble of
commenting code that I was too lazy to comment myself. They have my thanks for
this!
However, whenever you're doing something a little bit exotic, you should outline
what your intentions were within the code. Don't describe what the code does,
describe why it does it. The following would be a bad comment:
//
//
//
//
Does a map on the filtered property_list array. The filter filters
the array for all those streets that are part of the value $m in
the monopoly_sets mapping.
It returns the houses member of the class in array format.
tot = map (filter (property_list, (: member_array ($1->street_name,
$(monopoly_sets)[$(m)]) != -1 :)), (: $1->houses :));
That explains what the code does, not what the intention of the code is. You can't
tell from that comment whether the end result is what the code would suggest. A
better comment would be this:
// This piece of horrible code gives an array of all the houses a player
// has in the properties that belongs to a set.
That explains what the code was supposed to do, so that a developer can look at
the end result and decide whether or not that's what happens. Comments should
explain intention, not simply dissect the code.
Attribute Contributions
Coding is a collaborative effort, and much successful coding is simply welltargeted plagiarism. That's absolutely fine – if someone has already solved the
problem you are having with a bit of their own code, then use that code as a
template for your own. However, when you do this, it's very nice if you can
provide an attribution, such as:
// This code borrowed from Drakkos' Killer Weasels
Attributing the work of those who have gone before you is a respectful activity – it
doesn't take away from you as a coder, it enhances your reputation in the eyes of
440
Epitaph Online
http://drakkos.co.uk
other people. Moreover, it makes it easier for people to maintain the MUD - if I
know that you based a piece of code on a function in /d/game/stupid_thing, then
I know that if I fix your code I may also have to fix it there.
When You Have Written Code
Once you have written code, there are a few more things that fall under the
general criteria of politeness.
Abdicate Ownership
Once the code is written, you should mentally hand it over to the MUD (we
actually have a code ownership policy that requires that). It's no longer your code
– it's production code, belonging to everyone. It's a mental activity, so you don't
need to actually do anything for this... but if you get wound up or upset by
someone else changing something in your code, then you're doing it wrong.
Complaining that someone else 'changed your code' is rather rude in an
environment where everyone else is being a team player with the code they
provide.
Be Willing To Maintain
Even though you abdicate ownership of the code, you are still the person best
qualified to maintain it. As far as possible, you should keep an eye on reported
problems with your developments and be prepared to fix them, especially if they
flummox other creators. Although you aren't the owner of the code, you are the
expert on it.
Make Sure All Parties Have Adequate Information
There's little worse from a 'creator satisfaction' perspective than those with a need
to know not being told what they, well, need to know. Need to know parties will
vary from project to project, but if it's an area going into the game then the
liaison domain need to know as much as you can tell them about features that
may potentially go wrong. If it's a cross-domain collaboration, then all the
collaborating domain administration teams need to know the details.
A post on the game board is the usual mechanism for announcing a new feature
for the game, but a mail to the relevant parties ahead of time also shows the
appropriate amount of respect for your collaboration partners. Something like,
'Hey, we're going to make this live next week, so let us know if you have any lastminute comments' is a great way to make sure everyone gets a chance to have a
last look over the development before everyone is committed.
441
Epitaph Online
http://drakkos.co.uk
Conclusion
There aren't all that many rules you need to worry about – and most of them are
fairly self-explanatory. They all stem from a single basic concept though – don't
make life harder for people than you actually have to. Working with other people
means being able to compromise and communicate clearly. It's much easier when
everyone has a firm idea as to what is acceptable and what is not when making
development decisions.
Some of us are better (or worse) than others at being polite and respectful
developers. It is not unknown for a developer to wake up one morning to find that
everything they have written has been broken because someone else 'fixed' a low
level inherit. Such occasions are rare, but there are precedents. Simply bear this in
mind – how would you feel if you logged on one morning and everything you had
written had to be changed, without any warning or consultation? You'd be
legitimately pissed off. If it would bother you, don't do it to other people.
442
Epitaph Online
http://drakkos.co.uk
Source Control
Introduction
One of the most useful systems we have in place on the MUD is our source control
system. The MUD uses a system called RCS – the Revision Control System – to
ensure that multi-developer collaboration proceeds as smoothly as it can. It is a
system for restricting the access to make code changes so that only one developer
modifies a file at a time. This neatly gets around the problem of one developer
overwriting the changes of another developer – something that, with the best of
all intentions, is quite common when no formal system exists to prevent it.
Source Control in the Abstract
Imagine you are a Epitaph developer. That should be quite easy, because
presumably you are! You are working with a file, /d/game/awesome.c. You
download a copy of this to your local machine, and make some changes. While
you are doing this, unaware of the fact you are working with the file, I come along
and download it to my local machine and start making changes. You finish up,
and upload the file to the MUD. I finish up, and then upload my file to the MUD.
Despite the fact we both made changes, my version overwrites yours.
There are ways to minimise the chances of this – for example, like so:
(cre) Drakkos: Hey, I'm about to do something with /d/forn/awesome.c
(cre) AnotherCreator: Can you hold off a bit, I'm currently working with it.
(cre) Drakkos: Sure!
This is imperfect though – it doesn't catch anyone who is offline and working
through FTP, and it doesn't catch anyone who isn't reading the channels.
Communication can go some way towards making sure problems like this don't
occur, but it doesn't solve the root issue.
Source control steps in and provides the solution to this problem. All files on the
MUD can be placed into source control (this has to be done manually, because
sometimes it's inconvenient), and once they have been registered with the system
it becomes impossible for anyone to make changes unless they first 'check out'
the file.
Only one person at a time can check out a file, which means that if you are
working with awesome.c, I will get an error message if I try to make any changes.
When you're done, you 'check in' the file and provide a little description of what
you did. At that point, your changes are said to have been 'committed'.
Part of the beauty of a system like this is it keeps track of the differences between
the new version and all previous versions, and with a single command a developer
can revert the file back to a previous version. If your changes to awesome.c
443
Epitaph Online
http://drakkos.co.uk
introduced some horrible, game destroying error... well, we just 'revert' the file
back to its previous version and no harm, and no foul.
It really is a wonderful system. In our example of you and I working with the same
file, no-one has done anything wrong. It's not a sign of bad communication or a
dysfunctional environment – it's just One Of Those Things. While it's almost never
a good idea to apply a technological solution to a social problem, what we're
describing here is a technological solution to a technological problem.
Like any system though, it's only as good as the people using it. We'll talk about
some of the social problems with RCS later in this chapter.
The Epitaph RCS System
If you are the sole developer working on a project that is not in the game, you
may find it easier to keep everything off of RCS while you work. It can be
inconvenient to continually have to check files in and out of the system, and
somewhat against the spirit of the thing to check them out and never check them
back in until the development is completed. When the code goes live, or into
playtesting, you should – without exception – add the files to the RCS system.
Luckily, it's very simple to do – you use the rcscreate command:
rcscreate /w/your_name_here/deadville/rooms/*.c
You'll be prompted to enter some text – as a matter of convention, this text is
usually 'Initial Revision'. Once you've entered the text, that's it – your files are now
on the system and you won't be able to make any changes until you first check
them out. That's done using the rcsout command:
> rcsout /d/support/deadville/chapter_02/rooms/street_01.c
/d/learning/deadville/chapter_02/rooms/RCS/street_01.c,v -->
d/learning/deadville/chapter_02/rooms/street_01.c
revision 1.1 (locked)
Now you have access to change the file, and nobody else does. When you've made
your changes, you use the rcsin command:
rcsin /d/support/deadville/chapter_02/rooms/street_01.c
When you do this, you'll be prompted to enter some text. Please provide
something useful and meaningful for this, because it's what people will see when
they look at the log of changes that have been made. You shouldn't detail the
code you changed – that's available already (more on this later). Instead, your
comment should focus on intention and what the change was supposed to
achieve. If you haven't actually made any changes to the file, it will automatically
revert to the last version:
d/learning/deadville/chapter_02/rooms/RCS/street_01.c,v
d/learning/deadville/chapter_02/rooms/street_01.c
file is unchanged; reverting to previous revision 1.1
<--
444
Epitaph Online
http://drakkos.co.uk
If, after having checked out a file, you decide that you don't actually want to make
any changes, or if you've made changes and they're not what you want, you can
release your lock on the file using rcsrelease. This will release your claim to the
file without committing any of your changes. It'll revert automatically to the most
current version of the file:
> rcsrelease /d/support/deadville/chapter_02/rooms/street_01.c
d/learning/deadville/chapter_02/rooms/RCS/street_01.c,v -->
d/learning/deadville/chapter_02/rooms/street_01.c
revision 1.1 (unlocked)
If you want to see what files you have locked out, the mylocks command is your
friend:
> mylocks
You have the following files locked:
/cmds/combat_position.c
/cmds/creator/rehash.c
/cmds/creator/req_uest.c
/cmds/fighting_base.c
/cmds/gather_base.c
/cmds/guild-race/adventuring/assess.c
/cmds/guild-race/adventuring/deactivate.c
/cmds/guild-race/adventuring/inspect.c
/cmds/guild-race/adventuring/judge.c
/cmds/guild-race/adventuring/pick.c
/cmds/guild-race/adventuring/probe.c
/cmds/guild-race/adventuring/repair.c
/cmds/guild-race/combat/cleave.c
/cmds/guild-race/combat/flurry.c
/cmds/guild-race/combat/reload.c
/cmds/guild-race/combat/slam.c
/cmds/guild-race/crafts/assemble.c
/cmds/guild-race/crafts/cut.c
/cmds/guild-race/crafts/facet.c
/cmds/guild-race/crafts/fell.c
/cmds/guild-race/crafts/harvest.c
/cmds/guild-race/crafts/mill.c
/cmds/guild-race/crafts/mine.c
/cmds/guild-race/crafts/prospect.c
/cmds/guild-race/crafts/smelt.c
/cmds/guild-race/crafts/survey.c
/cmds/guild-race/medical/bandage.c
/cmds/guild-race/medical/culture.c
/cmds/guild-race/medical/diagnose.c
Whoops, I should probably check some of those back in! You can also check to
see which files another creator has locked out with mylocks:
> mylocks ploosk
Ploosk has no files locked.
Wow, way to make me look bad.
If you want to see who has a lock on a particular file, that's what the rcslocks
command does:
445
Epitaph Online
http://drakkos.co.uk
rcslocks /www/wrapper.c
File /www/wrapper.c locked by drakkos.
Finally, if you want to see the changes that have been made to a file, the rcslog
command gives you all that information:
> rcslog /secure/master.c
RCS file: secure/RCS/master.c,v
Working file: secure/master.c
head: 1.5
branch:
locks: strict
access list:
symbolic names:
keyword substitution: kv
total revisions: 5;
selected revisions: 5
description:
Initial revision
---------------------------revision 1.5
date: 2011/01/30 16:48:00; author: drakkos; state: Exp; lines: +2 -2
Permission fixes, and some adjustments to apply_unguarded
---------------------------revision 1.4
date: 2010/12/22 20:17:28; author: drakkos; state: Exp; lines: +4 -4
Forcibly released due to inactivity
---------------------------revision 1.3
date: 2010/03/08 15:20:02; author: drakkos; state: Exp; lines: +3 -3
Updated to the new paths
---------------------------revision 1.2
date: 2010/02/13 11:58:14; author: drakkos; state: Exp; lines: +2 -2
Updates
---------------------------revision 1.1
date: 2010/01/15 02:51:04; author: drakkos; state: Exp;
Initial revision
=============================================================================
We'll spend a little bit of time talking about these entries, because there's a lot of
information in there. Let's take the last of these as an example:
revision 1.5
date: 2011/01/30 16:48:00; author: drakkos; state: Exp;
Permission fixes, and some adjustments to apply_unguarded
----------------------------
lines: +2 -2
The first piece of information we are given is the revision number of the file. Each
time a change is committed, the decimal part of the version increase by one. This
follows a general convention of software updates... it's possible for a revision to
increase the whole number part, but nobody ever does it.
The date is when this change was committed, not when the change was made. As
such, there can be wide disagreement with the 'official record'... sometimes files
remain locked out for weeks or months, and so the date of a revision bears no
446
Epitaph Online
http://drakkos.co.uk
relationship to when the changes were made.
The author is the person who committed the change. The state tag is not
something we use much on Epitaph, or indeed use at all. It relates to the state of
the release – EXP stands for 'experimental'. The state can be anything, although
the convention for other states is STAB (for stable) and REL for release. You'll be
unlikely to encounter anything other than EXP though as you work your way
through the Epitaph codebase.
Lines indicates the net number of lines that were added (defined as any line that
was changed) and lines that were removed (defined as any life that is no longer in
the code).
The most important bit of all of this though is the text that accompanies the entry
– this is the text that the creator entered as part of the rcsin. If good practise is
being followed, this will be a meaningful description of the change that was made.
The final command you're likely to make use of is rcsdiff – this gives you the
exact difference between two version of a file, showing exactly what was added
and what was removed. If I wanted to know what changes were made between
version 1.2 and version 1.1 of a file, it's this command I use:
rcsdiff -r1.2 -r1.1 /secure/master.c
This will give the following output:
< * $Id: master.c,v 1.2 2010/02/13 11:58:14 drakkos Exp $
--> * $Id: master.c,v 1.1 2010/01/15 02:51:04 drakkos Exp $
220c220
<
return 0;
-->
return "/d/liaison/master"->query_deputy(person);
It can be slightly difficult to read this output. Lines marked with a < are lines that
have been added, and lines marked with a > are lines that have been removed. In
essence, this line:
return "/d/liaison/master"->query_deputy(person);
Was replaced with the following:
return 0;
There are more commands available as part of the RCS system, but these are the
commands you'll be working with most often. The help-file for RCS will outline
some more interesting and useful options.
447
Epitaph Online
http://drakkos.co.uk
Problems
There are, as usual, social problems that come along with any system. While our
use of RCS is on the whole very good, there are lapses – usually centred around
specific individuals. Alas, I count myself amongst these – senility has grabbed
hold of me in my old age, and I thus often forget I have files locked out. And then,
when I check them back in, I forget what it is I have done.
Usually this is a result of carelessness rather than malice – files remain locked out
for as long as it takes for someone to realise (for example, another person who
needs access to the file). However, there is a more insidious problem of people
pre-emptively checking out code so that other people can't change 'their code'.
This, as we have discussed, is not a mindset we like to encourage.
The rcslog of a file is a historical record – it shows what changes were made,
along with a short summary. However, this record can be constantly interrupted
with 'noise' such as files being forced (this is when someone forcibly checks in a
file for you – this is something available only to senior creators and above), or less
than helpful rcs entry messages. For example, from /secure/simul_efun.c:
---------------------------revision 1.6
locked by: drakkos;
date: 2010/02/13 11:57:27; author: drakkos; state: Exp; lines: +4 -5
Updates
---------------------------revision 1.3
date: 2010/01/17 23:14:52; author: drakkos; state: Exp; lines: +2 -1
Fixes
---------------------------revision 1.1
date: 2010/01/15 02:50:02; author: drakkos; state: Exp;
Initial revision
=============================================================================
None of these are useful messages - they give no useful information about what
has actually been changed, and people will need to go through the rcsdiff output
to tell. This is a problem with the people using the system, not the system itself.
Source control is not a substitute for a good developer environment – it stops
accidental collisions of code, but it won't help with genuine social problems
between developers.
Note too that once a file has been put on RCS, it can't be removed except by a
Trustee. That means no shuffling files around, or deleting them permanently. You
need to be sure that you can commit to what's there unless you want to risk the
wrath of waking a Highlord from their blissful slumbers.
448
Epitaph Online
http://drakkos.co.uk
Conclusion
Source control is one of the most important systems we have for supporting our
development work. It means we don't need to keep backups, or tediously roll back
changes by hand. It means we can track changes made to objects, and identify
people who were responsible for making changes. All of this in addition to its core
function of making sure that we don't end up killing each other over code
collisions. Learn to love it!
449
Epitaph Online
http://drakkos.co.uk
Documentation
Introduction
Writing documentation is one of the least enjoyable tasks that comes along with
developing code. As such, it tends to be something that's left until the last
minute, or done in infrequent, unreliable pushes of effort. It's a shame it is so
tedious to produce because good documentation is worth its weight in gold for
those who come after you.
When it comes to documentation, I don't necessarily mean commenting. I am not
a proponent of the view that comments should form X% of your source code
(although many people are) because I believe that good code is its own
documentation. In addition to comments that describe what code is supposed to
do, Epitaph has a commenting format that allows for automatic extraction and
indexing of object functions, their return values, and their parameter lists.
In this chapter we will also talk about the format used by the MUD's help-files, and
how you can aid in our documentation effort by migrating user help into bespoke
object help-files.
Commenting
The usual argument is, 'It is good practise to comment your code'. In my
experience, when this argument is followed to its logical conclusion it actually
detracts from readability. Imagine the following code (and this is not an
exaggeration, I have seen files like this by the dozen):
// This declares an integer variable called num.
int num;
// This declares a string variable called name.
string name;
// Create a for loop with a counter variable called i.
// It will loop while i is less the num.
for (int i = 0; i < num; i++) {
// Send the text that is in the variable name to
// this_player(), using the tell_object() method.
tell_object (this_player(), name + "\n");
}
The problem with these comments is that they are aimed at the wrong audience.
You shouldn't write comments so that your grandmother, or your mother, or your
best friend can understand what is going on. You should write comments so that
a fellow literate programmer can know what is going on. These comments do not
say anything that the code itself doesn't say. The code itself gets swamped in the
comments, making it a little less readable. Nothing is gained from this, even
though everything has been commented.
Good code is its own documentation. The following is bad code:
450
Epitaph Online
http://drakkos.co.uk
m=i+((r*i)-d);
While you can work out what this is doing, there is no hint as to why one variable
is being modified by another in a particular way. On the other hand, simply
choosing meaningful variable name turns that into self documenting code:
my_money = income + ((reserves * interest_rate) - debits);
In this code it is obvious what is happening – there's no need to comment this.
You'd certainly need to comment the former.
Sometimes though, even with meaningful variable names, you're going to end up
doing something a little bit esoteric. Whenever you feel that it is unlikely that a
fellow literate programmer will be able to tell, at a glance, what you were trying to
do – that's the time to add a comment. This gives a happy balance between
enhancing readability and not restricting you from coding productively.
However, even assuming complete comprehension of what each individual line of
code is doing, it is hard to tell, 'at a glance' what the big picture is. That's where
the system documentation comes in – we provide documentation on each of our
functions so people can tell what they put in, what comes out, and what the value
that comes out will mean.
Commenting Good Practice
One of the biggest problems with large bodies of commenting is the difficulty in
keeping it up to date. What tends to happen is that the code gets changed and the
accompanying comment doesn't get updated. Before too long, the comments bear
little relation to the functionality and become actively unhelpful. That's why it's
important to document the intention, not the actual steps taken.
You should also try to document 'why' along with 'what' – why did you decide to
do something one way over another? Any time you had to spend a bit of time
puzzling over alternatives, you can save those who follow you the effort by saying
'I decided to do it this way because it's more efficient/maintainable/readable than
the other way'.
If you are making any assumptions at all in your code, then for the love of god
document those assumptions. If the entirety of your function assumes that a
particular parameter is within a certain range, make sure that information is
documented somewhere other than in your head. Of course, if you are going to
rely on such things you should have a guard condition in the code ensuring that
the function won't be executed if the parameters are invalid. Still, document the
assumptions you make.
Avoid being humorous in comments if it comes at the expense of clarity. Don't
use code words, or in-jokes, or obscure references, no matter how widely
understood you believe the reference to be:
451
Epitaph Online
http://drakkos.co.uk
// This fubars the string.
string do_fubar(string str) {
}
Finally, don't comment out obsolete functionality – delete it entirely. The revision
control system means that the functionality is available should it be required (oh –
make sure the file is on RCS first!), and removing it entirely from the code greatly
increases clarity.
Autodoc
Epitaph has a commenting convention based on the Javadoc standard. It's called
Autodoc, and it integrates documentation for functions into the standard help
system. Imagine you wanted to know what net_dead was for in /global/player –
you can find out by typing 'help net_death', and you'll get a little help-file
discussing it:
net_dead
net_dead
Epitaph creator help
Name
net_dead -
Called when the player goes net dead.
Syntax
void net_dead()
Defined in
/global/player.c
Description
Called when the player goes net dead. This is called by the driver
when a player goes net dead. Turns them into a statue and stuff.
That help-file is generated automatically from the comments that have been put
before the function in the file. As long as the comments adhere to a particular
syntax, they can be parsed and made available to everyone. You are unlikely to
ever need to do this for rooms, NPC and specific items – but if you're doing
anything more substantial, it's very useful if you can provide autodoc
commenting.
An autodoc comment starts with a special code: /**
Every line that follows begins with a star in line with the first of the asterisks, and
it ends with the normal closing of a block comment: */.
/**
*
*
*/
452
Epitaph Online
http://drakkos.co.uk
The first line of text is what is used for the summary that follows the name of the
function. The rest of the text is used for the description of the function. You can
mark this up with normal HTML, so you can add in paragraphs and line-breaks as
necessary.
The syntax of the function is extracted automatically by the autodoc handler, as is
where it is defined. The rest of the information we need to provide, and we do this
using autodoc tags. These begin with a @ symbol, and are interpreted by the
autodoc handler according to the text that belongs to the tag.
For example, let's take a simple function from /d/support/ master.c and put it
through the autodoc format. The function is this:
int set_project(string name, string pro) {
if (geteuid(this_player(1)) != query_lord())
return 0;
return ::set_project(name, pro);
}
Its task is simple – it checks to see if the person making use of the function is the
lord of the domain. If they aren't, it returns 0 and does nothing. If they are, it
passes responsibility onto the object that this object inherits.
First, let's describe that in an autodoc:
/**
* This function sets the project of a domain member. It first checks to
* see if the person making the call to the function is the lord of the
* domain. If they are not, it will return 0 indicating failure. The
* method will make a call to the set_project of /std/dom/base_master.c
* if this initial check is passed.
*
*/
Next, we add in tags to provide an explanation of what the parameters and return
value mean. We have to be careful with formatting here – there should be one
space between the asterisk and the tag, or it won't be picked up by the handler.
@param is used to give a meaningful description to a parameter, and @return is
used to describe how the return value should be interpreted.
*
* @param name The name of the person for which we want to change the project.
* @param pro The project the person is to have in the domain.
* @return 1 if the project is successfully changed, 0 if it is not.
*
There are some other valuable tags we can provide:
453
Epitaph Online
Tag
@see
@example
@ignore
http://drakkos.co.uk
Description
Adds a reference for other objects of interest. You can use this
to direct attention towards related objects.
You can use this to provide a code example of the object in
use.
Makes it so the autodoc handler ignores the function for the
purpose of automatic generation. This is useful if it's a small,
private function that no-one need worry their pretty little
heads about
We should definitely add in one of each of the first two;
* @see /mudilb/inherits/dom/base_master.c
* @example
* ret = set_project ("drakkos", "Being Awesome");
This would give us our full autodoc comment:
/**
* This function sets the project of a domain member. It first checks to
* see if the person making the call to the function is the lord of the
* domain. If they are not, it will return 0 indicating failure. The
* method will make a call to the set_project of /std/dom/base_master.c
* if this initial check is passed.
*
* @param name The name of the person for which we want to change the
*
project.
* @param pro The project the person is to have in the domain.
* @return 1 if the project is successfully changed, 0 if it is not.
* @see /std/dom/base_master.c
* @example
* ret = set_project ("drakkos", "Being Awesome");
*/
This will generate the following help-file for help set_project:
set_project
Epitaph creator help
set_project
Name
set_project -
This function sets the project of a domain member.
Syntax
int set_project(string name, string pro)
Parameters
name - The name of the person for which we want to change
the project.
pro - The project the person is to have in the domain.
Returns
1 if the project is successfully changed, 0 if it is not.
Defined in
/d/support/master.c
Description
454
Epitaph Online
The
http://drakkos.co.uk
This function sets the project of a domain member. It first checks
to see if the person making the call to the function is the lord of
the domain. If they are not, it will return 0 indicating
failure.
method will make a call to the set_project of
/std/dom/base_master.c if this initial check is passed.
Example 1
ret = set_project ("drakkos", "Being Awesome");
See also
/mudlib/inherits/dom/base_master.c
Cor, don't that look purty?
As a matter for convention, you should also add such a comment at the top of the
file detailing, at the very least, who the author is and when it was started. If you're
working with a legacy file, then that might actually be known:
/**
* Support Domain Master Object
* @author Who Knows
* @started A Long Time Ago
*/
Still, it's better than nothing. Marginally, anyway.
Autodoc works for function level documentation. For commenting within a
function, standard commenting is all you need to use.
The Autodoc Process
Once you've written a file that you want to add to the autodoc system, you have to
add it using the autodocadd command:
autodocadd /d/support/master.c
A second or so later, your file is in the system. However, it won't appear when you
try to get the help file. Help-files are updated on a delay to reduce load on the
system – it will be generated at some point in the not too distant future. However,
you can kickstart the process by using the autodoc command – this will force the
generation of the documentation:
autodoc /d/support/master.c
This will create an overall view of the file in /doc/autodoc/. The filename will be
the same as the file path, except all the backslashes will be replaced with dots.
Thus, it's the file /doc/autodoc/d.learning.master.c.
Each of the functions that have been documented will be stored under
/doc/creator/autodoc/- everything in here is organised in a familiar file hierarchy.
455
Epitaph Online
http://drakkos.co.uk
The one for set_project will thus be found at
/doc/creator/autodoc/d/support/ master/set_project.
We can force these files to be added into the help system using the rehash
command on the directory:
rehash /doc/creator/autodoc/d/support/master/
Your help-file will now be available in all its glory, to everyone who needs it.
Other Help-Files
Player help-files on the other hand are deployed using data files - when we rehash
the help category, the help handler will take all of these files, write them out into
a format called nroff, and create the web-help for the
concept/command/room/object.
The helpfile for the forge command, for example, lives in
/data/help/known_commands/forge.hlp, and looks like this:
::item "forge"::
::->title:: "forge"
::->dir:: PATH_KNOWN_COMMANDS
::->aka:: ({ })
::->see_also:: ({ })
::->bits:: ({
"Syntax",
"$lit$"
"forge <string> {from|with} <parts>",
"Description",
"The forge command is used to shape raw parts into a finished product, "
"most often used for manipulating metal products. Which products you can"
"create is handled by your access to schematic books, and the specific parts"
"and skills required to create the item varies from schematic, material used,"
"and end product.",
})
$lit$ is a special code used by the handler that says 'treat this like MUD output' the it makes sure that the commands and syntax helps are in a consistent format.
The rest of the text of the helpfile works by alternating between heading and
content - the helpfile this produces looks like this:
Epitaph Help
Help
Forge
Epitaph
Name
Forge
Syntax
forge <string> {from|with} <parts>
Description
456
Epitaph Online
http://drakkos.co.uk
The forge command is used to shape raw parts into a finished product,
most often used for manipulating metal products.
Which products you can create is handled by your access to schematic
books, and the specific parts and skills required to create the item
varies from schematic, material used, and end product.
There is no need for you to wrestle with the intricacies of nroff formatting - all
your helpfiles should be deployed as data files.
Why Document?
Sad as it is, you probably won't be here when the End of Epitaph comes. That's
true of almost everyone – in the space of a few short years, absolutely everything
can change. People who seemed like part of the scenery become merely part of
your memory. What will go on though is the contribution you made to the game.
The only constant in life is change – the MUD is going to change around your
code, and if your contribution is to remain in the game it's going to have to be
written in such a way that it is possible for other to maintain it after your
departure.
There is a great saying that helps get the idea in your head – 'write your code as if
the person who maintains it after you is a homicidal maniac who knows where you
live'. Knowing that guy is going to have to deal with your code, wouldn't it be nice
if you could placate him with some calming, soothing, useful comments?
Moreover, good documentation can serve as an aide-memoire for yourself – when
you write a lot of code, you're guaranteed to forget the older stuff. When you
come back and look an incomprehensible mess a year or so later, you'll be in only
a slightly better state than any creator coming to the code for the first time unless
your code is well documented.
Conclusion
Good code is its own documentation, I can't stress that enough. Commenting
done improperly is worse than no commenting at all – comments can be
unhelpful, misleading, or downright wrong. In the process they can drown out the
source code amidst a sea of green. However, when done properly, they are
immensely valuable to everyone who works with your code.
The Epitaph Autodoc system is a powerful way of providing help for coding
functions in a consistent way – for small objects such as rooms and simple NPCs,
it's safe to ignore it. For anything that is going to be used more widely, you need
to be considerate of your fellow creators and thoughtful of the future
maintenance duties that go along with making a lasting, maintainable
contribution to the game.
457
Epitaph Online
http://drakkos.co.uk
Domain Integration
Introduction
Effective development on Epitaph is a complex problem to solve. It involves many
different developers, with many different cultural backgrounds, with varying
degrees of expertise in software development, spread across many time-zones.
It's remarkable we ever get anything done, when you think about it. As the MUD
has grown more complex, it has introduced a whole new set of issues that need to
be resolved.
In this chapter we're going to talk about a process called Continuous Integration,
but we're not going to use it in the same way most software developers mean.
Most of what Continuous Integration involves are things we don't actually need, or
tools that make no sense in the context of Epitaph development. You can think of
this then as a modified process for continuous integration. We almost always do
this anyway, but it's worth discussing why this strategy is worth adopting when
dealing with code files coming from multiple sources.
Multiple Developers – the Traditional Approach
We've already spoken a bit about the cultural and technical barriers that come
with working with multiple developers. Once those have been resolved, the
problems don't go away – it just reveals the existence of new problems. The
comments I am going to make here don't necessarily apply to single developer
projects, but as soon as multiple developers start working on the same code files,
there comes a problem in terms of integrating this code together.
The traditional approach in software development works something like this:





Everyone writes their code in isolation, over a period of time.
At some point, the project leader says 'Right, let's put everything we've
written together to see it all work'
Everyone pools their code, and links it all up.
Hilarity Ensues
Everyone spends the next week or so changing their code so that it all
actually meshes together properly.
The length of time between code integration events directly influences how many
errors will be experienced. Technically, these are known as integration errors,
and they come from various sources. The key source though is assumption –
everyone assumes everyone else is doing things a different way.
Often, the problems aren't as obvious as files not loading – the errors can be
much more subtle. You probably remember the 1999 Mars Lander probe that
went horribly wrong, costing NASA around $125 million. What you probably don't
know is that the reason was because of an integration error. One team at NASA
was writing their code using imperial measurements. Another was writing using
458
Epitaph Online
http://drakkos.co.uk
metric measurements. All of the internal error checking that was done at NASA
failed to pick it up because each part of the program was actually working
correctly. The problem came when the two pieces of code were supposed to work
together.
Partially this is a political problem – if everyone decides on a standard to begin
with and everyone sticks to it, the problem can be greatly mitigated. However, in
large part it's a simple consequence of multi-developer work. People will make
assumptions.
One of the reasons why this is a problem is in the observed behaviour – a failing
probe, for example. Another reason is the simple stress and hassle of getting a
project to work properly – it can take weeks to resolve integration errors in a
complex project (admittedly, the project for which this is true are usually a good
deal more complicated than the typical Epitaph project) at a time when tensions
are already high (integration is not a relaxing process). It can actually be bad for
your health! It's certainly not fun, and that's what we're all here for.
Epitaph operates a 'reuse' mentality rather than a 'roll your own' mentality. That's
what all of our many inherits and handlers are for – to make it so people don't
need to reinvent the wheel. However, if someone in another project is making use
of your code, and it suddenly breaks because of an integration error – well, the
last place people tend to look for the problem is outwith their own code.
This is especially true if integration is an infrequent event – the less frequently
people integrate their code, the less likely people are going to assume that an
integration error caused their new, baffling problems.
The problem breaks down to the length of time between attempts to integrate –
the longer people go without bringing all their code together, the longer bugs and
errors have to creep in.
Examples of this on Epitaph
This may sound like an abstract problem with little relevance to Epitaph, but one
particular domain development strategy shows it in clear focus. It used to be the
case for some domains that project code was developed in your /w/ directory, and
the /d/ directory was only for finished code. Imagine that extended to the
development of, for example, a whole city... in order for anyone to actually walk
around the city, every room has to connect to the right directory in the right /w/
drive.
The code can't make use of the armoury because the armoury doesn't pick up
items in a /w/ drive. Everyone has to have workarounds and shoddy code just to
make sure the areas work (like cloning objects from a /w/ directory rather than
using the armoury). And then it's 'Integration Day', all the code gets moved into
/d/, and absolutely nothing works properly. Everyone then has to spend the next
week or so getting to the point where everyone thought they already were. People
have inherits they have written that would be of use to others, but only their code
459
Epitaph Online
http://drakkos.co.uk
is using it because nobody knew it was there. One person has their move zone
called 'Blah Zone' and the other has 'blah_zone'. Another person thought the
connection to Awesome Street from Fantastic Avenue was a north/south exit,
whereas everyone else was working under the assumption it was east/west.
Multiply these problems (and others) by the size of the project and the number of
creators, and you have yourself one massive headache.
Solving the problems before they arise is always the best bet, but who can solve
the problems across a dozen /w/ directories? Very few people have blanket write
access to /w/, and while individual creators can grant permission to their /w/
directories, it's a lot to co-ordinate. If you're having a problem with your code and
you need someone to help, they can only advise from the sidelines – sometimes it
helps if someone can just pop a few lines of code in place to show a tricky
concept in situ.
The problems of distributed and decentralized development get smaller with
fewer creators, but they don't go away. Any length of time between integration
events is going to cause integration issues.
Continuous Integration
Continuous Integration is how we solve this on Epitaph, and indeed our
continuous integration is usually a good sight more continuous than 'real'
programming. The philosophy is simple – if the delay between integration events
causes problems then the solution is surely to simply remove the delay. This is
why large projects tend to work using 'live ammo'. When you get a project, you're
told which directory in /d/ your project resides, and often there's already a
skeleton in place so that people can actually walk around.
The benefits of this are considerable. It gives context so people know where their
code will fit into the larger arc of the domain. It ensures everyone is working with
the right tools – everyone uses the same inherits. It means that the handlers we
use for in-game code can be used for development code. It also means that your
project leader or domain administration don't need to go hunting through your
things to check something for an update, they just wander to where the code is
supposed to be.
For large projects (the development of Dunglen, as an example), it is simply
infeasible to do development any other way. The projects are too large, and there
are too many creators working on them. Imagine the hassle if each creator had
their street in their /w/ directory!
There are a few problems that come with incorporating development code into a
/d/ directory though. Mainly due to the fact that 'rough' code is living in the same
directories as real code. One example of the problems this causes is in error stats
- the reports from in development or playtesting areas tends to skew the
perception of bugs across a domain - we take our bug counts seriously, and it's
important when addressing maintenance and the triage of bugs that we have a
reasonable understanding of the depth of the problem.
460
Epitaph Online
http://drakkos.co.uk
A scheme is in place to resolve the problems that come with live code existing
with development/testing code. Directories that are under development should
contain, as part of their path, the string _dev. For example, if you are working in
/d/game/awesome_project and you want it to be marked as a dev area, you would
change it to /d/game/awesome_project_dev. When it comes time to put the area
into playtesting, it becomes awesome_project_pt. When it goes into the game, it
becomes simply awesome_project. The code that is likely to have to make a
distinction between play and development areas all have filename checks built
into them. If you set a filename to have _pt in it, this also helps regulate certain PT
capabilities, such as when and where PT protection