Multiplayer Interactive-Fiction Game-Design Blog
Copyleft 2003 – 2013.
|
Version 1, 26
October 2012 Version 2, 3
November 2012 Version 3, 10
November 2012 Version 4, 16 December 2012 |
Version 5, 7 March 2013 Version 6, 14 March 2013 Version 7, 8 June 2013 |
Click to Quick-jump
CircumReality (Multiplayer
Interactive Fiction) Game-design walk-through
Running the world-server
on your own computer
Running the CircumReality game
client
Logged into the game server,
before you begin playing
Multiplayer Interactive-fiction
Computer-Game Design
Evolutionary explanation for entertainment
Story and plot vs. freedom in virtual reality
Why text adventures aren't commercially viable
Virtual worlds and virtual holidays
The attraction of impossibility
Automatically generated content
The game with a thousand faces
The parlour, the lobby, and the sand box
Why traditional MUDs/MMORPGs work
We don't always get what we want
My current "grand unified theory" of avatar games
Analysis of Peter Molyneux's Fable
More thoughts on the strengths of text
Avoiding elves, orcs, and hobbits
(A theory of massively single-player games)
Oblivion: Full spectrum content, from hand-generated to
procedural
My current grand-unified theory of multiplayer avatar games
Quests, stories, and spaghetti
Interactive fiction vs. games (and world-like worlds)
Interactive fiction equation 2
Multiplayer interactive fiction
Making players forget they're playing a game
Immersion-emotion
feedback loop
Posted on a game-designer’s
private web-forum
Blizzard challenge 2008
results compared to 2007
Changes between Blizzard
2007 and Blizzard 2008
Objectively calculating
target and join costs
Mismatched left/right
context target costs
Non-contiguous join costs
estimates
Blizzard Challenge 2009
results compared to 2008
Multiplayer
Interactive-Fiction Scripting-Code
Documentation – Standard Library
Documentation – Server
Library
Documentation – Basic
interactive Fiction Library
Documentation – Basic
Administration Library
Documentation – Basic
communications Library
Documentation – Basic RPG
Library
Documentation – Basic
Artificial Intelligence Library
Documentation – Basic
fantasy Library
Documentation – Basic
Monsters Library
Library – Basic
interactive Fiction Library
Library – Basic
Administration Library
Library – Basic
communications Library
Library – Basic
Artificial Intelligence Library
Library – Basic fantasy
Library
Library – Basic Monsters
Library
CircumReality (Multiplayer
Interactive Fiction) Game-design walk-through
This is a walkthrough of my CircumReality (Multiplayer interactive fiction) game. The source-code is available on http://www.CircumReality.com/mXacSourceCode.zip. (See the “Source code” section in this document.)
A piece-wise download of the .zip file is available at:
1. http://www.CircumReality.com/mXacSourceCode_part1.zip
2. http://www.CircumReality.com/mXacSourceCode_part2.zip
3. http://www.CircumReality.com/mXacSourceCode_part3.zip
4. http://www.CircumReality.com/mXacSourceCode_part4.zip
5. http://www.CircumReality.com/mXacSourceCode_part5.zip
6. http://www.CircumReality.com/mXacSourceCode_part6.zip
7. http://www.CircumReality.com/mXacSourceCode_part7.zip
After installing the CircumReality game (from www.CircumReality.com), you can play CircumReality directly from my game-server. If it is still running.
If my game-server is no-longer running, then you can still play CircumReality:
1. In the “c:\program files\mXac\CircumReality” directory:
2.
Run “CircumRealityWorldSim.exe”.
3.
If you have not already unzipped the
mXacSourceCode.zip file, then do so now.
4.
Someplace in the .zip file, you will find a file
named, “AshtariEmpire.crf”. As of this writing, the file is, 38,225KB.
AshtariEmpire.crf may ONLY be available in the piece-wise
mXacSourceCode_partX.zip files, where X is 1..plus.
5.
Copy “AshtariEmpire.crf” to its own directory,
such as “c:\temp”.
6.
In the World Simulator, press the “Dialog”
button near the filename.
7.
In the open-file dialog-box, type in
“c:\temp\AshtariEmpire.crf”, and press the “Open” button.
8.
Back in the World Simulator, press “Open (and
run)”
The window that appears shows all of the users connected to your
CircumReality game – which is available only on your computer... and (kind of)
if you happen to tell your friends what your computer’s IP address is.
1.
In the “c:\program files\mXac\CircumRealty”
directory, run the “Circumreality.exe” application.
2.
The second time that you run CircumReality.exe,
a pester-window appears requesting payment for the game. The game still works
without paying, although some graphics-functionality and text-to-speech
functionality is limited. Payment is NOT supported at the moment, since the
game is unfinished.
Skip through to “Play the Trial version”.
3.
You might (or might-not) be prompted with
another HTML-like page (using my “Escarpment.dll” code, source-code included in
mXacSourceCode.zip). This page prompts you for your user name.
Every person in the household would have a different user-name. Each user-name
has an associated password-file. The password-file lets players play-in
multiple CircumReality worlds, just like text-MUD players can use a MUD client
to play in many different MUD-worlds. (http://en.wikipedia.org/wiki/MUD)
If this window shows up, press “Create a new password file”.
Type in a user-name, such as “Test Now”.
Click “Next”.
A preferences screen, with preferences common to all CircumReality
worlds appears.
Leave it unmodified, and press “Next” again.
When I pressed “Next”, somehow (I don’t remember programming it), my
CircumReality game automatically logged onto my local server.
4.
If you are NOT automatically logged onto the
local server by the CircumReality client, then....
5.
In the “User password files” page, you will find
your username, “Test Now” listed.
6.
Click on “Test Now”.
7.
You may see the following page:
8.
Click on “Try playing in a new world”.
9.
You will see the following dialog-page:
10.
Under the “File name” edit box, type “c:\temp”,
and press enter.
11.
The page-display changes to show the worlds
(.crf files) in the local directory:
12.
Click on “AshtariEmpire.crf” in the list-box.
13.
Press the “Play” button.
14.
A randomly-generated user-name and password are
automatically saved in your password file.
1.
The following screen will appear in
CircumReality.exe:
2.
If you look on the “World Simulator” window, you
will see that you are logged on:
3.
Back in the game-client, scroll-through the
“Rules of conduct” and press the “I agree to abide by these conditions” button.
CircumReality hosts its own HTML-like windows (provided through
Escarpment.dll, in the mxacSourceCode.zip download). HTML-like pages WITHIN the
game are critical for user-interface design (and flexibility)!
4.
Select a race:
5.
If you select “Show a (fe)male portrait”, the dialog-“tab”
switches to “Chat” (I’ll show that later), and random portrait is displayed.
ALL of this is handled by the HTML-like displays for the windows, as
well as the multi-core RENDERER built-into CircumReality.
6.
Select a gender. (If the player has entered
preferences to always use the same gender, then they are NOT asked this
question.)
7.
Type in the character’s name. If the player has
selected a name that they always use, then the player will-not have to type in
a name.
8.
Since text-to-speech is used EVERYWHERE in the
game, you should press “Speak my name” to hear what your chosen-name sounds
like when it is mispronounced by text-to-speech.
9.
Press “OK”.
1.
You begin the game on a different planet, from
the main game-world. The game was supposed to end on the “different” planet.
Since I only finished 20% of the content, the game does-not end... oh well.
2.
The first thing that you might wish to do is
increase (or decrease) the image-quality. There is an option at the top. You
can also turn-off text-to-speech.
A higher image quality:
3.
You look-around the room by
clicking-and-dragging on the room image.
4.
Your character defaults to a random face. To
change your character’s face, click on your character’s image, underneath the
room display:
5.
Every “object” has a menu of 2-to-6 options, as
well as a more-advanced, “What else can I do?” Click on “What else can I do?”
6.
Oops! The appearance-changing option isn’t
listed. I may have coded the character-image so that “Change my appearance”
does-not appear in the menu until later.
7.
In the edit box, type in the natural-language
command, “Change my appearance”. (REMEMBER: The program is not-yet finished.
The user-interface is INCOMPLETE and ROUGH.)
8.
The following UI appears in the lower-right
corner of the screen:
9.
The most-fun way to change your character’s appearance
is to press “Press this” link, to show a number of different images.
10.
Click on an image you like.
11.
Click on the “Keep this face” option.
12.
Repeat until you find the best face for your
character.
13.
NOTE: I ONLY show the character’s head, since (a)
it’s a lot-less work to 3-D model, and (b) this provides for better emotes.
14.
Click on the “Book” image in the lower-left
corner of the screen.
15.
Move your mouse over the enlarged book-image,
and click on the enlarged book-image. The cursor will show “Open
<Object>”.
16.
The book opens, but due to a user-interface bug,
you only see the text as very-small, and unreadable. If you are actually
playing-along, you will-have noticed a text-to-speech voice speaking-out
narration such as “You open “My adventures in Amroth.””
17.
To view the book enlarged, move your
mouse-cursor to the top of the screen. An icon-menu will slide down; it is
mostly filled with emote icons. Click on the “Zoom” tab:
18.
The book is kind-of like a linking-book in the
Myst series. It is also a journal, showing-you how you have affected the world,
like the radio in Bethesda Game Studio’s Fallout 3. (THE RADIO FEATURE in
Fallout is VERY-VERY COOL/IMPORTANT.) (http://en.wikipedia.org/wiki/Fallout_3, http://en.wikipedia.org/wiki/Myst_(series) )
19.
You can have the book read to you by clicking on
the text, where the mouse-pointer tooltip shows, “Read the right page of
<Object>”.
20.
To enter the world, press the “Enter the story”
menu-option, visible in the menu on your right.
1.
You teleport (linking-book-style) from the
planet-room to a row-boat, being rowed to a small island. (Due-to a bug, the
background screen doesn’t change properly.)
2.
After a short cut-scene, you are provided with a
few choices:
3.
You can ask Gary some questions, shown on the
right. Try that.
4.
Then press, “Please continue rowing”.
5.
More cut-scene play.
6.
Press “Please continue rowing” again.
7.
Your character eventually arrives at the pier.
8.
Click “Climb onto the pier.”
9.
You now are in a room, with a 360-degree view of
the pier. You can rotate around and look in different directions. The images on
the lower-right section of the screen shows you players, non-player characters
(NPCs), and objects in the room.
10.
As you stand around, Gary enacts an “idle”
activity that is narrated. “Gary pulls a piece of grass, and twiddles it
between his fingers.” Etcetera. All NPCs potentially have idle narrations.
11.
Sunset from the pier.
Each segment of the
cut-scene is a MIFL (mXac Interactive Fiction Library) object. (Code is available
in the mXacSourceCode.zip file download.)
The
rCutSceneEvansworthApproach1 resource looks like this:
M3D.exe is a 3D editor,
used to create 3D models and 3D worlds used in CircumReality. (The source-code
is included in the mXacSourceCode.zip download.)
If/when you get this
start-up error, either (a) find the code in the source-code and remove the
time/date check, or (b) temporarily set your computer-clock back.
Below is the Stibbles.m3d
file, located in the mXacSourceCode.zip download.
Stibbles.m3d from a
different angle:
Creating a new building
user-interface:
Adding windows to the
building user-interface:
Character heads:
Painting a surface
user-interface:
The terrain editor:
This tool is an
integrated-development environment for editing a CircumReality world. It also
“runs” the world on a remote server, or the player’s computer. (The source-code
is included in the mXacSourceCode.zip download.)
Wave-editor. (The
source-code is included in the mXacSourceCode.zip download.)
This tool lets you create
your own text-to-speech voice. (The source-code is included in the
mXacSourceCode.zip download.)
1.
Choice-fiction (“Choose your own adventure” and
“Fighting Fantasy”) game-play is intermingled with other game-play. (http://en.wikipedia.org/wiki/Choose_Your_Own_Adventure, http://en.wikipedia.org/wiki/Fighting_fantasy)
2.
Use Cyan’s Myst-like 360-degree surround-images
for graphics. Also like Riven, and Myst III:Exile. (http://en.wikipedia.org/wiki/Myst, http://en.wikipedia.org/wiki/Riven, http://en.wikipedia.org/wiki/Myst_III:_Exile)
3.
Add Zork-like interactive fiction. (http://en.wikipedia.org/wiki/Zork, http://en.wikipedia.org/wiki/Interactive_fiction)
4.
Text-MUD-like features are added. MUD =
Multiuser dungeon. (http://en.wikipedia.org/wiki/MUD)
a.
Players select a character-race and gender.
b.
Add text-MUD-like combat. Combat is NOT the core
of game-play, though.
c.
Player-characters can learn-and-improve skills,
computer-role-playing-game like. “Skills” have turned-out to be unimportant to
CircumReality game-play though. (http://en.wikipedia.org/wiki/Role-playing_video_game)
d.
Add multiplayer capabilities from text-MUDs.
CircumReality also supports single-player no-internet play. And, “run your own
private game for a few hours on the weekend,” so that you can play with a small
group of friends.
e.
Players can team-together into “parties”.
f.
Players can undertake player-versus-player
activities, NPC social-manipulation as well as duels.
g.
Game-play is script-driven. New game-play can be
added, such as card-games, real-estate, etcetera. As per LP-MUD. (http://en.wikipedia.org/wiki/LPMud)
h.
Non-player characters (NPCs) act as
item-and-quest vending machines.
5.
Add non-player characters (NPCs), with
capabilities similar to Bethesda Game Studio’s Oblivion, Fallout 3, and Skyrim.
(http://en.wikipedia.org/wiki/The_Elder_Scrolls_IV:_Oblivion, http://en.wikipedia.org/wiki/Fallout_3, http://en.wikipedia.org/wiki/The_Elder_Scrolls_V:_Skyrim)
a.
The NPCs have daily schedules that they
maintain. Working, sleeping, eating, meeting friends, etcetera. They also have
goals.
b.
Players can listen-in on conversations between
NPCs.
c.
Players can complete “quests” for NPCs.
d.
NPCs act as equipment vendors.
e.
NPCs are sources of information (and stories).
f.
Players can “talk to” NPCs by selecting an item
from a short-menu of questions/responses.
6.
As players change the world... Similar to
Fallout 3. (http://en.wikipedia.org/wiki/Fallout_3)
a.
NPCs disappear, or move-in.
b.
The player’s accomplishments are written into a
journal, along with descriptions of the effects of their accomplishments.
Somewhat like a village newspaper. This feature is similar to Fallout 13’s
radio-announcer.
7.
NPCs speak using text-to-speech (and
transplanted prosody). (http://en.wikipedia.org/wiki/Speech_synthesis)
8.
Players can “talk to” NPCs by typing in
natural-language questions, such as “Where is the cafe?”, “Is Fred friends with
anyone?”, “How much is my sword worth?”, and “What time is it?”
a.
Chatter-bot functionality is also possible. (http://en.wikipedia.org/wiki/Chatterbot)
9.
NPCs have relationships with other NPCs:
a.
When an NPC likes/trusts a player-character
more, the NPC’s friends like/trust the player-character more. The NPC’s enemies
dislike/mistrust the player-character more.
b.
NPCs are in “factions”, similar to extended
relationships.
c.
Players (and player-characters) can infer NPC
relationships by watching NPCs interact. Or, players can ask the NPCs (with
natural-language processing) about their relationships – which the NPCs
sometimes lie-about or deny. Or, players can ask OTHER NPCs about relationships
between NPCs.
d.
Players have a “journal” that displays graphs of
the NPC relationships.
10.
Players can get NPCs to like/trust them by:
a.
When players complete “quests”, NPCs like/trust
the player’s character more.
b.
Gifting objects (and money) to the NPCs, such as
boxes of chocolates. Specific NPCs only accept certain gifts. Players can
determine what gifts NPCs like by observing the NPCs, or by asking other NPCs
what the NPC likes.
c.
Being polite to the NPCs.
d.
NPCs occasionally ask the player-characters personality-test
questions. If the player answers the questions correctly, the NPCs will
like/trust the player better. “So what do you think about my new dress?”
Players are provided with 3-to-4 possible answers. As NPCs like/trust the
player-character more, it becomes more-important for players to answer the
question correctly, as expected by the NPC.
e.
Players can tell the NPCs rumours. (See below.)
f.
NPCs observe player-character’s actions, and
modify their like/trust of the player-character. One NPC in the game notices if
the player helps cleans rooms for him.
g.
Befriending the NPC’s friend improves a NPC’s
like/trust. Becoming an enemy of the NPC’s enemy has a reverse effect.
11.
Players can “find” and propagate rumours.
a.
When players complete quests, NPCs sometimes
tell the players rumours.
b.
When a NPC likes/trusts a player enough, the NPC
sometimes tells the player a rumour.
c.
When players observe NPCs interacting, their
player-character sometimes learns a rumour.
d.
Player-characters can learn rumours from notes,
diaries, NPC-spoken stories, etcetera.
e.
Players can pass the rumours amongst themselves.
f.
Players have a journal with a list of rumours,
and which NPCs the rumours are associated with.
BUGBUG – I am still
working on this
Multiplayer Interactive-fiction Computer-Game Design
This section is a collection of web-page “blogs” that I wrote about computer-game design, specifically for multiplayer interactive-fiction and massively-multiplayer online gaming.
Many/most of the web-page links
are broken.
19 August 2003
by Mike Rozak
A
few months ago I purchased my first MMORPG, Asheron's Call 2. Sadly, I was underwhelmed. It
didn't deliver the experience I was expecting. I tried Anarchy Online, but it
was even less interesting. After reading many other MMORPG reviews, I have
concluded that none (yet) deliver on MMORPG's true potential.
Rather
than sulk, I decided to write this document to critique the flaws in AC2, as a
representative of MMORPGs in general. This critique gets a bit negative. I'm
not trying to rip into AC2, but point out areas for improvement. AC2 does lots
of things right, but I won't bother to mention them here since this isn't a
magazine review trying to provide an unbiased feel for AC2. It's more of a
post-mortem, a technical term by software companies when they review what went
right and what went wrong in a project.
I
am E-mailing this document to a handful of MMORPG companies, although I suspect
most will ignore it.
I'm
not in the target market
I
wish to point out at the beginning that I don't really fit the target market
for AC2:
Now
that I've clarified my point-of-view, let me proceed with AC2's analysis:
Boiling
it all down
When
I approach a problem I like to look at in different ways so I can get a better
grip on the entire problem. One technique for looking at a problem is to boil
it down to its essence. It's the "What are we trying to accomplish
here?" question. For a MMORPG, my answer is this:
MMORPGs are a themed virtual playground for adults (and
teenagers). Instead of swings, merry-go-rounds,
and slides, the adults are given combat, economics, empire building, etc. A
playground is also an environment that encourages socialisation; so too with a
MMORPG.
I'll
touch on this more later, but if you think about MMORPG as a playground you'll
see that many of their features are actually contrary to a MMORPG's goal.
User
interface
I
have been designing user interfaces for years, so I think I'm good at it. Some
people would disagree. My user interfaces tend to create opinions; people
either really like them or really hate them.
The first problem with AC2 user interface is clutter;
it has many different floating windows (map, character stats, inventory, radar,
help, etc.) that not only obscure the scene, but are drawn with cool-looking
half-transparent backgrounds that make reading the small 9-point type difficult
to read. I have two suggestions:
1.
If there's a second monitor, put all the
clutter on the second monitor and use the primary monitor for just the 3D
image. Most people don't have a second monitor, so this idea really doesn't
work.
2.
Display the 3D image in 16:9 widescreen at
the top of the 4:3 screen. Put all the clutter on the bottom of the screen in a
divided window. Perhaps even allow users to control how large the divided area
is.
AC2 should use text-to-speech... Sure,
text-to-speech sounds awful. It's a lot better than trying to simultaneously
read 9-point type and fight a monster though. (I am a bit biased since I used
to work in the speech technology group at Microsoft.)
Remove the radar window. I
discovered that I used the radar window a lot. This is actually a problem since
it shifted the focus of the game from being a first person experience to a
submarine battle, and ultimately took some of the fun out of the game. The
purpose of the radar window is so that the small field-of-view from the 3D view
doesn't become a frustrating hindrance to game play. One alternative to the
radar might be to display two 3D views, one with a 60 degree field of view that
occupies most of the screen. The other would have a 180 degree field of view
that would allow for peripheral vision without the detail; the 180 degree FOV
image could be immediately below the 60 degree FOV image, and one third the
height.
The maps could have been better. AC2
only provides a global map (with no detail) and a zoomed in map with more
detail, but not enough. Multiple zooms and more detail would greatly improved
things. Plus, not having all the points of interest clearly marked would made
exploration more fun.
The in-game help was poor. The
web-site help is much better, but because AC2 can't handle an Alt-Tab to a web
browser I can't both play AC2 and use the internet help. Besides, the in-game
help should be a mirror of the HTML version.
Socialisation
Given
that MMORPGs are online and have thousands of players, one would assume that
they would encourage these thousands of players to interact and entertain one
another. This is what merry-go-rounds do, since the more people on a
merry-go-round the more fun it is.
AC2
doesn't seem to go far enough to encourage socialisation, for a few reasons:
Customising
avatars
People
like to have characters that look different from the other players. AC2 does
not facilitate this:
The
world
AC2's
world is huge; I suspect many MMORPG designers assume that bigger worlds are
better. I thought this too, until I played AC2:
Combat
One
of the toys that players in AC2 have is combat. AC2's combat, however, is not
fun. In previous games where I found combat fun, it has been fun because of
three reasons:
Ultimately,
combat in AC2 becomes make-work, something you have to do in order to reach the
next level. You try to reach the next level a) because it's there, and b)
because you can't explore further without becoming stronger. Given that
exploration in AC2 isn't too exciting, the purpose of combat is only to
increase your level for the sake of increasing your level.
I
have a limited attention span and was bored with this repetitive pattern by
level 5. I did stay with AC2 to level 16 in desperate hope that it would
eventually get interesting, and because I didn't have anything else to play.
Some
other annoying aspects of combat are:
Lack
of puzzles
While
I like the idea of quests in AC2, I was disappointed that the quests were
ultimately scavenger hunts for one or more items. The obstacles between your
avatar and the item were lots of beasties to kill. That's it.
A
few puzzles scattered here and there would have made the quests more
interesting. I suspect that AC2 didn't include puzzles because their solutions would
be posted on the Internet about 30 seconds after the first person solved the
puzzle. Sure, some people would cheat. But most wouldn't. After all, cheats are
available for all adventure games. (Confession: I do use cheats, but only when
I've been bashing my head against a puzzle for hours.)
Lack
of NPCs
Many
MMORPGs discard NPCs (non-player characters) because they think that with
thousands of real people playing there's not need for NPCs.
This
concept extends to shopping: Goods and services are expected to be
player-to-player interactions, not purchases from a NPC at a shop.
I
don't think the player-run economy model works...
1.
When I ran an adventure BBS in 1986 I wrote
in a item selling scheme like E-bay. I thought it was cool; I was creating my
own microeconomic. Well, prices never seemed to stabilise. This could be
because I didn't have enough players. An E-Bay like service might work with
10,000 players, but AC2 doesn't even supply the feature.
2.
After playing AC2, I did some reading about
Ultimate Online's history. It seems they've had lots of problems with their
virtual economy, either with inflation or people hoarding all the money.
My
suggestion would be:
Item
crafting
Many
MMORPGs include an item crafting feature. This allows characters to make
equipment for their own use or to sell. Item crafting is included because a) it
gives players something to do and provides easy-to-create quests, and b)
creates an economy that should (theoretically) make the game more fun.
While
I agree with this in theory, it didn't work well for me in AC2 because:
Miscellaneous
Asheron's
Call 2 appeared in Australian computer stores many months after the US release.
I assumed this was because they were setting up servers in Australia or SE
Asia. They obviously weren't, because my only choice of server was in North America.
My
game experience would have been better if they did have more local servers:
Other
directions
As
I stated earlier, MMORPG are really virtual playgrounds for adults. AC2
provides the following "toys" for its players: combat, exploration,
and item creation. Other toys could be added: (Many of these ideas are
half-baked and shouldn't be taken too seriously.)
8 November 2003
by Mike Rozak
For
awhile I've been thinking about the question: "What makes games fun to play?",
or more broadly, "What
is entertaining?". Needless to say, you can come up with a
list of thousands of entries, none of which really answer the question.
Frustrated
by the huge number of answers, I took a different approach by defining
entertainment: Entertainment
is an activity that keeps people interested in itself despite the fact that
there are no obvious economic rewards (aka: work). So what is
entertaining? This list was just as bad as the "fun" list.
I
then tried to tackle a simpler problem. Having spent a lot of time around animals
(I have volunteered at a few zoos), I decided to answer a slightly different
question, "What keeps an animal's interest?", or more specifically,
"What keeps a
primate's interest?"
This
one is a bit easier to answer:
Animals
also have some internal "drives" (or instincts) that encourage their
actions:
If
you look at the above activities you'll notice that they do a pretty good job
of keeping your interest too. Interestingly, all of the above are fairly common
entertainment devices. Some forms of entertainment exploit them better than
others; books rarely use the "food" drive because words just don't
compare to the real thing, but danger and socialisation are common themes in
novels.
Humans
are different than other primates though, so I'll include a few other items
that interest humans alone:
So
what does this prove? Not much, yet.
I
have listed a number of external stimuli and internal drives that will keep you
and/or an animal interested... at least for a while. If an animal (or you) get
too much of any particular stimulation it will get bored (so to speak) and go
onto something else. Boredom
acts as a safety switch to ensure that an animal doesn't become
obsessed with the activity, since obsession often leads to death and/or failure
to breed. In humans the failure for boredom to kick in is considered a mental
disorder. (Alcoholism, food addiction, computer nerd, etc.)
While
an animal can have too much of a stimulus, it can also have too little. Instincts
dictate that an animal which doesn't get enough food will seek out food. The
same obviously applies to narcotics, sex, socialisation, ferreting, hunting and
gathering, and migration. I suspect (although cannot prove) that if an animal
doesn't have enough danger or "new"-ness it will also seek these out.
Humans obviously do (danger = adrenaline activities, new-ness = take up a new
hobby, etc.)
To
summarise my theory:
1.
Animals' (and humans') interest can be
captured through various external stimuli and internal drives. These
stimuli/drives can be categorised into a relatively short list.
2.
If an animal (or human) gets too much of a stimuli/drive,
it will become bored and ignore it.
3.
If the animal (or human) doesn't get enough,
it will seek it out.
Fine.
But how does this relate to entertainment?
Modern
society is a recent invention. Throughout
most of homo sapien's (that's us) evolution, we were sitting in a savanna in
Africa hunting for our food and being chased by lions. Our genetics are not
attuned to modern life; they are attuned to life 1 million years ago.
This
may be too much to swallow for you, especially if you you're a descendent of
Adam and Eve. Let me give you an example on a less controversial animal, a
house cat, which is designed to hunt in the wild. If you lock it up in a house,
the cat will display some odd behaviours, namely chasing pieces of string
around. That's because it doesn't have any prey to chase, so its hunting drive
needs some outlet. I suspect that if the cat were able to satiate its hunting
drive in the wild, it wouldn't be nearly as interested in a tasteless and
easy-to-catch piece of string.
The
same goes for a human. Modern society provides us with plenty of food and
usually (but not always) socialisation. There aren't many lions chasing us
around though. And we don't get much of a chance for hunting and gathering
(although a shopping mall trip comes fairly close to gathering). My theory
predicts that people will seek out whatever need is un-met. (Specifics will
vary from person to person since not only will their daily experiences differ,
but so will their genetics.) Entertainment
is how we do this.
Following
this logic, people that like to jump out of airplanes are obviously exposed to
less danger in real life than their genes would recommend. Those interested in
soap operas aren't getting enough socialisation (gossip) in their life. People
that go on holiday are fulfilling the migration and/or exploration urge.
(They're also trying to escape from the stress of their every-day lives.) Etc.
But
how does this relate to fun?
When
you ask someone why they participate in an entertainment, they usually say,
"Because it's fun." From this I make the cognitive leap that our
sense of fun is a codeword for "Because it's entertaining," or
"Because it keeps my interest - and I'm not even getting paid for
it." (Some people will play a game even when it's no longer fun, but
that's become they've become obsessed with winning at it.)
Putting
my marketing hat on... Even if "fun" is not the same as "keeping
one's interest" it doesn't matter. As long as it keeps the user's interest
more than any other activity it will sell, so it's just as good as fun.
I
suspect most people reading this are thinking, "Interesting, but way too
simplistic. It doesn't explain why I like to do X." True, it's simplistic,
and true it can't be used to explain everything, but (from my perspective at least)
it provides a basis for explaining why an activity is fun. This is infinitely
more useful than no basis at all.
If
this theory is true, what are the consequences?
1.
All good entertainments have elements of
danger, socialisation, exploration, etc. That's why it's common to go out to
dinner (socialisation and food) and then a movie (danger and exploration).
Conversely, if you have a dinner party at home (socialisation and food) you
always try a new exotic recipe (danger and exploration). Okay, it's not possible
to have all the elements included in an entertainment, but most good
entertainments fulfil a variety of drives.
2.
If you know what a person is missing in their
life you can invent an entertainment for them. (This isn't a new idea and
doesn't have much of an effect because people are self selecting; those that
need socialisation will tend towards a social game, etc.)
3.
Corollary: A computer game can model the
user's need for danger, socialisation, etc. based upon a questionnaire or some
other method. From this it can determine how much danger, etc. it should
introduce into the game, and even guess when the user's danger-drive has been
satisfied (aka: the user is getting bored) and a new drive (such as
exploration) can be emphasised. (In writing terms this control of danger,
exploration, socialisation, etc. is known as the "tension" of a story
arc.) This of course, is difficult to do.
There's
one thing I forgot to mention: Why do I think that stories in themselves are
interesting to humans?
My
thinking falls along the following lines:
Humans
have been able to speak for several hundred thousand years, maybe more. They
have been sitting around campfires and telling stories for just as long.
At
first the elders were just passing on common wisdom to younger tribe members
without the story, such as "Don't eat red berries because you'll
die." As anyone knows, being told something is not the same actually
seeing it or experience it yourself. Being told that red berries are poisonous
is not as "sticky" as actually eating a red berry and getting very
sick (or even dying) or seeing a friend eat the berry and get sick or die.
So,
elders would spruce up their words of wisdom by attaching them to reality.
"Your Uncle Ug ate red berries and died." This helped because it also
included the socialisation instinct/drive into the equation, making the message
just a bit more sticky.
A
generation later though, socialisation didn't come into the equation because no
one alive knew Uncle Ug any more. Connecting him to the message made no
difference so it was back to square one. The solution was to give Uncle Ug
relevance by first describing him as a person and as part of the clan. Only
then do you kill him off by making him eat the berries. A story started to
form.
The
story was further extended with more words of wisdom. After all, an elder isn't
going to spend 10 minutes describing Uncle Ug (so he becomes part of the clan),
and then only include only one sentence of wisdom about him... "Uncle Ug
was a great any mighty warrior... blah blah blah... he was my father's great
uncle. blah blah blah... One day he ate a red berry and died. The end."
The
elder sticks some other bits of wisdom in, such as "White berries are good
to eat" and "To hunt a tiger you do X, Y, and Z." All of these
pearls are strung together with some more narrative about Uncle Ug first
hunting the tiger and doing X, Y, and Z, and then eating the white berries. He
was still hungry so eat ate a red berry and keeled over. Using this trick, the
elder kills thee birds with one stone.
However,
for some people this still wasn't sticky enough because they didn't buy the
long explanation of why Uncle Ug was important. (History teachers still have
the same problem today.)
Genetics
solved this problem though. Those people that didn't believe the story about
Uncle Ug didn't pay too much attention to not eating the red berries, and well,
they eventually ate one and died. Those that paid attention lived. The brain
managed this by creating a semi-hypnotic state where the story's words were
treated as reality (more or less) by the brain, instead of first being passed
through other functions of the brain (such as critical facilities).
Just
ask any high school student about the Titanic; they're much more likely to
relate the scenes from the movie than the drab facts they learned in history
class. Stories are sticky. Facts are not.
As
for why I think story telling is semi-hypnotic: Have you ever watched people
watching TV? Most have that zoned-out look to their face that indicates they're
engrossed by the story. They are oblivious to the rest of the world until the
TV's plug is pulled. (I'd call this hypnotised.) Another interesting point
about TV is that people usually watch the flickering story-teller at night,
very similar to sitting in front of a flickering fire listening to the tribe's
story teller.
This
theory also explains why stories usually have morals and knowledge embedded
within them.
4 May 2004
Revised 7-29 May
by Mike Rozak
Recently,
Ubisoft announced the cancellation or "Uru Live", an online adventure game.
I was saddened to hear the news because I enjoy adventure games, and the online
concept intrigued me; particularly what kind of people would be attracted to
such a service.
I
didn't even get a chance to try Uru Live out since Ubisoft cancelled Uru Live
while it was in beta; I was waiting for a released version before trying it.
While most of the discussion is about online adventure games in general, any
reference to Uru Live details comes from other people's experiences with the
beta; Uru Live may have developed into a different beast if it had gotten past
beta. Therefore, don't
read this essay as post-mortem of Uru Live.
I
was interested in Uru Live because I have played a couple MMORPGs, found them
to be poorly implemented on-line versions of off-line CRPGs (such as Elder
Scrolls: Morrowind or Dungeon Siege), and was underwhelmed. Not only did I get
bored of killing orcs after the tenth one (let alone the 10,000th), but MMORPGs
tend to attract people whose personalities don't match my own. Off-line CRPGs
are only populated by me and a few thousand NPCs; they are devoid of
bloodthirsty teenagers.
I
have also been considering writing my own online adventure game platform (aka:
interactive fiction, or IF) targeted at hobbyist authors. I was hoping to see
what problems Uru Live encountered and how they dealt with them. Obviously,
they encountered insurmountable problems. From what I've heard, they didn't
have enough on-line users to make the venture worthwhile. Since then I've
wondered why they didn't get enough players.
Rather
than investing a few million dollars to produce my own adventure-oriented
online world only to have it fail, I decided to undertake some thought
experiments and try to understand the reason for Uru Live's failure. This
document is the result. It is not
"the definitive work" for online adventure games, but merely intended
to propose some ideas and start a discussion.
Multiple
models
I
have taken too many years of physics. Consequently, I am a strong believer in
particle/wave duality. For people that have forgotten their physics,
particle/wave duality an odd tendency for light to act as a particle when
physicists do experiments that try to prove that light is a particle, and to
act like a wave when the experiments try to prove that light is a wave.
I
apply this understanding to the modelling the universe in general: I don't believe
in a grand-unification theory of anything, since no model can completely
describe the universe. I prefer to approach a problem that requires modelling
by creating several different models and seeing what each has to say.
For
my thought experiments I have used or created several different models. The
models are:
1.
Not enough content
- The effect of content generation costs on on-line adventure games.
2.
Explorers, achievers, socialisers, and
killers - Richard Bartle's model.
3.
Keeping players interested
- Since I volunteer at zoos and have dealt with a number of species, I often
view humans as intelligent primates. This model tries to gain some insights by
mixing primates' interests and virtual worlds.
4.
Achiever vs. explorer content
- And how they differ.
5.
The nightclub model
- Virtual worlds and nightclubs both involve socialisation.
6.
A God-game made real
- Sim City with real life people.
7.
A
virtual world is a platform, not a place - A marketing model.
8.
Playgrounds, Disneyland, and the
Holodeck - Adding story to virtual worlds.
(Documentation
note: From now on I will use the term "virtual world" or
"VW" in place of MUD or MMORPG, unless I specifically mean a MUD or a
MMORPG.)
Model
1: Not enough content
The
original MUD was created by Roy Trubshaw because he "enjoyed
single-player adventure games (Crowther and Wood's ADVENT, Aderson, Blank,
Daniels, and Lebling's ZORK, and Laird's HAUNT)." (Designing Virtual
Worlds, Richard Bartle)
MUDs, as they exist today, are nothing like Adventure or
Zork. (I haven't tried Haunt.) While some still
retain the text interface, they have almost completely discarded the adventure
components in favour of CRPG and socialisation elements.
Why
did this happen?
Roy
Trubshaw's original MUD was eventually inherited and maintained by Richard
Bartle. When asked why adventuring took a sideline to socialisation, Richard
Bartle stated that he intended to use MUD more as a social tool than just an
adventure game, and suspected that people who wrote MUDs after him just
followed his example, perhaps blindly.
Thousands
of virtual worlds are now run by hobbyists (as MUDs), and a few dozen by
corporations (as MMORPGs). The vast majority of virtual worlds are still more
closely related to CRPGs than adventure games.
Could
a successful virtual world model exist that emphasises adventure-game aspects
but which no-one has yet discovered? The Uru team obviously thought so; but
they cancelled Uru Live due to poor attendance.
So
why did no adventure virtual worlds exist?
I
did a thought experiment... (Just to emphasise, this whole article is one long
thought experiment.)
I
imagined that I wrote on adventure game (like Zork), and put it online. What
would users think of the experience? What features would they request? Here's
what happens (in my thought experiment):
1.
The first feature I'd add would be the
ability for users to "chat", since it seems silly that people
wandering around the adventure game couldn't talk to one another.
2.
Next, I'd add the ability for players to work
together on puzzles and hand each other objects.
3.
It takes 20-40 hours to complete Zork (or any
adventure game). After that, players run out of stuff to do and stop playing.
In an online environment, players would complete the game in half the time (or
less) because they'd give each other hints; then they'd run out of stuff to do,
but rather than stop playing, they'd start whinging about lack-of-content.
In
other words, after working on content for a year, I'd have one week of public
release before some players would be asking me for more. So much for my
long-awaited holiday...
At
this point I have a problem. It takes approximately one man year to produce a
text adventure game that keeps someone occupied for 20-40 hours. In a week's
time I'd be able to produce 30-45 minutes of content. The average VW player is
on 20-40 hours a week. For me to keep them all entertained with adventuring
content I'd need to hire a staff of dozens. (By the way, graphical content,
such as what Uru Live was producing, is at least 10x more expensive to create;
my guesstimate is 1 man year of development for 1 hour of entertainment. Raph
Koster presents similar numbers at http://www.legendmud.org/raph/gaming/contentcreation.html.)
So
what are the solutions?
1.
Do nothing (but socialise)
- If I do nothing (except produce content as fast as I can) users will
eventually consume all the content. Most users will leave. Those that stay will
sit around and chat. They will request even more chat functionality, so I'll
stop working on content and emphasise socialisation features. Eventually, the
adventuring component would fall completely by the wayside (except as different
scenery for chat rooms.)
I
could always keep producing content at full speed, regardless of what features
players were using. After a few years I'd eventually get a decent amount of
adventure content. The question is: In the meantime many users would be
attracted to the virtual world because of the socialisation, not the adventure
content. If I suddenly began emphasising the adventure content, would my
existing users leave? Would potential adventure-gamers think about visiting
when all the virtual world directories indicated my world was targeted at
socialisation?
2.
Rely on user created content
- Since I can't create all the content myself, I provide tools so the users can
create their own content. Some MUD authors took this approach creating MUSH's.
The problem with this approach is that 99% of what users create is junk, and if
it isn't junk, it will probably clash thematically with the content created by
the other users. Chaos ensues and users leave. (Just imagine a Jazz band with
1000 players, only 20 of which are good. For that matter, just imagine a Jazz
band with 20 good players, each trying to do their own thing.)
As
Andrew Plotkin pointed out, an Uru Live beta-tester, I am overstating the
"chaos" aspect. When the author-created content runs out, people will
find ways to amuse themselves by creating "content", either through socialisation,
creative uses of world physics, or out-of-game venues, such as web-pages. Some
of it may even be enough to keep the virtual world alive. Relying solely upon
user created content worries me though, because if not managed properly, the
user created chaos has the potential to destroy a world.
3.
Automatically generated content
(monsters) - Rather than trying to create the content
myself, I'd have it randomly generated and spend my time improving the random
generators.
The
easiest randomly generated content is to create monsters. If I place enough
monsters between puzzles in my adventure, then players will spend so much time
fighting the randomly-generated content that my 45 minutes of new content every
week will take them 20-40 hours to get through.
Monsters
introduce some issues of their own; they imply that characters have skills
(namely combat skills), and that the more monsters that characters kill, the
better they get, and the tougher the monsters must be. I have just
(unintentionally) introduced levelling and the levelling treadmill.
Magic
spells aren't far behind.
All
the loot that players collect, and their need for bigger and better armaments
leads to an economy, and maybe even crafting.
Furthermore,
if player characters can kill monsters, players will ask for features so player
characters can kill other player characters... and in turn, enable griefers.
Griefers cause all sorts of other problems that eventually lead to guilds and
numerous other constructs that are now commonplace in virtual worlds.
Interestingly,
but the time all is said and done, the adventure component of the virtual world
disappears and is completely replaced by an online CRPG.
4.
Mix of socialising and monsters
- I could also take the direction of supporting socialisation and combat
functionality. Or, I could mix socialising and user-created content. (I
couldn't, however, mix user-created content with combat because users would
exploit the user-created content to make their own characters stronger.) Both
of these models exist as virtual worlds.
In
"Designing Virtual Worlds," Richard Bartle points out that there are
four stable configurations for virtual worlds. Interestingly, these correspond
to my four solutions for the "not-enough content" problem:
1.
Type 1: Killers and achievers in equilibrium.
(What I have labelled "Automatically generated content.")
2.
Type 2: Socialisers in dominance. ("Do
nothing (but socialise)")
3.
Type 3: A balance between all four types, with
enough explorers to control the killers. ("Mix of socialising and
monsters")
4.
Type 4: An empty virtual world. ("Rely on
user created content")
Richard
Bartle has some other relevant observations...
Model
2: Explorers, achievers, socialisers, and killers
In
1996, Richard Bartle published a paper, "Hearts, Clubs, Diamonds, Spades:
Players Who Suit MUDs", (http://www.mud.co.uk/richard/hcds.htm).
If you haven't read it, I suggest you do so before continuing on.
The
gist of the paper is that players of virtual worlds (online adventure or CRPG
games) fall into four general categories that can be arranged into a 2x2
matrix. These categories are:
One
minor point about explorers from Bartle's book, "Designing Virtual
Worlds", is particularly important to me: "Explorers are a rare occurrence in virtual
worlds."
Why?
I'll get to this in a minute.
Note:
Throughout the document I use a different
definition for "explorers" and "achievers" than Richard
Bartle provides. In "HCDS: Players who suit MUDs",
explorers are defined as players who try to find out as much as possible about
the virtual world, while achievers are people who give themselves game-related
goals and set out to achieve them. Instead, I equate explorers to the online
equivalent to people that like adventure games, and achievers to the online
equivalent to CRPG players. This does twist the model around, in the same way
that using a piano to play music written for harpsichord affects the music of
Bach. You might even argue that it invalidates the model, although I would
disagree. I found such a transposition of the original to be useful to the
problem I'm addressing: why online adventure games seem to fail. If my
adjustments offend you then pretend there's no connection between what I'm
describing and Bartle's model.
My
take on player types
Richard
Bartle's paper goes on to explain how a virtual world can modify its design to
attract different player types, and how each player type affects the other
player types. For example: If hordes of killers move into a virtual world, all
the achievers and socialisers will leave, shortly followed by the killers
(because they have no-one left to kill but themselves.)
Although
Bartle lists the reasons why one player type affects another, I thought I'd
examine the subject from a different angle and see if the results were the
same.
The
first question I asked is, "If I'm player type X what do I think of player
type Y? Do I want more of them in the game, fewer of them, or don't really
care?" This could be tested by identifying players as explorers,
achievers, socialisers, and killers, and then asking them if they'd like to see
more or fewer (or don't care), explorers, achievers, socialisers, and killers
in the world.
Here
is my guess at how it would turn out:
Sometimes,
what people think they want is not what they really want:
How
many explorers are there?
My
comparison of "explorers" to "adventure tourists" brings up
another interesting issue: How many explorers are there in the real world? How
many achievers are there? Socialisers? Killers?
You
can answer the explorers vs. achievers ratio by visiting your local game store
and seeing how many adventure games are on the shelf compared to the number of
CRPGs. In my favourite computer-game store, there are usually one or two
adventure game boxes per ten CRPG boxes, implying that there are 5-10x as many
CRPG players as adventure game players. Or, in other words, there are 5-10x as
many achievers as explorers. (Note: This is only a guesstimate. As a reviewer
noted, explorers may take longer to finish a game than achievers, or may not
play as many games. Or vice versa. My game store may be atypical. Etc.)
Another
way to answer the question is to see where people holiday (assuming they have
the money). Do they go to Florida, Spain, or other safe destination, or to
search for gorillas in Africa or trek around Nepal?
How
many killers are there? This is more difficult because killers only show their
true colours when they have power or anonymity. In real life, that means people
in management positions and prank phone callers. My guess is that 5%-10% of the
real-world population are killers (not literally, but in the player-types
sense).
5%-10%
of the population are explorers. The rest are evenly divided among achievers
and socialisers, 40%-45% each.
Anecdotally,
I've observed that some populations are skewed towards explorers, socialisers,
achievers, or killers:
Why
does the number of explorers in the real world matter? It affects the potential
market size for products targeted at explorers. This, in turn, affects what
type of companies will target them, and how.
The
cost of content
Virtual
worlds are in competition with the real world, television, and other virtual
worlds. If the content (what draws the player to the virtual world) is not
compelling enough, the player will leave. All player types need content. All
content costs money.
Targeted
virtual worlds
Using
the new observations, let me re-explore the fate of virtual worlds targeted at
specific player types:
The
problem with targeting
Even
if targeting a virtual world at a specific category of player works (such as
for achievers), targeting a virtual world at a mix of player-types works better
for a few reasons:
1.
The explorer, socialiser, achiever, killer
matrix is actually a two dimensional continuum. Many people fall in-between
explorer and socialiser, or explorer and achiever, etc. Virtual worlds targeted
exclusively at one player type will lose those players that fall between.
2.
Players' types (explorer, socialiser, etc.)
change depending upon their moods. (For example: While I usually like playing
adventure games, I still go for the occasional CRPG.) One reviewer commented
that it's worse than this, since player types also change depending upon the
virtual world - which is an interesting social engineering subject that
influences all virtual worlds, not just those targeted at explorers.
3.
Players have friends who may not all fall
into the same player type. If a virtual world is exclusively targeted at one
player type (such as explorers) then players will find it harder to get friends
to play. If their friends get involved in another (more generalised) virtual
world, they are more likely to follow their friends.
This
follow-ones-friends issue has other ramifications for virtual worlds, namely
that of critical mass. A commonly accepted "rule" of MMORPG marketing
is that if it doesn't achieve 100K users in a year its subscription rate will
gradually decrease and the MMORPG will fail. Fixed costs are one reason for
failure, but people following their real-life friends is a positive feedback
loop that causes large MMORPGs to get larger, and small MMORPGs to get smaller.
While a virtual world that only targets a sub-set of player types can exist,
it's an uphill battle that ultimately reduces the number of players attracted
to the virtual world.
Model
3: Keeping players interested
After
having a stress dream that the achievers and socialisers of the world got
together and kicked out all the explorers because they were useless (similar to
how the Golgofrinchians kicked out the telephone sanitisers in Hitchhiker's
Guide to the Galaxy), I decided that I wasn't entirely happy with these bleak
conclusions.
A
few months ago I wrote up a short article about an "Evolutionary
explanation for entertainment". See http://www.mxac.com.au/drt/NaturalEntertain.htm.
In the article I produced a list of stimuli and drives that hold a primate's
(aka: human's) attention.
Below
(in the first column) is a list of those stimuli and drives. The other columns
show how well these stimuli and drives are satisfied by a number of different
entertainments, including TV/movies, adventure games, CRPG, and virtual worlds.
The more stars, the better the entertainment fulfils the stimulus/drive. Red
stars indicates that the entertainment is the best at this type of
stimuli/drive. (You may disagree with some of the stimuli/drives or scores. If
so, trying making up your own graph to see if the results change.)
|
TV
/ Movies |
Adventure
games |
CRPG |
Virtual
world |
|
|
Food |
||||
|
Narcotics |
||||
|
Danger |
* |
* |
** |
|
|
Socialisation
- politics |
** |
|||
|
Socialisation
- friends |
* |
** |
||
|
Socialisation
- status |
* |
* |
*** |
|
|
Socialisation
- competition |
* |
* |
*** |
|
|
Socialisation
- gossip |
* |
** |
||
|
Something
new |
** |
** |
* |
* |
|
Sex |
** |
* |
||
|
Ferreting |
* |
*** |
*** |
|
|
Hunting
and gathering |
* |
** |
** |
|
|
Migration |
* |
** |
** |
** |
|
Money |
* |
** |
||
|
Learning |
** |
* |
||
|
Creation |
* |
|||
|
Exploration |
* |
** |
||
|
Problem
solving |
*** |
* |
* |
|
|
Escapism |
** |
*** |
** |
* |
|
Stories |
*** |
* |
* |
Not
surprisingly, CRPGs and virtual worlds are very similar. Virtual worlds equal
or outscore CRPGs on every category except two:
Looking
at how a CRPG's scores change when on-line functionality is added (turning them
into a MMORPG or MUD), one can guess how an adventure game's scores will
change. Below is a table listing how on-line functionality can be used to
improve an adventure game:
|
Adventure
games |
|
|
Food |
- |
|
Narcotics |
- |
|
Danger |
Online-content
can increase the danger if players are allowed to attack one another, and if
there are tangible consequences for losing. |
|
Socialisation
- politics |
Players
will be able to communicate just like in a CRPG virtual world. They may be
more limited in their ability to grief one another, diminishing the benefits
for status, competition and gossip. |
|
Socialisation
- friends |
|
|
Socialisation
- status |
|
|
Socialisation
- competition |
|
|
Socialisation
- gossip |
|
|
Something
new |
CCD
(See below) |
|
Sex |
Virtual
sex? |
|
Ferreting |
- |
|
Hunting
and gathering |
- |
|
Migration |
CCD
(See below) |
|
Money |
On-line
economics make money more interesting than in off-line play. |
|
Learning |
Players
can "learn" from one another by having interesting conversations.
CCD (See below) |
|
Creation |
Players
can share their creations on-line. |
|
Exploration |
CCD
(See below) |
|
Problem
solving |
|
|
Escapism |
If
all the players role-play then an on-line system can improve escapism. If
player's don't role-play then escapism will be harmed. |
|
Stories |
CCD
(See below) |
For
an adventure game, the bulk of the stimuli/drives cannot be improved by going
online. Many of the categories are marked as "CCD", which is short
for "Cheaper content delivery". It's the only thing that online
content has to offer for the given stimulus/drive.
Basically,
if putting an adventure game online can make it cheaper (or better for the same
cost) then players will go online. If going online does not reduce cost then
online adventure games have little to offer, and explorers would prefer to play
off-line adventure games.
The
perils of online distribution
The
Internet promises to be a frictionless (ie: cheap) distribution system. While a
game may be sold for $50.00 at a store, the game company only gets
(approximately) $15.00 of that due to COGs, distribution, and the supply chain.
Theoretically, a game company could just distribute its game on the Internet,
bypass all the expenses, and pocket the $50.00.
As
seen in the stimuli/drives list, potentially cheaper distribution is the main
way an on-line adventure game can improve upon an off-line adventure game.
Distributing
on-line comes with a heap of problems though:
What
happens when all these issues are added up? An on-line adventure game requires
2x as much development time (because of hint giving and on-line development
costs, but lower eye-candy costs), for one quarter the revenue ($7-$10/copy,
and no revenue from bad purchases). A subscription service will bring in more
revenue, but will also incur on-line service costs and continual need for new
content. The number of paying players might increase because of much lower
piracy rates and browsers that unexpectedly liked the game.
Model
4: Achiever vs. Explorer content
Throughout
this document I've been claiming that achievers are people that like playing
CRPGs, and explorers like playing adventure games. Although everyone reading
this has played CRPGs and adventure games, I haven't given a detailed
description of a CRPG or adventure game. Doing so provides some interesting
results...
CRPGs
are games involving repetitive tasks with a variations on a theme that result
in the character's skills improving, such as killing monsters using various
weapons and spells. In other words, "Practice makes perfect." In a
CRPG players learn a skill (killing things with a dagger), apply it (kill 100
rats), improve upon the skill (learn how to use a longer dagger), and repeat.
The process of applying the skill results in a reward of experience points or
money for the player. Both rewards allow the player's character to use
new-and-improved weapons and armour, or enter new regions of the world,
allowing the character to attack new-and-improved monsters. The reward also
acts as a metric to tell the player how well they're doing. (For a detailed
description of this process, see "Swords and Circuitry: A designer's Guide
to Computer Role-Playing Games", Prima Tech.)
CRPG
authors control development costs by having the player kill the same monster
10-1000 times, as many times as possible before the player gets bored with the
activity. To prevent boredom, and keep costs low, variations are added to the
monsters, world, and player-character's abilities. Instead of attacking rats
with a dagger in a house, the PC now attacks "giant rats" with a
"short sword" in a "basement". This cycle of "kill
monsters", "get bored", and "add new variation" are
repeated until the player wins (at which point he/she can start playing over
again), or the player gets completely bored and quits the game. Typical CRPGs
last 50-200 hours.
The
repetition-with-variation model can be applied to more than just CRPGs. MMORPGs
use a similar approach for crafting, or in the case of "A Tale in the
Desert" for building a virtual Egypt.
Adventure
games, on the other hand, involve a world with puzzles that must be solved.
Each puzzle is unique; in adventure games, once a puzzle is solved (such as
killing Zork's troll with the nasty knife) it is frowned upon to have another
puzzle involving a similar solution. (In Zork, the thief can be killed, but the
cyclops and bat cannot.) There is no possibility for "practice makes
perfect" because every challenge requires a different solution.
Adventure-game players are rewarded for their puzzle-solving ability by
learning new information or being allowed into new areas of the game, where
they encounter bigger and better puzzles.
Adventure
games reduce their costs by making their puzzles more difficult, since
difficult puzzles require players to (figuratively) bang their head against the
wall longer until the solution is discovered. (A typical 40 hour adventure game
only takes one hour if the player follows a walkthrough that gives the solutions
to every puzzle.) The cycle in adventure games is: "present player with
puzzle", "player character wanders around the world looking for
clues", "player solves puzzle", and repeat. Adventure games
repeat until the player wins or gets bored. (Interestingly, as the player
wanders through the same environment time after time, he/she notices new
aspects about the world that he/she had missed before and which certainly would
be missed in the walkthrough. It's a bit like reading a good book for the second
or third time. The only difference is that adventure games force you to
re-read.)
Achievers
(who play CRPGs) do not like explorer content (adventure games), considering
adventure games tedious and unexciting. Explorers do not like achiever content,
considering CRPGs to be mindless and boring. This isn't completely true: Some
CRPGs include some relatively-easy puzzles (adventure game content), and some
adventure games include simplistic combat (CRPG content). The middle-ground, an
even mix of CRPG and adventure games, is fairly rare on the PC. (One reviewer,
M. D. Dollahite, pointed out that the Final Fantasy series contains a mix of
both adventure and RPG, but it's console only.) I'm not sure exactly why the
two genres don't mix, but I have a theory:
Induction
and deduction
So
how are puzzles different than killing monsters?
I
was going to write that achievers liked the danger element, but I don't think
this is true. A CRPG has just as much danger as an adventure game; namely none.
Achievers can pace themselves so they're only every fighting monsters that are
clearly weaker, so they have no chance of losing. (The only time they cannot do
this is in a PvP game, which is exciting.) Besides, adventure games can kill
PCs just as easily as CRPGs, if not easier; Zork killed your PC if his lantern
went out.
Some
other differences also exist:
I
don't think any of these are the defining issue though.
The
real difference
between a CRPG and an adventure game is the type of reasoning used to solve
problems. CRPGs use "inductive"
reasoning, while adventure games use "deductive" reasoning. Just in case
these terms are a bit fuzzy, or you never took a logic/math course using them,
here are some definitions from Dictionary.com:
Basically,
induction identifies a pattern, and from the pattern more general conclusions
can be drawn. The "mathematics" definition is strikingly similar to
my description of a CRPG. "First the theorem is verified for the
smallest admissible value of the integer." corresponds to "Start
the player character with a dagger and have him kill a rat." "Then
it is proven that if the theorem is true for any value of the integer, it is
true for the next greater value." corresponds to "Once the
PC kills the rat, teach him how to kill a giant rat with a short sword."
"The final proof contains the two parts." means "Repeat."
Deduction
is what Sherlock Holmes does, aka: an adventure game. "The process of
reasoning in which a conclusion follows necessarily from the stated premises"
corresponds to "The process of solving a puzzle by combining the hints
in the game.". "Inference by reasoning from the general to
the specific." means "Using the generalised hints to solve a
specific puzzle."
Note:
This is a completely different dimension that Richard Bartle uses to
differential achievers (CRPG) from explorers (adventure), which is "action
vs. interaction". The "world vs. players" axis doesn't even
exist in this model, except for the alpha-(fe)male discussion, below.
(Anyone
with a mathematics degree is probably cringing at my distortion of the
mathematical terms, induction and deduction, into game-play.)
Biologically,
induction is often thought of as left-brained while deduction (intuition) is
right-brained. Could there be a biological reason explaining the difference
between player types? (Women are supposed to be more right-brained than men. I
wonder if women are more likely to play adventure games than CRPGs?)
What
does induction and deduction have to do with explorers not liking online
virtual worlds? I'm not really sure.
Here's
a half-baked
hypothesis though: Content that's mid-way between pure adventure game and pure
CRPG requires both inductive and deductive reasoning. The human brain might
find it difficult for both modes of thinking to be active at once. I know that
if I'm playing a CRPG and run across adventure game puzzles the
"flow" is broken, just as if I'm in an adventure game and run across
combat the flow is broken. I couldn't tell you what the "flow" is
though, except that maybe it's related to which side of my brain is dominant.
Perhaps
a mid-point between a CRPG and adventure game is not advisable. If this is true
then content cannot be a mix of CRPG and adventure game, and therefore achiever
content must be completely separate from explorer content.
Watering
down content
Having
explained that CRPGs rely on induction while adventure games use deduction, let
me return to my discussion of achiever vs. explorer content.
If
an achiever comes across explorer content while playing, they won't find it
interesting (or will find it too difficult) and are likely to "cheat"
by downloading and following a walkthrough for the game until they get back to
the CRPG part. If an explorer encounters achiever content they too may
"cheat" and find a way of avoiding the combat, such as getting a gang
of friends to safely defeat a monster that an achiever would have taken on by
themselves. (This only works in online worlds. In offline worlds an explorer
will get bored with the combat and shelve the game.)
If
both sides cheat when they get to the content that they don't like then what's
the big deal? The problem is that achievers not only like to use induction,
they like to "win", which means being the best at the game and
beating out all their competitors. Achievers that encounter puzzles and use
walkthoughs to save time (and boredom) will level-up faster, obtaining an
advantage over other achievers. As a result, all achievers will cheat and use
walkthroughs. Explorers that are intent on "winning at all costs" can
just as easily use the walkthoughs, but it becomes a very hollow victory.
Consequently, there are no explorers that play to "win".
This
isn't exactly true, but before explaining why, I must digress.
Let
me return to the issue of costs: A CRPG's cost-per-hour-played is controlled by
how often a player is forced to fight the same monster. If a player must fight
a monster 1000 times instead of 100 times, the CRPG costs 1/10th as much to
develop. An adventure game's cost-per-hour-played is controlled by how
difficult the puzzles are. The more difficult, the longer it will take players
to figure them out, and the cheaper the content. Harkening back to pubs in the
wild west, let me call this process "watering down the content".
For
both achievers and explorers, the more watered-down the content, the less
interesting the game. If an achiever's content is watered down too much they
will quit. If an explorer's content is watered down too much they will use a
walkthrough to get through the difficult bits, and then quit.
Making
the content too easy (killing only 10 rats instead of 100 levels one up, or
making puzzles trivial) isn't good either. It makes game development more
expensive and reduces the satisfaction players feel upon completion.
Virtual worlds water down content.
When I play a CRPG I usually get half way through it before I get bored,
playing for about 20 hours. When I played Asheron's Call 2, I played about 40
hours and only got 1/8 way through the content. There was more content (I've
heard about 2-3x as much as a typical CRPG), but I also noticed I had to kill
more monsters of the same type before moving onto a new class of monster. I'm
not the only one; many people complain that virtual worlds require too much
work to advance.
Virtual
worlds do this on purpose. While a typical CRPG needs to last 50-200 hours, a
virtual world must last 400-800 hours (20 months x 20-40 hours/month). After
all, in a virtual world, players are paying by the month. Additionally, those
virtual worlds that make levelling too easy find that some players quickly max
out and then either whinge about the lack of content or leave, neither of which
are good.
What
happens to players like me when we conclude that the virtual world has watered
down its content? We leave the virtual world and go back to playing offline
CRPGs. The people that remain in the virtual world are playing for one or more
of the following reasons:
What
about online adventure games? If they were cheap enough to be feasible, what
kind of explorers would stick around for watered down content?
As
a result, people that play the online CRPG portion of virtual worlds are mostly
achievers (levelling treadmill and power-games), killers (PvP and
alpha-(fe)male wannabes), or socialisers.
So
what does this say about the feasibility of explorer content?
This
model indicates that I can create explorer content as long as I don't expect
players to stick around. Watering down the explorer content does retain some
player personalities (power gamers), but most will revert to a walkthrough for
content that is too difficult.
Can
I mix achiever and explorer content? The induction vs. deduction
thought-experiment implies that I cannot have explorer and achiever content in
the same "quest", but that's a half-baked
idea. I could always have two separate quests, one targeted at
explorers and the other at achievers. Most players would play only those quests
that interested them, However, alpha-(fe)male wannabes would use walkthroughs
to cheat on the adventure game content if it provided them an advantage (such
as experience points or special items).
Model
5: The nightclub
Online
worlds are a lot like nightclubs.
People
visit nightclubs to drink, dance, listen to music, partake in velcro suits (or
other trendy activity), and to meet
other people. People visit virtual worlds to kill things,
explore, get a sense of accomplishment, and to meet other people.
Nightclubs
are often "themed", being western, pop, punk, under-18, classical,
etc. People pick their nightclub based on the theme. Each theme, of course, is
associated with a style of music. More importantly, each theme is associated
with a style of patron.
One
of the reasons I don't like MMORPGs is because they're filled with teenagers
(or teenage-minded people). I don't care to talk to most teenagers. I didn't
care to talk to teenagers when I was a teenager. The entire MMORPG environment,
especially the achiever/killer features, is targeted at teenagers. If I were
producing a MMORPG I'd make this decision too, since teenagers are a very large
percentage of gamers and their personality works well with MMORPGs.
One
of the reasons I was interested in Uru Live was because teenagers would stay
away from it, leaving adults (or adult-minded teenagers). It didn't provide the
action or reptilian-brained activities that so attract teenagers.
I
suspect that if explorer-oriented worlds are ever commercially viable they will
be targeted at adults, and achiever-oriented worlds will be targeted at
teenagers. Uru Live was clearly targeted at a different demographic than
MMORPGs, and many customers liked it specifically because it didn't include
teenage-minded MMORPG players. (See Uru Forums) (If this
is so, my earlier question about whether it's possible to mix explorer and
achiever content is moot; they won't be mixed because each one will be targeted
at a different demographic.)
Some
nightclubs include another useful "feature" that many virtual worlds
already copy; they have a person who stands at the doorway and only lets
desirable people in.
Model
6: A God-game made real
In
a "God-game", such as Black & White or Sim City, the player
pretends to be God. His subjects are thousands of electronic AI's that he must
keep happy and healthy. If he doesn't they either leave or die.
Oddly,
virtual worlds are God-games for the authors. The virtual world author must act
through his virtual world tools to keep his virtual world's inhabitants happy,
or they'll leave. Unlike a God-game, the inhabitants of virtual worlds are real
people. The God-game nature of running a virtual worlds is a well known to MUD
wizards, although not necessarily stated in such a blunt manner.
Of
course, real-life players are infinitely more complex than the AIs used for
God-games, and it takes more than a few well-placed roads and skyrises to keep
real people happy. However, the God-game analogy raises some interesting
issues:
First,
some players really enjoy playing God, in God-games, and as virtual world
authors. A virtual world could include sub-worlds where players act as Gods and
invite other players in to enjoy what they have created. This, of course, is
player created content. It has a few problems:
I
don't have any novel answers to these solutions except for the exploit
problem... CRPGs encourage exploits because experience, gold, and items
translate from one sub-world to another. (If they don't transfer then many CRPG
players won't bother investing time in the other world.) In an adventure game,
exploits are much less likely since there is no experience or gold (usually),
and items can logically be kept in the world where they were created.
The
second observation is this: The real God (or deities of your choosing) uses
people to accomplish his (or her) goals, often by "working in mysterious
ways" through luck, voices, dreams, and visions. How can a virtual world
author (aka: "God") manipulate players to improve the world
experience?
When
I say paying, I don't mean with real money, but with game money or goods.
Here's an example:
Current
virtual worlds "pay" players to go on quests, such as killing all the
rats in the farmer's basement or rescuing the a lost villager. Payment includes
experience, in-game money, and equipment. Many players like quests because they
give the players goals, and some additional pocket money.
Virtual
world authors should be yelled at by their accountants for such quests. It
costs 100 g.p. (of virtual money) to hire a player to kill the rats. This is a
waste of money, since the virtual rats don't really need killing. After all, they
were generated for the sole purpose of being killed by the questing character
in the first place. It's like creating make-work for employees, and doesn't
make economic sense.
Why
not "pay" players to entertain each other? It's easy; just twist the
quests a little. Have one NPC, someone in organized crime, hire players to
steal chickens from all the farmers in town, for a cash reward of 100 g.p. The
farmers, in turn, hire different players to protect their chickens, for 100
g.p. Only one of the player characters gets the reward. Both get entertained.
The
plot can be extended: A third PC, the local mayor, may pay someone 1000 g.p. to
knock off the organized-crime PC. When that happens, the market for chicken
stealing and protecting dries up until a new NPC fills the old one's place.
Maybe a NPC that was previously a pick-pocket is promoted to being the new
chicken-stealer. (One reviewer mentioned that http://www.skotos.net/articles
contains articles with similar concepts, although I haven't spent the time
looking for them.)
Repeat
ad infinitum.
Of
course, this is very manipulative. Players are being objectified by the NPCs.
(Which is probably just, because NPCs are objectified by the players.) Will
players like the scheme? Who knows. Some may object, but I suspect many of them
will find it more interesting to be part of the larger story that to pout that
they're being used by the authors to enhance the virtual world.
The
author, playing god, is responsible for programming in the major NPCs' goals.
The NPCs, in turn, manipulate the players and drive the world story. When an
NPC reaches its goals, or is killed by a PC, the author jumps in and adjusts
the story.
What
does this second observation have to do with explorers? The web of NPCs hiring
PCs to do odd jobs might actually add up to an important story. Maybe a wizard
keeps hiring PCs to acquire various magical ingredients. If someone is smart
enough to observe, they may realise that the wizard is building a special
magical item, or is casting a powerful spell that allows the wizard to take
control of the city. Or maybe the wizard is being controlled by the Boy
Sprouts, who are in turn controlled by the UFOs. (If you don't get this joke
you've never played the "Illuminati" card game.) If the wizard is
stopped, do the future events of the world change? This is all explorer
material.
The
system also requires relationships between individual NPCs and individual
players. A NPC will learn how reliable a player is, and give the more difficult
tasks to the more reliable players. Achievers will like this, at least until
their patron is killed by a griefer or another achiever.
The
revised quest system has at least two problems:
1.
It's a bit too much like real life. Most
people are already pawns of others in real life, and may not wish to play the
same role in a virtual world.
2.
It's open to numerous exploits that may doom
it from the start.
Model
7: A virtual world is a platform, not a place
This
title isn't exactly correct... To the players a virtual world is a place. To
the developer it's a platform. I'll explain why, and how this affects virtual
worlds targeted at explorers.
If
you think that a virtual world is solely a game, then what I'm about to say
won't make much sense. I view a virtual world as a place that allows players to
partake in activities, some of which may be games, some socialisation, and
other forms of entertainment. The game is just a portion of the virtual world's
experience. It's not even one game, but many games, such as CRPG, economics,
and flight simulators all rolled into one. If you haven't heard this concept
before then do some searching on the web or read "Designing Virtual
Worlds." If you don't agree with my assumption that a virtual world is a
place, this model won't bear any weight.
Back
to talking about "place"... Assume that a virtual world were just a
place, such as the Earth stripped of all people, animals, and potentially
plants. What could an individual do in it?
This
would quickly get boring. (Anyone who doesn't think so has a much longer
attention span than I do. For those who don't believe me, try one of the many
3D chat virtual worlds during off-peek hours and see how entertaining vacant
worlds are.)
A
developer could make the world more interesting by adding sentient creatures,
like other players. This would allow for another activity:
When
people run out of stuff to talk about, chatting too gets boring. In the real
world, bringing people together to chat is called a party. It is accompanied by
food, alcohol, and games (such as cards and twister) in order to liven things
up. Since food and alcohol aren't viable on-line services, a developer can only
use games to make the virtual world more interesting.
Existing
virtual worlds have added games that go well beyond card games in scale and
complexity. These new games are:
Virtual
worlds could also incorporate other computer genres:
Notice
the trend: Because a virtual world by itself is fairly dull, developers add
various sub-games (and activities) to the world. As players get bored with
those sub-games, developers either expand the existing sub-games (such as
adding more levels to a CRPG), or incorporate new sub-games (such as Star Wars
Galaxies' recent addition of space combat). This becomes an infinite cycle.
Most
game genres that have been incorporated into a virtual world originally existed
off-line, and are still sold as such: CRPG, adventure, FPS, cards, etc. When an
off-line game is incorporated into a virtual world, the user's on-line
experience is often inferior to the off-line counterparts:
Virtual
worlds offer only a few games that are better on-line than off. Such sub-games
rely on large numbers of online users, such as kill-fests, crafting, and the
economy.
Despite
the inferiority of many virtual world sub-games, people are still willing to
pay a lot of money to play. Why?
It's
because of synergy. The combination of several off-line games (or free on-line
games) into one virtual world produces a better experience. The whole becomes
more valuable than the sum of its parts. Here are some examples:
Interestingly,
if you apply the synergy concept to an adventure game (such as Myst) you'll see
that an adventure game is a virtual world containing sub-games of puzzles. The
two elements have a positive impact on each other. The virtual world element
gives a purpose to the puzzles. The puzzles make the player spend more time
wandering around the virtual world and discovering the scenery. Myst, if broken
down into scenery and a set of puzzles, is much less interesting than the
whole. (Myst also includes a story component, which I'll discuss later.)
So,
let me rephrase the question a bit: How does a virtual world provide
value-added to the player?
A
virtual world indirectly provides value-added to the player by being a good
platform for the developer:
Virtual
worlds are "platforms" because the virtual world, which is mostly
worthless stand-alone, provides the structure upon which other games are built
and marketed.
I
can imagine a future where the top three virtual world developers morph into
something like cable providers... Consumers pay a monthly fee. For that fee
they get to play any of 50-500 games (as opposed to cable providers, which
provide 50-500 TV channels). The game company's marketing model would change
from a per-unit model to an annuity... If you were a large corporation, would
you rather have a risky hit-based model, or a stable and profitable annuity? I
know Bill Gates' answer to that question.
In
"Playgrounds, Disneyland, and the Holodeck" I discuss a variation on
the cable-TV model.
Here
is a thought: How many people have more than one cable provider? How many
people have both cable and satellite TV? Approximately none. Apply this same
thinking to virtual worlds: How many people will pay to be members of two
virtual worlds at once?
My
Microsoft-trained mind does the math and comes to the following conclusions: In
the future there will be three (maybe five) virtual world companies that own
80%-90% of the market. They won't own just the virtual worlds market, either.
They will also own most of the retail games market. The Big Three will dominate
any customer segment they're interested in; this means mass market. They will
dominate any market where throwing money at the problem improves the user
experience; such as modelling and animation. (Just think: large virtual world
providers = mass market = Hollywood.)
The
remaining 10%-20% of the market will eke out an existence by targeting
consumers that don't like the mass-market content provided by the Big Three.
They won't have enough money to provide the dazzling visuals and animations
that the Big Three will. Nor will their worlds be as large. They probably won't
even get shelf space on retail stores, so they'll have to provide internet
distribution.
Am
I right about this? (I hope not.) Would anyone believe me if I were right?
(Probably not. People don't like considering dire predictions, especially if
they're the ones whose doom is predicted. If they did listen, though, they
might be able to protect themselves.)
If
you agree with me and follow my line of thought then you'll notice many
ramifications for virtual worlds. However, I'm discussing virtual worlds
targeted at explorers here. How does all of this affect them?
Model
8: Playgrounds, Disneyland, and the Holodeck
After
posting a draft of this document on rec.arts.int-fiction, several reviewers
pointed out that I didn't mention author-created stories, and that I didn't
discuss some of the future possibilities of virtual worlds. Originally, I
hadn't touched on author-created stories because they're not currently possible
in virtual worlds.
However,
I forgot one very important thing: Stories and adventure games are linked.
Except for the first few adventure games, the puzzles have not only been linked
into the world, they've been linked with the story surrounding the world and
immediate happenings. Some adventure games rely on a story that happens before
the game starts (such as Myst or Deadline), while others use the puzzles as a
means to advance the story (Syberia).
One
reason that off-line adventure game players may not want to go online is that
virtual worlds find it much more difficult to include a personalised story;
100K players running around makes plotting a personal storyline very difficult.
Traditionally,
virtual worlds are devoid of author-created stories and instead rely on players
to create their own "stories" through their activities. While such
"stories" are compelling because the player is part of them and
influences the stories, they do not compare with a story created by a
professional author.
In
a large virtual world, personalised (author-created) stories are not possible.
Backstories are certainly possible. Stories that affect the world as a whole
can also be done. (From what I've heard about the beta of Uru Live, the team
was incorporating a running story into the world, much like Asheron's Call
does. Neither Uru Live's nor Asheron Call's author-created story can be
personalised for every individual though.)
Maybe
adventure game players want a more personal story?
If
this is so, then online adventure games are still-born. Here's why:
1.
An author writing a linear novel (or movie)
can create a very good story.
2.
An author writing a nodal novel (Choose Your
Own Adventure), cannot create quite as compelling a story as the linear one
because the author can't control all the reader's choices; good stories often
hinge on choices. The loss in story quality is (hopefully) made up for the
enjoyment of interactivity.
3.
In a face-to-face RPG, the game master can
maintain a decent story with six players, even if the players decide to derail
the story. The game master, being human, has enough intelligence to either get
the story back on track or invent a new one. The game master cannot, however,
manage the story if all six players go in different directions.
4.
An offline CRPG or adventure game can
maintain a poor story if the player implicitly agrees to stay on track and not
try to break the storyline (or if the world prevents the story from being
broken). As soon as the player tries to diverge from the current story the
whole system collapses.
5.
Many MUDs, such as those from Skotos, provide
storytellers (real people) that provide more individualised stories. The
storyteller, of course, can only handle so many players, and those players must
be amenable to provided the story, just like face-to-face RPGs. (Uru Live had
actors that helped develop and personalise the story.)
Analysing
the above results produces a few rules:
1.
The more players in a virtual world, the more
difficult it is to maintain a story.
2.
The more choices that players have, the more
difficult it is to maintain a story.
3.
Players that try to subvert a story make it
more difficult to maintain a story.
4.
The only way to mitigate the above
difficulties is to have more intelligence behind the story generation, either
more storytellers (real people) or a more-intelligent storytelling AI.
A
large virtual world is problematical because of the number of players and the
number of choices each player has. To provide author created stories at an
individual level (as opposed to world-based plotting), the virtual world must
either hire an army of storytellers and hope their stories don't collide with
one another, or produce a really good AI that can create non-colliding stories
for 100K players, many of which will be trying to break the AI for fun and
profit.
An
army of storytellers is possible, although very expensive; undoubtedly some
virtual worlds will cater to this market, but they'll charge more for it. The
other approach, AI, requires technology that doesn't exist.
Personalised
stories for large virtual worlds seem doomed...
However,
approaching the problem from a different direction provides an answer that
isn't quite so bleak:
Playgrounds
A
playground
is a piece of land (place) with numerous mechanical contrivances (world
physics) that lets kids (players) interact with one another. A contemporary
virtual world is a virtual playground. Instead of merry-go-rounds and see-saws,
virtual worlds have combat and trading. Playground PvP involves pushing kids
off the merry-go-round, throwing sand, calling kids names, and occasional
fights. Virtual world PvP is based on virtual combat, virtual economics, and
social abuse.
Adults
build playgrounds for kids so that the kids will be entertained and physically
worn out after the experience, providing the adults with some rest. Playgrounds
also serve a socialisation purpose, teaching children how to interact with one
another. Richard Bartle presents socialisation experience as an important
aspect of virtual worlds.
A
playground has no author-created story, even though stories are created by the
kids in the playground. Playgrounds are not usually themed, other than an
occasional colour scheme or painted smiley face.
Disneyland
Disneyland is a playground,
kind of. Actually, it's a "theme park". However, it has many elements
of a playground: lots of mechanical contrivances that are either designed to
entertain or make one feel sick. The contrivances, such as Space Mountain, are
more complex than a playground, but Space Mountain is essentially a really big
and really fast slide.
Disneyland
is not designed so that kids interact with kids though. It's designed so
parents and kids enjoy quality time together. The quality is so compelling that
some people travel half way around the world to enjoy it.
Unlike
a playground, Disneyland has an author-created story, or rather, stories. When
people wander around Disneyland they are also wandering around many of Disney's
movies and television shows, such as the tree-house in the "Swiss Family
Robinson" or the castle from "Sword in the Stone". Not only are
many of the rides based on Disney stories, but NPCs (such as Mickey Mouse and
Goofy) are right out of the stories. Because visitors to Disneyland have been
previously exposed to Disney stories, the act of climbing up the "Swiss
Family Robinson tree-house" somehow includes the visitors in the story of
the "Swiss Family Robinson".
Star
Wars Galaxies and Middle-Earth Online are both using the Disneyland model to
enhance their virtual playgrounds. They may not do so successfully, but both
virtual worlds are only the first generation. Give them time. (Uru Live was
also heading in this direction, building upon the stories established in
previous Myst adventure games and books. Uru Live also had actors.)
A
quick comment about actors: Uru Live hired human actors to wander around their
world and give speeches that advanced the world's plot. While this has
advantages of realism, it is problematical. Players that were not around when
the actor was online felt like they missed out. Such feelings will either cause
all players to be online and in the same area where actors will appear (which
is a technology issue), or leave players feeling like they're on the outside
looking in (for those people living in non-US time-zones). Things can get even
worse: As the infamous assassination of Lord British in Ultima Online shows,
someone will try to derail the actor's actions just for the fun of it.
In
"A virtual world is a platform, not a place" I discussed the
possibility of each of the Big Three producing a fantasy, science fiction, etc.
virtual world. They could go a step further than this, producing several
science-fiction worlds (or one science-fiction virtual world with several
sub-worlds), each one based on a different author's works. One science-fiction
world could be based on Star Wars, while another on Larry Niven's works, and
another from Farscape. The virtual world could either license the IP from the
author, or the author could license the space from the virtual world.
Of
course, the themed virtual world would be tied into the appropriate books or
movies. Players could wander through the themed world, enjoy the themed
activities, and even interact with actors playing important characters from the
books/movies. The virtual world might even include linear narratives that
further the story, presented as cut-scenes or conversations with NPCs. Maybe
the Han Solo NPC would relate a short anecdote (a 10 minute cut-scene) to any
visiting player about how he acquired the Millennium Falcon.
Is
a themed world with cut-scenes enough story to make everyone happy? Probably
not, but it's a start.
Holodecks
At
first glance, Star Trek's Holodeck
is the "holy grail" in virtual worlds, not only because of the
stupendous graphics, but because the Holodeck is smart enough to tailor an
experience to a user. (It's even smart enough to take over the Enterprise a few
times.) Oddly enough, Star Trek's Holodeck doesn't include avatars of users
from all over the galaxy, like a MMORPG does; such additional
"features" can easily be imagined though.
The
Holodeck may seem like the final word in virtual worlds, but such appearances
may be deceiving. Even if the Holodeck were possible today, it might not
"work". Here's why I have my doubts:
Over
the course of the 1980's I wrote a number of amateur games, including
text-adventure games, a Wizardry clone, an Ultima I clone, and an adventure
BBS. I also did some thinking about where these games were going. What I
imagined then was similar to what we have in contemporary MMORPGs. (Actually,
current MMORPGs have much better graphics and many more users than I ever
dreamed of.)
However,
I am dissatisfied with current MMORPGs; they feel soul-less to me. This is not
something I anticipated, but is a consequence of having 100K players in a world
being run by a corporation bent on maximising sales. While my predictions were
right on one level (graphics), they completely missed the mark on another
(user-experience).
Twenty
years later, I can imagine a Holodeck-generated virtual world, but I don't
think a Holodeck will turn out as I expect. Obviously, some technologies won't
exist in 20 years: Transporter-fabricated matter is a bit undo-able, so I'll
have to settle for a 3D virtual reality headset with a data suit, or a VR room.
The AI is also near-impossible, requiring some human intervention in a Holodeck
experience. The fundamentals are there though.
I
suspect that the Holodeck experience won't be the ultimate virtual world
experiene, for the same reason that kids rebel against their parent's dreams of
having them become a lawyer or doctor. Simply put, people don't like being told
what to do. They like being manipulated even less. Any attempt by an AI or real
person to impose a story on a virtual world is an act of manipulation, even
though players may wish for some form of story.
Game
masters in face-to-face RPGs know they walk a fine line between creating a good
story and forcing players' hands. Players that feel like they've been wronged
will make a fuss and/or leave the game. Part of the reason that face-to-face
RPGs work, and holodecks may not, is that the player and GM know and trust one
another; If the player feels they're being forced to do something they don't
want to, they will be more forgiving because they know the GM has their best
interest at heart, and making a stink could hurt a friendship. Similarly, the
GM knows enough about the player to know what buttons not to push. Getting
an AI or paid professional to fill the role of friendly GM is very difficult,
although not necessarily impossible.
I
can imagine myself going along with the Holodeck story some days, while
spending other days seeing how far I can go before I break the AI or make it go
crazy. What happens if I replay the murder mystery and hide the murderer's
weapon? If I jump off a cliff, what does the AI do to realign the story, have
Superman catch me? Basically, I'll turn the original virtual reality game into
a new game, push-the-AI.
Does
this mean that holodecks won't "work"? Maybe they will and maybe they
won't. Unexpected effects are bound to appear though. The potential problem of
players resisting story-manipulation is only one example. More issues are
undoubtedly lurking.
Mix
and match
A
virtual world doesn't need to pigeonhole itself into just being a playground,
or just a Disneyland, or just a Holodeck. It can be a combination of all three,
using elements where appropriate.
The
"playground" aspect of a virtual world is the world's physics and
mechanics, determining how players (and NPCs) can interact with one another.
Playground "stories" are all player generated using the world's
physics. Contemporary MMORPGs are virtually all playground. Uru Live had
relatively little playground, other than chat and cone soccer.
The
"Disneyland" dimension is the amount of author created stories.
Being created by a person, the stories are expensive, so must be reused by
players. As a result, either the story must be outside the player's control
(such as story that happened before the game began, as in Myst), or the player
must be willing to follow an essentially linear story path (Syberia).
Unlike
a traditional adventure game, the "story" doesn't have to limit
itself to one subject. Hundreds of stories can populate a world, creating a world of stories, much
like Tolkien's Silmarillion or Australian Aboriginal Dreamtime stories. (Most
Dreamtime stories are associated with places, so as Aborigines move through
their countryside they are also moving from one Dreamtime story to another.)
Finally,
the "Holodeck" dimension of storytelling is the amount of intelligence (human or AI)
used to provide a more personalised story. For a long time to come, this will
be a person, not AI. Although, a simple AI or well-built tools could aid the
human storyteller. Traditional pen-and-paper RPGs use this form of storytelling.
Each
form of story has its own advantages and disadvantages: Playground stories are
compelling because the player is part of them and creates them, although the
stories are not refined. Disneyland stories are refined and polished, but
immutable. Holodeck stories are a compromise, allowing users to affect the
story, but at the expense of story quality and human intervention, which
introduces an element of conflict between the player and the storyteller.
Different
players prefer different story types. Providing only one form in a virtual
world will alienate some players.
For
example: Many off-line CRPG players accept a world with only playground stories
(Diablo or Dungeon Siege - both of which are very weak in the author-created
story department). Such players have no problem adapting to a MMORPG (which
lacks Disneyland and Holodeck style stories). Since most adventure games
include a strong author-created story component, I suspect adventure game
players prefer author-created stories (Disneyland or Holodeck-style).
Consequently, they're alienated by the playground-style stories in MMORPGs.
Not
modelled: Poor launch for Uru Live?
Uru
Live was officially cancelled because not enough users signed up. However, part
of the reason they may not have had enough users is because of a poor launch
strategy: Uru, the retail package, was available on store shelves in November
2003, just in time for Christmas. Uru Live, the online portion, wasn't
scheduled to go public until February 2004.
Uru
Live (online) shipped after Uru (offline). In the user's mind, Uru was
synonymous with Uru Live. Users probably expected that when they installed Uru,
they'd have access to Uru Live; I know I did. This wasn't the case, since the
team (or at least marketing) considered them two separate beasts. Therefore,
Uru had shipped, but Uru Live was in beta until February, and perhaps later.
This is a bit confusing for users, even me, and I work in the computer
industry.
I
suspect that management decided to ship Uru "separately" from Uru Live
so that Uru would be out in time for the Christmas market, even though Uru Live
wouldn't be ready in time.
This
decision had several ramifications:
1.
Loss of marketing momentum, since much of the
hype over Uru would have dissipated by the time Uru Live was ready.
2.
People that purchased Uru expecting to play
Uru Live right away would be upset, producing expensive support calls and user
dissatisfaction.
I
resemble this item. In Australia, the Uru package didn't include the
registration code I needed to sign up for Uru Live. I called the computer store
where I purchased the game, only to learn I had to call a 1-900 Ubisoft number,
which would cost me
money, four or five dollars I think. After two calls I eventually got my
registration number, only to learn that Uru Live wouldn't be available until
February.
3.
By the time Uru Live would ship, many Uru
players would have finished Uru and moved onto other games. Getting them to
come back and sign up for Uru Live would be difficult. If beta took longer than
expected, which seemed to be the case from Uru
Forums posts, then getting users back would be even
more difficult.
4.
Some users may have known about Uru Live's
delays and not bothered to sign up for their free month until Uru Live was
officially released. After all, why waste a free month? The Uru team would have
no idea how many users were waiting in the wings. (This is exactly what I did.)
5.
Because the game contained both an online and
offline component, some people may have left the online portion until they were
finished with the offline game. Again, why waste a free month? And again, the
Uru Team would have no idea how many users applied this strategy.
Combined
wisdom/folly of the models
Now
that I have examined explorers from every possible angle, does this thought
experiment give me any useful information?
Here
are some possible ways to get explorers using on-line virtual worlds:
1.
Make content creation as cheap as possible
(easier said than done), and/or allow blessed users to create their own content
(fraught with problems).
2.
Produce puzzles that still require work to complete
even if the solution is spelled out to a player in a walkthrough. This could be
accomplished using automatic content creation or dynamically changing puzzles.
3.
Reduce the player's cost by eliminating the
retail package. (This then requires a small download and/or broadband.) Price
the game so that the player sees the monthly fee as good value for money.
4.
Produce a world with private regions, or a
mechanism so explorers will know how crowded a region is. If a region is too
crowded they can spend their time exploring an emptier one.
5.
Emphasise exploration and socialisation.
Limited achiever and killer functionality could be provided, but it may not be
worth the development, content, and headache costs.
Some
less-obvious solutions might also work:
1.
Find a business model that allows players to
stay for only a couple of months, or only play for a short while (5 - 10 hours)
each month. (So much for a monthly fee. TV-style ads won't work because the
online service will only be paid for click-throughs, not eyeballs. People are
not likely to click on an ad and interrupt their immersion. In-game advertising
could work for modern or futuristic worlds.)
2.
Since players will only log in once in
awhile, provide automatic E-mail alerts to players when new content is added.
(Think weekly/monthly episodes, just like TV and E-mail being the TV-Guide.)
3.
If players only log in a few hours each
month, they won't build any social contacts, and the socialisation features
wouldn't be used (except for people that know each other outside the virtual
world). This could be solved by co-marketing with several other virtual world
providers and encouraging players to distribute their play time amongst them,
perhaps one night a week in each. Encourage players to play on the same night
each week so the same faces will be around. (Sounds even more like TV.)
4.
Expect and encourage explorers to make
on-line friends and meet up with them in the different worlds. (Do not expect
explorers to bring their real-world friends into the game.) Provide "dating"
features that let explorers group up based upon which content they haven't yet
explored.
5.
Include "games of skill", like
card-games and chess. The allow for limited PvP and encourage socialisation.
6.
Find a way to include more "story".
Adventure games, CRPGs, and virtual worlds are all beaten by TV's story-telling
ability. Adding stories may draw more users into the game. (The stories do not
necessarily need to involve the players' characters, but can be about the
world's famous NPCs.)
7.
Use the adventure-game nature of
entertainment to weed out undesirable player personalities. (Or just have
someone that only lets desirable people into the game.)
8.
Either encourage or enforce role playing (to
benefit immersion and escapism), or admit that users will socialise
out-of-context and add more NPCs (with in-context conversations) to repair some
of the lost immersion.
9.
Have the NPCs manipulate the players into
role playing.
Would
enough explorers be attracted to a virtual world even with the above changes?
I'm not sure.
From
my perspective there isn't much of an alternative. Achievers, killers, and
socialisers are having their needs catered to by 100+ MMORPGs and 1500+ MUDs.
The explorers are left out in the cold.
Interesting
links...
Thanks
to...
Thanks
to the following people for reviewing and commenting on this awfully long
document. (Said following people do not necessarily agree with all or any of my
conclusions. You can read their responses in rec.arts.int-fiction, under the
"Thought-experiments about the failure of Uru Live" thread begun
5-May-2004, and "Design: Online adventure games" in https://www.kanga.nu/lists/listinfo/mud-dev.)
1 June 2004
by Mike Rozak
Computer
generated virtual realities seem to have a dilemma which has been bothering me
for some time: If you
allow players to do anything they want, the world will be devoid of story and
plot, feeling intellectually empty. If you enforce a story and plot, players
will feel trapped.
I
have a potential solution to this problem, which I'll get to later. First, let
me build a foundation on which to base my solution.
Space-time
- It's not just a tree
As
everyone who has watched Star Trek (or any science fiction) knows, contemporary
space-time theories propose that time is not linear, but a series of branches.
A branch occurs whenever a decision is made, such as flipping a coin. If the
coin comes up heads, one reality comes into existence. If tails, another. Every
time a new decision is made a new branch appears, ad infinitum. (And as anyone
who watches Star Trek knows, one of the branches includes an evil Captain
Picard.)
I
subscribe to this somewhat... The problem with the theory is that there are an
infinite number of decisions happening all of the time (not just when coins are
flipped), and most of the decisions result in more-or-less the same reality.
For
example, if I were to flip a coin and then put it in my pocket without looking
at it the result of the flip wouldn't change the future detectably either way.
There might be some subtle changes, but they would be very minor. Under such
circumstances I wouldn't view this as a branch, more as a splitting of the
timeline and then rejoining. Although, the two timelines (heads or tails) might
not strictly rejoin, but they'd be so close together they could be thought of
as the same. To use another Star Trek example, if a mad scientist were to knock
me from the heads reality to the tails reality, I'd never notice the realities
change. (Maybe this is why pens and keys always go missing.)
So,
instead of the timeline being a pure tree, it allows realities to reconnect.
Technically this is called a "rhizome".
My
other contention is that decisions are made all the time. In some realities I
may have flipped the coin 1 microsecond earlier. Furthermore, the probability
of some events is more likely than others. The infinite possibilities change
the nature of the rhizome. The branches of rhizome become fuzzy from subtle
variations of the same reality, and vary in brightness due to the probabilities
of a path being taken.
In
fact, the tree isn't really a rhizome, it's an infinitely dimensional space
(which I'll discuss shortly) filled with varying densities. Most of the space
is empty, or nearly empty. Thinking in 3 dimensions, it looks mostly like a
rhizome (or cob-webs), except that a real rhizome (or cob-web) can't get thin
enough to represent the really unlikely events.
Space-time
- Infinitely dimensional
Contemporary
thought assumes that space is three dimensional and time is one dimensional
(except for the alternate realities part, which is someplace between one and
two dimensions - called a fractal.)
In
simplified terms, the three dimensions of space define a relation between
objects (molecules, atoms, etc.). This is called a "spatial"
relationship, since it says how far apart two objects are, along with their
directions.
Other
relationships exist between objects, such as whether object A has ever been
"near" object B. Or, to make a more extreme example, whether object A
(let's call it Frank) is in love with object B (Jenny).
Interestingly,
while physics are interested in special relationships between objects, stories
are interested in less quantifiable relationships (like love). I'll discuss
this in a bit.
There
are practically an infinite number of ways that any objects can be related
since a new type of relationship (distance, love, colour, speed, etc.) can be
invented at any moment. To compound this, in a universe there are practically
infinite number of objects
You
can use this (practically) infinite number of relationships to create an
infinitely dimensional space, using one dimension per relationship. A snapshot
of the universe can be boiled down to one point in this infinitely dimensional
description of the universe.
In
the likely event that you can't imagine infinitely dimensional space, just
pretend it's a 3d-dimensional volume in the shape of a cube. One axis is
"How much Frank loves Jenny", the other is "How far away Frank
is from Jenny", and the third is "Time." You can use a point to
represent the current state of the universe at any time.
Now,
imagine that Frank is sitting in his room at 1 PM, at which point he doesn't
love Jenny at all. The state of the universe is (0 love, 1 km, 1 PM). He spends
half an hour walking to Jenny so he can talk to her. If you trace the point it
will create a line from (0 love, 1 km, 1 PM) to (0 love, 0 km, 1:30 PM). Frank
then spends half an hour talking to Jenny, and falling in love. The line now
traces to (1 love, 0 km, 2:00 PM). Frank walks back to his room over the next
half hour, tracing the line to (1 love, 1 km, 2:30 PM).
What
I have just described to you is a "narrative", a linear description
of what happened (or what will happen).
If
Frank (and Jenny) have free will then in comes coin flipping (and probability).
Maybe Frank decides to stay in his room between 1:00 PM and 2:30 PM, never
talking to Jenny. You could add another line to the universe, running from (0
love, 1 km, 1:00 PM) to (0 love, 1 km, 2:30PM). Or, Frank may spontaneously
fall in love with Jenny even though he never meets here, creating a line from
(0 love, 1 km, 1:00 PM) to (1 love, 1 km, 2:30 PM). This scenario is extremely
unlikely, so create only a very dim line.
Note
that Frank cannot suddenly teleport to Jenny, so there is no line from (0 love,
1 km, 1:00 PM) to (0 love, 0 km, 1:01 PM). This is called "physics";
there are just certain things that can't be done in a universe, or at least
which are extremely unlikely. (The evil Captain Picard could suddenly get the
urge to teleport Frank to Jenny though.)
Repeating
the process produces another rhizome, with paths (in infinitely dimensional
space) connecting possible realities. (While this description of the Frank's
universe is uninteresting, it simplifies the infinitely dimensional universe
down enough that the Human mind can imagine it.)
So
what does all this mean?
1.
A narrative (linear description of what
happened or will happen) is produced by picking a starting point on the rhizome
and following it, usually through positive time.
2.
A virtual reality (freedom of choice) exists
when the user can choose which paths of the rhizome to follow. Usually, the
user will be forced to follow positive time, but in virtual reality, going back
in time to alternate realities is also possible.
3.
The holes in the rhizome are known as the
"physics" (or logic, self-consistency, etc.) of the world. Some states
cannot exist.
Stories
and space time
Infinitely
dimensional universes are all fun and games (until someone loses their mind),
but what does this have to do with story and plot vs. freedom in virtual
worlds?
As
a stated above, a narrative is what happens when you follow a path in the
rhizome.
Of
course, given a universe, there are an infinite number of narratives because
you can traverse the rhizome in an infinite number of ways. Most of these are
not well suited for a story. Either they are boring (see "An evolutionary explanation for
entertainment" for my definition of boring) or they
lack plot (discussion follows).
What
is a narrative's plot? If you ask 100 people you'll get about 200 different
answers.
Here
are my two answers:
1.
A narrative with a plot is one in which all
of the elements of the narrative lead in the same direction. Conversely, a
narrative without a plot has elements that wander around aimlessly.
2.
Or, for a stricter definition: A plot is the
shortest path between the starting state of the narrative (in the infinitely
dimensional rhizome space) and the ending state.
Since
definition 1 is completely vague and fuzzy, I'll expand on definition 2 a bit.
For
some reason, people like to feel that there's a purpose to life, and hence,
everything that happens (or at least most things) somehow leads towards that
purpose. When we reach the purpose, the story should end. If elements of
narrative do not serve to reach the purpose, they're seen as distracting and
uninteresting.
The
most purposeful movement is a straight line. In this case, from the point
representing the start of the story to the point representing the end of the
story. This has two problems:
1.
It would probably make for a short and
very boring narrative. Not only is a straight line is the
shortest (and quickest) distance between two points, but travelling in a
straight line is pretty boring. (How many people take scenic drives on long
straight roads? They usually take the windy ones.)
2.
The physics of the universe won't
allow it. For example: Perhaps the "plot" of
the story is for Jerry to fall in love with Susan. The straightest line is from
(0 love, 1 km, 1:00 PM) to (1 love, 1 km, 2:30 PM). While this is possible, it
is highly improbable.
Since
the narrative's path cannot go in a straight line, it must wander a bit in
order to make for a longer and more interesting narrative, and to take the most
probabilistic route that gets the the end. (If an author takes an improbable
rout, such as teleporting Jerry directly to Susan, the readers will be very
angry because the author has just broken the laws of physics. Or at least the
physics that the readers think the story is using.)
Conversely,
if the path wanders to far away from the straight line, this also bothers
readers.
For
example: If Jerry, for no particular reason, first took a short walk away from
Jenny, this would disrupt the plot because it lengthens the path though the
rhizome even though the physics of the universe would allow for a shorter path.
The
reader's need for entertainment (interest) is at odds with the plot. The plot
wants to get from point A to B as quickly as possible (assuming it doesn't
break the physics of the universe). The reader's desire for entertainment wants
a car chase and a fight scene thrown in.
A
traditional author's job is to find the
best narrative, the one which is most interesting and with the best plot.
(They need to do lots of other stuff too, such as inventing the universe and
actually writing the words.) Obviously, this is a difficult job because there
are so many possible narratives, 99.999% of which are either boring or have a
lousy plot.
Virtual
reality freedoms
A
virtual reality uses the same universe as the author, but instead of the author
choosing the narrative, the player does.
The
good thing about letting a player choose his/her path along the rhizome is that
the choice is itself interesting, improving the player's experience.
The
bad thing is that the player will inevitably choose the paths that lead to
uninteresting narrative or narrative that veers away from the plot. They choose
the wrong paths, not because they want to be bored, but because they don't have
a complete vision of the world and its physics. Since they can't accurately
predict what will happen if they take an action, they can't predict if it will
result in an good narrative. And if they could predict which path was best, it
wouldn't make for a very interesting experience since they would already know
what will happen.
This
poses a bit of a problem. Free choice makes the experience more interesting,
but it is more likely to destroy the experience because the player will make
all of the wrong choices.
Let
me abandon Frank and Jenny, and go to a rich world that everyone knows about,
Tolkien's Middle Earth. What would happen if the player were to take on Frodo's
role? What would the player do?
Here's
an extreme example: The scene is just after Gandalf tells Frodo that his ring
is the one ring. Frodo offers the ring to Gandalf, but then Gandalf refuses. So
what does the player, as Frodo, do? Muster up his bravery and offer to take the
ring to Mordor? Maybe, but...
At
least one player will flush the ring down the toilet. After all, if the wring
wraiths can't find the ring then Sauron won't do as well in his battles.
Unfortunately, this makes for a rather poor story, both uninteresting and one
that veers away from the plot. (Which is the destruction of Sauron and the
writing of another tail for Middle Earth's history.)
I
think most people would agree that flushing the ring is a bad idea, but what
are the solutions to this problem:
Of
all these solutions, taking a detour in the plot and redirecting the plot are
most satisfying for the player because they don't break the physics of the
universe, still result in the narrative having a plot, and don't constrain the
player's reactions.
Both
solutions are both very difficult, time-consuming, and basically impossible.
Kind of... they're impossible for a pre-programmed virtual world. They're
entirely possible if a human is monitoring the player's actions and tweaking
the world in response.
The
easiest programming/authoring solutions to the freedom problem either remove
too many freedoms from the player, or result in an unsatisfying story.
A
virtual world without interest or plot
Since
maintaining plot and user's interest in a virtual world is extremely difficult,
some categories of virtual worlds have found a way to be plot-less and
uninteresting. These include virtual chats, computer RPGs, and MMORPGs.
Well,
perhaps I'm being a bit harsh. They may have some plot and some areas of
interest.
Virtual chats let are basically
virtual worlds in which a player can wander around, build scenery, and talk to
other players. What makes them interesting is the socialisation and the
creativity of building. There is no overall plot, although individuals may have
their own self-appointed sub-plots, such as trying to gain social dominance.
Computer RPGs are virtual worlds
with a bit of plot (the player must kill the bad guy) and a very complex
physics engine. A traditional story makes itself interesting by providing
intricate webs of interactions (and relationships) between the characters and
the world. The complex relationships allow for a complex rhizome, maximising
the chance of creating an interesting and plot-filled narrative. This is far
too difficult to program. Therefore, a RPG only allows for limited types of
interactions between the player and computer-controlled characters, namely
killing them. RPGs do provide thousands of different ways to kill though, which
is part of what makes them interesting.
MMORPGs are a combination
of a virtual chat and a computer RPG. MMORPGs find a plot virtually impossible
to implement because not only is the player making choices that work against
the plot, but there are thousands of them. After all, only one player can
posses Sauron's ring.
These
virtual worlds do exist without a plot and very little of interest. They
survive because:
1.
They provide a challenge (killing monsters,
find treasure, and going up levels).
2.
They include socialisation.
3.
The act of exploring the word, and the rules
the govern the world, is in itself interesting. (At least to me.)
But,
they are time wasters. Whenever I finish playing one I don't feel as though
I've experienced anything profound or learned anything. (Which is partly
because they don't include a plot.)
In
the mid 1980's I ran an "Adventure BBS", which is basically an early
version of a MMORPG. My experience with it went as follows:
1.
I first implemented a traditional BBS, which
just allows people to chat. (Aka: Virtual chat).
2.
I decided to add the adventure part (RPG)
because the programming interested me, and I thought people would like more
types of interaction than just chatting. (Aka: Virtual chat + RPG = MMORPG)
3.
I created some content in the form of
monsters and dungeons for players to explore.
4.
Players quickly worked their way through my
content.
5.
The players then used the chat functionality
to whinge about the lack of content.
Overall,
it was not a successful endeavour. (I supposed that the RPG content did provide
more reasons to use the BBS though.) A good part of it was my fault, but some
of the fault is inherent in the system.
RPGs
(and MMORPG) rely heavily on automatic content generation. This is often why
they become uninteresting.
Human-generated
vs. automatic content
One
aspect of narrative that I haven't explicitly talked about is
"content". In general terms, it's what makes a book or virtual
reality interesting, since it's the human contribution to the complexity of the
world.
The
content includes all the artwork, characters, objects, history, etc. It also
includes the software, since software is human generated and can apply to the
story, although people do not usually think of the software as content.
Basically, it includes everything, except the mechanism used to express the
world to the reader.
As
a general rule, the more content the more an interactive fiction will hold the
reader's interest. When the content runs out (has all been viewed or
understood) by the reader the interest in the interactive fiction generally
ends. Of course, not all content is created the same. Some people are very
skilled in content creation and can do much with a paragraph, image, 3d model,
or line of code than others.
In
terms of the rhizome, the content is the collection of relationships
(dimensions) and how the rhizome is arranged to maximise reader interest and
sense of plot. How the world is expressed to the reader (though text or
graphics) is not part of the content, although without it the content would not
be accessible. (A book, by itself, is not interesting. The meaning of the words
contained within the book is the interesting part.)
Why
one rhizome is interesting and the other not is largely a human issue. That's
why humans design the rhizome, and why it cannot be automatically generated.
My
last statement is somewhat erroneous. Parts of the rhizome can and are
automatically generated. For example, the physics of a virtual world is usually
software, although the rules for the physics are human generated. Likewise,
elements of the universe that are not important individually, but which may be
important as a whole, do include some automatic generation. This includes
details of landscaping and foliage placement. Many systems automatically
generate characters. RPGs and MMORPG do this to create an unending supply of
monsters to kill.
Automatically
generated content has a problem though:
Automated content is only as interesting as the work and
thought involved in producing the code that automates it. Why? Because once a
player has intuitively determined the underlying automation algorithm the
system becomes predictable (even though it may be random) and is no longer
interesting.
This
statement is not true if the player becomes obsessed with the challenge of
beating the system. For example: In RPGs/MMORPG the generation of monsters is
obviously automated, yet some people will go on being entertained by them not
because the automatically generated monsters are entertaining, but because the
goal (high level/status) is so desirable.
Theoretically,
an automated system could be build that's complicated enough to generate
interesting content. This hasn't yet been done.
If
it could be done, an automated system could solve some of the problems
resulting from freedom in interactive fiction. One reason why freedom-of-choice
is very difficult for an IF author is that the player can and will do anything,
or go anywhere. When the player comes to the end of the content, the author is
forced to create some sort of barrier. Ultimately, this breaks the physics of
the universe and makes the player feel walled in.
One
obvious use for automatic content creation is the edge of the world. Many
authors simply put an impenetrable barrier (mountains) at the edge of the
world, informing the player that they can't go any further. If the world is
deemed large enough (by the player) then this isn't much of a problem. Unfortunately,
creating a sufficiently large world requires a lot of content, even if much of
the content doesn't pertain to the plot.
Automatic
landscape generation can be used to easily and quickly create a sufficiently
large world, or even to extend a world during game play. Because it's
automated, the content isn't very interesting, but it's either uninteresting
content or wall in the player.
The
concept of automatic landscape generation could also be extended to include
automatic city generation, along with the city's inhabitants.
Going
even further, the universe could all be automatically generated and then
peppered with human-generated content that then becomes the thrust of the
narrative. If the player choses to avoid the human-generated content (and hence
human-generated plot) then that's up to them.
The
automatic content could be guided by some broad human strokes. Parts of the
world could be marked as "swamp" and others as "mountains",
letting the automated systems fill in the details.
Much
of automation is a holy grail, and although desirable, largely unattainable.
"ThePlot"
object... or a virtual God
Automated
content can theoretically form an infinitely large universe that is at least
somewhat interesting. If the algorithms are complex enough it will take a
player a long time to intuitively figure out how they work. However, automated
content generation does not lend itself well to a plot. The automated worlds
that I have experience have been largely plot free.
A
human mind is the best way to achieve a plot. Unfortunately, players have a way
of messing up the plot (such as flushing the one ring down the toilet).
Can
plot be automatically generated?
Some
MMORPG have sub-plots called "quests". These are fairly simple goals
for the player to achieve, such as recurring someone's cat, killing a troll
that's charging people to cross a bridge, etc. A few even include automatically
generated plots. I haven't been too impressed with the automatically generated
plots, but the idea has potential.
Basically,
an automatic plot generated modifies the content to create a plot. The system
finds a helpless character that's standing next to a tree. It then places a cat
in the tree and implants and idea into the character's mind that the cat is the
character's own and needs to be rescued. The same concept can be used for the
troll in the bridge.
Note
that while the specifics of the plot are automatic, the code that generates the
plots is written by a human. The plots are archetypal - with maybe only 20-50
types of plots available, but because bits and pieces are varied all the time
they come in infinite varieties.
As
described, it's easy to imagine the software that could create quests. These
are only simple plots though. How could a much larger plot be created automatically?
I'm
not really sure, but here's an idea.
When
an author creates IF he/she creates code for all the objects (people, places,
things) in the world. Each object has code associated with its own behaviour.
An
author could also create a "ThePlot" object, or to be blasphemous,
"God". The purpose of "ThePlot" object is to monitor what's
going on in the world and make it as interesting and purposeful as possible for
the player.
Using
the Tolkien example, "ThePlot" would notice that the player had
flushed the one ring down the toilet. Realising this, it would somehow modify
the content (usually in subtle ways) so that either the player will eventually
recover the ring, or play can continue without the ring.
Likewise,
"ThePlot" could identify that the player has become bored (not
playing the game for awhile) or stuck, and subtly modify the content to make
things more interesting.
Sounds
good? I haven't the faintest clue how to do it.
Reality?
Here's
a deeply philosophical thought for you: A virtual world is, in part, a
simulation of the real world. If automatic content generation and
"ThePlot" need to exist for a virtual world to be successful, do they
also exist for the real world?
1 June 2004
by Mike Rozak
Recently
I've been pondering the following question: Why aren't text adventures commercially viable? The
obvious answer is that they use outdated technology, have no graphics, and
hence are like watching a black-and-white 50's TV show on a high-definition TV
with surround sound. This is true to an extent, but it isn't the whole
answer... Many people still watch 50's TV shows despite their colourlessness.
Not nearly as many people play old text adventure games.
This
paper discusses some of the reasons why text adventures are no longer
commercially viable, and what lessons can be learned.
History
of adventure games
First,
let me provide a very brief history of adventure games:
|
Late 1970's |
The first adventure game,
Adventure, was created by Will Crowther in 1975. It was fairly similar to
what is now referred to as a "text adventure" except that it had a
simpler parser, no plot and only a few puzzles, and it required a very
expensive computer to run. |
|
Early 1980's |
The early 1980's were the
golden age of text adventure. During this time the classic Infocom games such
as Zork, Deadline, Trinity, and The Hitchhiker's Guide to the Galaxy were
produced. A
second form of text-adventure began to appear around this time, the
multi-user dungeon (MUD). Roy Trubshaw and Richard Bartle wrote the first
on-line text-adventure. Interestingly, the on-line adventure transformed into
a completely different beast, but that is another tale. See http://www.mxac.com.au/drt/TroubleWithExplorers.htm
for more details. |
|
Late 1980's |
By the mid-80's every PC
manufacturer provided graphics, and adventure games took advantage of them.
The later part of the 1980's saw the rise of the graphical adventure and fall
of the text adventure. One
form of graphical adventure was pioneered by "Mystery House", which
was basically a text adventure with static 1st-person graphics (mostly stick
figures) bolted on. Because the graphics occupied so much of the screen the
text elements were cut back to only a few lines. The parser was inferior to
Infocom's. The
second form was popularised by the King's Quest series. The screen was filled
with a static background-image of the room, and the player was allowed to
move a sprite-based character around the room. Text was still shown on the
screen, but it was secondary to the image. |
|
Early 1990's |
Commercial text adventures
completely disappeared. First person graphical adventure games faded, while
third person games (King's Quest) dominated the market. Graphics improved,
but little else. |
|
Late 1990's |
1995 was an important year
for adventure games with the introduction of Myst. Myst returned to the 1st
person view with stunning pre-rendered 3d-graphics and sounds. It excluded
all text except for diary entries. Commands were no longer typed, but instead
a single click of the mouse appropriately manipulated the object it was
clicked on. Parsers no longer existed. The
late 1990's also saw a short-lived trend in adventure games where a game was
constructed from a series of short video clips. As in Myst, user input
consisted of mouse clicks. The most famous video adventure of the time was
Phantasmagoria. Third-person
graphical adventure games seemed to fade away. |
|
Early 2000's |
2000-2004 has provided a
few styles of adventure games: First-person
games like Myst are still around, although not doing terribly well. Third-person
games derived from King's Quest are once again a successful category. The
backgrounds are pre-rendered 3D images, while the characters are rendered
real-time with 3d accelerators. The most popular such game is Syberia. First-person
3D accelerated adventure games, such as Uru, are just beginning to appear.
All graphics are rendered real-time using the 3D graphics accelerator. Users
move around with keystrokes and can manipulate the world by clicking on
objects. |
So
why do text adventures matter at all? Aren't they the deceased ancestors of
modern graphical adventure games.
Yes.
And,
no.
While
graphics have greatly improved over the last two decades, adventure games have
lost some important properties as a result of losing language (text being the
means to convey the language):
This
loss of capabilities bothers me. Stunning graphics are certainly a good thing,
but I get the uneasy feeling that the genre has gone backwards. (It's not just
adventure games. Other computer-game genres have followed a similar path. The
fundamentals of CRPGs haven't changed much since Wizardry, also from the early
1980's.) I wonder if this is how the Romans felt as their empire crumbled and
civilisation decayed into the dark ages.
A
fresh look at text adventures
While
I was pondering these thoughts, I decided to take a look at old (1980's) text
adventures and new text IF to see if my memory of them wasn't overly rosy. I
hadn't played a text adventure game for 15 years.
I
downloaded and installed a few of these games only a few minutes. (I remember
when 150 KB took hours to download at 300 baud.) I excitedly ran the game and
was...
Well,
I was bored.
My
memory of text adventure games was a bit distorted, and I had gotten used to
all the snazzy graphics and sounds of modern adventures. The sophistication of
the parser, and the "depth" of the text adventures did impress me
though.
Here's
a summary of my thoughts about text adventures as seen through my 2004 eyes:
1.
I have become used to graphics and sound,
just as I am used to colour on my television. Going back to mere text and utter
silence felt like a step backwards; maybe my attention span has shortened and
I'm addicted to the stimulation.
2.
Reading text off a computer monitor is
difficult. As I recall, it was easier in the early 1980's because I used a
green-screen monitor (as did most people). Green (or orange) screen monitors
use a slower phosphor than televisions, which means that there's no flicker.
(They sucked for fast-moving games though because the image would stay ghosted
for half a second after it had moved on.) Modern monitors flicker at 60-80 Hz,
which still makes text difficult to read. Even with a slower flat-panel display
some flicker is noticeable.
The
other (minor) issue is that the text adventures I tried defaulted to small type
and only occupied a small portion of my 1024x768 screen. Text adventures in the
1980's used larger type and occupied the entire screen, but that was all they
had. Large type is easier to read, and it seems silly for text IF to default to
a small font.
3.
I expected to be able to use my mouse. The
older games didn't allow this, but surprisingly many new IF titles do.
4.
Early text adventure games required that the
user create a paper map of the environment. Creating a map, in my opinion, is
drudgery. Early games couldn't create a map because they didn't have graphics,
but I expected the modern IF titles to have automatic mapping features. I
didn't find any.
5.
As a said earlier, the parser and depth of
the worlds impressed me, although I still ran into "guess the verb"
problems that my memory had neglected to recall with its nostalgia.
6.
The new IF was much more experimental (and
intellectually interesting) than the older commercial work. I suspect this is
because IF has been taken over by intellectual hobbyists who don't cater to the
masses.
These
are just my observations. Many people enjoy playing text IF.
The
larger question remains... Why aren't text adventures commercially viable? Here
are some hypothesis:
Reason:
Shrinking market for adventure games
While
the number of computer users has increased by several orders of magnitude since
the early 1980's (when text adventures were popular), the market share for
adventure games has gone down. They are now a minor genre (only 1%-2% of the
market), and are dwarfed by the market for first-person shooters, sports games,
role playing games, and the sims. Occasional successes, like Myst, do arise,
but the category does not dominate like it used to.
Why
is this?
1.
Only certain personality types are attracted
to adventure games, namely those people that like to solve problems and
"explore" new ideas. In the early 1980's, people that purchased
computers were oddballs, spending a thousand (or more) dollars on very
expensive calculator. I suspect that the same personality traits that attracted
the early-adopter computer users were also the same traits that attract people
to adventure games.
Nowadays
half the population owns the computers. While the "oddballs" still
exist, they're a smaller percentage of the computing population.
2.
In the early 1980's, there were half a dozen
major operating systems and no standardisation for graphics (if the computer
even had graphics). Text adventure games had the advantage in this environment
because they were easy to port to different operating systems; graphics-based
software, on the other hand, was difficult to impossible to port. A text
adventure therefore worked on more platforms and had a much larger market
share, making them more profitable.
3.
Many computers did not have graphics
capabilities at all. Game players were left will little other choice than text
adventure games.
Because
adventure games only appeal to a small segment of the overall population, they
will only ever be a niche market. First-person shooters, sports games, and
others less intellectual games will dominate.
Reason:
Old technology
As
I said earlier, running a text adventure on a computer that's capable of fancy
3D graphics, sound, and Internet communications feels like a waste. Having said
that, I still occasionally watch black-and-white movies on my colour
television. User's feeling like they're wasting technology is probably only a
small part of the text-adventure's demise.
I
suspect that a lot of what drives games sales is new technology. People like
games that use the latest and greatest technology. If you look at the history
of games you'll see that those games that use the latest technology at the knee
of the technology adoption curve are major successes. If a game uses a
technology before it's widely available, such as Ultima IX's use of 3D, it will
fail. If it waits until the technology is established, another game will have
become wildly successful instead.
Here
are some examples:
Text
adventures are not commercially viable any more not just because they fail to
use 3D graphics, sound, and CD-ROMs, but because they don't use the latest and
greatest technology. (This is also a strength, because using the latest
technology increases development costs, which would change the nature of text
IF.)
Following
this line of thought though, one can predict that the next big gaming hits will
use the next big technologies:
Adventure
games could take advantage of most of these future technologies. Luckily, only
the 3D modelling aspects are development syncs. Some technologies, such as DVDs
and speech recognition, are particularly useful to an adventure game genre.
(Interestingly, speech recognition is less useful for other genres, although
one first-person shooter has tried to incorporate it.)
Reason:
People aren't willing to pay for text adventures
Even
if people are willing to play a text adventure, they aren't wiling to pay for
it. There are a few reasons for this:
1.
Text adventures are obviously old technology, so
they're not worth as much to people. DVDs of black-and-white classics sell for
less than modern blockbusters. Even the distribution medium matters: A DVD
movie sells more than a video tape of the movie (old technology), which sells
for more than the paperback book of the movie (older technology).
Interestingly, the DVD is the cheapest media to manufacture.
2.
People often value objects by size. Have you ever
noticed how software is sold in large boxes that contain nothing but air, a
registration card, and a CD-case? Text adventures are "small".
Thousands of them can be fit onto a single CD-ROM. Contemporary graphical
adventure games take at least four CD-ROMs. One that required four DVDs would
be even more "valuable". Because text adventures are so small they
must not be worth as much money as a graphical adventure that requires four
CD-ROMs.
3.
Because of their small size, text adventures
are distributed on the Internet. When someone "buys" software over
the Internet, they hand over their credit card number (which people don't like
doing over the Internet) and receives a E-mail with a password. That's it. No
box, no CD-ROM, no map, no peril sensitive sunglasses, nothing. People like to
receive tangible objects
for their purchases.
4.
Why pay money for a text adventure when so
many free ones
(many of them good) can be downloaded from the web?
Reason:
Others
Some
other factors also hurt text adventures:
Conclusion
As
you already knew at the beginning, text adventure games are not commercially
viable. My examination of the problem merely enforces the conviction.
However,
that doesn't mean that that interactive fiction isn't viable. It does need to
change, however, if it's going to be anything more than freeware:
(Or... What I did
on my holiday)
25 June 2004
by Mike Rozak
I
spent the last few weeks acting as tour guide for relatives from the US,
showing them around northern Australia. In my spare time (the few wakeful
moments that I lay in bed before I fell asleep) I puzzled about the nature of
virtual worlds and how users might like to experience them. Naturally, as I
drifted off to sleep, I combined the two thoughts and considered the similarities between holidays and
virtual worlds.
Quite
a few similarities exist. After all, virtual worlds are places that people
"escape" to, and holidays are trips to exotic places, usually an
attempt to escape from the stresses and dullness of ordinary life. Of course, a
virtual world is not entirely the same as a holiday in an exotic location, but
I'll get to those issues later.
By
the way, don't take this write-up too seriously.
Let
me enumerate the "elements" of a holiday and how they are similar to
a virtual world:
Reasons
for going on a holiday:
How
people get to their destination:
What
tourists do on the holiday:
Holidays
also include some experiences that aren't so easily classified:
Of
course, virtual worlds differ from real-world holidays in a number of important
aspects:
Does
this comparison amount to anything? Maybe. Maybe not. It does provide a
different perspective though, one that I wouldn't take too seriously.
6 July 2004
by Mike Rozak
This
morning I awoke with another "angle" on virtual worlds spinning
through my head. It's an obvious one that any Hollywood script-writer would
know, but it took some time for me to get my brain around to it...
People play virtual worlds to do or
experience things they can't in real life.
The
reasoning for this is fairly simple: If they could experience it (whatever that
may be) in real life, they would; real life provides a more rewarding experience.
Virtual worlds are merely a backup mechanism for experiences that can't be had
in reality.
With
this thought in mind, I started listing off the impossible things that players
in contemporary MMORPGs can do or experience:
Interestingly,
most of the "impossible" things that MMORPGs allow their players to
do are actually possible in the real world. They just require the person to be
an adult with lots of money, and in the case of illegal acts, an adult with
enough money to hire a very good lawyer. According to this list, the
demographic most attracted to MMORPGs would be teenagers, or adults without
much money. Real-world statistics say it's about half teenagers and half
20-somethings.
My
list is incomplete though. Virtual worlds (not just contemporary MMORPGs) allow
players to do or experience other impossible activities. MMORPGs haven't
catered to these activities because their designers haven't thought of them
(unlikely), or because the demographic is too small.
Of
course, both my lists are incomplete. You should be able to add dozens more
impossibilities.
20 August 2004
Revised 7 September
2004
by Mike Rozak
For
awhile I've been pondering the spectrum of story-like entertainments that can
be produced on a computer. The most obvious story-like entertainment is, of
course, a story. Computers can display stories as either E-books or videos.
Neither of these take full advantage of the computer's abilities since they
don't support interactivity (the ability for players to affect the outcome of
the story) or multiple users (using the Internet to place several players
within the story). Adding both interactivity and multiple users to a story
turns it into a completely different beast: a virtual world.
What
lies in between a story and a virtual world?
To
better understand what happens as each technology is added, I decided to do a
thought experiment, beginning with a story, and adding interactivity and
multiple users in different steps. After adding the technologies, I pause and
see what's happened to the entertainment. Each technology changes the
experience, with obvious improvements as well as newly introduced problems,
which I have enumerated for each step.
Step
0: A linear narrative (stories) vs. the real world (0 players)
I'll
begin with a novel or movie, a form which everyone is familiar with. Novels and
movies often take place in imaginary worlds, following a handful of characters
(controlled by the author) through the world. The world, characters, and their
actions are designed by the author to maximise the entertainment value. Most
people wouldn't call novels or movies virtual worlds though.
Both
novels and movies can be displayed in a computer, novels turning into E-books,
and movies into video. (Note: If the movie's computer graphics are generated
real time and played from within a game, the movie is called a cut-scene.)
My
first transition is from the real world to a story. Why would anyone give up
the real world to experience a story?
|
Advantages of a story
(compared to the real world) |
Disadvantages of story
(compared to the real world) |
|
|
Step
0.5: A rail-game vs. a linear narrative (1/2 of a player)
In
a "rail game" the player can affect the narrative in very limited
ways. Usually this is a choice of camera location, which character will be
followed, optional sub-stories, and the occasional "make the right
decision or the story ends here." Some rail games allow for a handful of
story endings. Adventure games are often rail games. The Dragon Slayer
laser-disc game from the 1980's was a rail game. Choose-your-own-adventure(tm)
books are rail games.
|
Advantages of a rail game
(compared to a story) |
Disadvantages of a rail
game (compared to a story) |
|
|
Step
1: Adding single-player interactivity to a rail game
Adding
more interactivity to a rail game allows the player to take full control of a
character in the author's world, instead of just occasional control. Of course,
due to technological and simulated limitations, players never have complete
control of their characters.
Most
"computer games" fall under this category.
|
Advantages of interactivity |
Disadvantages of
interactivity |
|
|
Step
1b: Adding a sustained Internet connection
To
produce a multi-player game, we need to add an internet connection to a single
player game. Single-player Internet games do not exist (except in rare
circumstances) because players don't get any benefit from the combination.
Therefore, the configuration is unstable.
However,
separating the addition of Internet from adding more players makes things a bit
clearer.
|
Advantages of a sustained
Internet connection |
Disadvantages of a
sustained Internet connection |
|
|
Step
2: Supporting 10 players in one world
The
next step in the story-to-VW spectrum is to support a small group of players
(approximately 10) in a world. Being such a small group, they'll know one
another well, probably in the real world too.
Neverwinter
Nights is a CRPG that supports small groups of players. Face-to-face RPGs like
Dungeons & Dragons are also based upon a small number of players.
|
Advantages of 10 players |
Disadvantages of 10
players |
|
|
10-player
games can be designed so they can be experienced in single-player mode,
although the content design is then torn between a single-player and a
team-oriented experience.
Step
3: Supporting 1000 players in one world
If
the number of players is increased to 1000, the dynamics of the game changes
dramatically. The world changes from a cozy outing with friends into a world
populated by mostly strangers, some of them enemies.
|
Advantages of 1000's of
players |
Disadvantages 1000's of
players |
|
|
1000-player
virtual worlds can include design elements intended for single-player or
team-playing, but these are never as strong as a design specifically for
single-player or team-playing.
However,
with "private regions" and other insulating techniques, it is
possible to make 10-player worlds, single-player worlds, and pure stories
within the larger 1000-player virtual world. At that point, does the large
virtual world become merely a way to access the private regions?
Step
4: Supporting 1,000,000 players in one world
Increasing
the number of players to 1,000,000 produces further ramifications, only a few
of which can be guessed, since no virtual world is even close to 1M
simultaneous players. This section is almost pure speculation.
|
Advantages of 1M of
players |
Disadvantages 1M of
players |
|
|
Sub-worlds
Because
a world designed for N players has flaws, designers will inevitably design in
sub-worlds to handle smaller groups of players. These sub-worlds can take two
forms: Those weakly separated from the main world, and those strongly
separated.
A
weakly separated sub-world allows uninvited players to enter. Quests, for
example, are weakly separated sub-worlds since the quest is a
"sub-world" targeted at 1-10 players; only they can
"complete" the quest. However, the quest takes place in the same
world as all the other players.
A
strongly separated sub-world only allows invited players to enter. Private
regions/dungeons provide for this. 1-10 players get together and have a private
area of the world create. Only they can enter it. Once there, they are separate
from the rest of the world.
Conclusion
The
transition from a linear narrative to a full-blown virtual world is an
interesting one. Each stage of the cycle is different enough from the others to
allow for a unique experience.
I
have made a small Microsoft Excel spreadsheet that graphically displays what
each "step" is best at handling. It's in VirtualWorldSpectrum.xls.
Of course, the numbers are only guesstimates. The important information in the
graphs are the trends; some world sizes are better for some types of
experience.
5 September 2004
by Mike Rozak
In
one of my previous articles I mentioned that I thought of virtual worlds as platforms. I thought
I'd go into more detail:
Imagine
creating a virtual world... some place with lush scenery that your character
can walk around in. You spend all your time walking around looking at the
picturesque trees and impossibly tall mountains. And when you get bored of
that, your character finds other places to wander around in. And when you get
bored of that?
Might
as well at chat... You are no longer wandering around the world alone. You can
see other people in the world and talk/type to them. You can even animate your
character with emotes. This will keep you occupied for a bit longer, but
eventually you will get bored. Now what?
How
about providing some games to play with other people? Maybe allow people to
fight, fly planes, or play virtual capture the flag... Will this keep you
interested? Yes, for awhile, but even this will get boring.
Continue
ad infinitum...
Virtual
worlds by themselves quickly get boring. To make them interesting developers
add sub-games and activities
to them. The world is continually changing and growing (in size and number of
sub-games and activities) to prevent players from getting bored and leaving.
Contemporary
virtual worlds are almost all based off one major sub-game, the CRPG (like
Morrowind or Neverwinter Nights). A few are based upon vehicle simulations,
such as space combat. One, Star Wars Galaxies, even combines both into the same
world. I predict that over time more and more virtual worlds will be based off
several sub-games, just as Star Wars Galaxies is.
What
sub-games can a virtual world include?
Single-player
sub-games
Here
are some single-player sub-games that can be added to a virtual world: (Note:
Single-player sub-games are not ideal virtual world material because they don't
encourage people to play together, see "What makes a good sub-game?")
Sub-games
for one to a few players
Some
sub-games can either be played alone, or with a small group:
Sub-games
for a few players
Some
sub-games require a small group of people:
Sub-games
for many players
Some
sub-games require a large group:
Meta-game
The
act of creating a virtual world is also a sub-game:
Other
sub-games?
While
I listed quite a few sub-games, the list is certainly not complete. With a bit
of imagination you can probably double their size.
What
makes a good sub-game?
So
what makes a good sub-game? I'm not 100% sure, but here are some general
guidelines:
1.
It should be an activity that can be done on a computer.
2.
Most virtual world sub-games are inferior to their
stand-alone counterparts. For a sub-game to work well in a virtual world, it
should get some benefit from being multiple-player or integrated with other
sub-games.
Example:
MMORPGs are based on CRPGs, but they make rather poor CRPGs. The reason they
are successful is because MMORPGs provide a multiple-player environment and
synergies with other sub-games (such as economics).
3.
The sub-game should be resistant to cheating.
One of the problems with adventure games and trivia contents (listed above) is
that they are easy to cheat on. If a player cheats on a single-player game they
are only hurting themselves, but virtual worlds turn into competitions (for
some people), so if they cheat in a virtual world, they get an advantage over
other players.
4.
Small sub-games
that can't be sold on their own, but which can be sold as part of a larger
package.
Example:
Chat is a free commodity on the Internet. However, most people spend
significant amounts of their time in a virtual world using (and paying for) the
VW's chat.
Business
reasons for sub-games
Why
would a business want to bundle a number of sub-games into a virtual world? Why
not sell them separately?
1.
The whole is more valuable than the
sum of its parts. (See "What makes a good
subgame?")
2.
Players would rather pay one large monthly bill than many small
ones. As a result, virtual worlds that offer more sub-games are
more likely to acquire and retain customers. (Think of cable TV... you only
have one cable TV provider, and only pay one bill for all 50+ channels.)
3.
One retail package for a host of games:
Retail shelf space is expensive and difficult to get. Bundling a number of
games into one box reduces the profit per box, but potentially increases
subscriptions (which is where virtual worlds make their money).
4.
Brand names and ease-of-acquisition
- If the customer wishes to play a genre of game, such as a racing game, they
are more likely to purchase the game from a company they are familiar with.
Furthermore, if the game is already part of their virtual world, they are less
likely to buy a competitor's version.
Conclusion
Don't
think of a virtual world as a single game. Think of it as a platform that can
support many games and other activities.
19 September 2004
by Mike Rozak
I
have read many a discussion on various newsgroups about issues in virtual
worlds, such as class-based systems vs. skill-based systems, the best combat
system, and what to do about spawning problems.
For
whatever reason, most of the discussed problems don't impress me. From my POV,
virtual worlds have a handful of large problems that dwarf the classed vs.
classless (and ilk) discussions. These large problems are so huge that they may
be unsolvable, or only solvable to the exclusion of other solutions.
Following
are the major problems with virtual worlds, as I see them (in no particular
order):
Are
there solutions to these problems? Maybe. Below are listed some existing
solutions.
How
MMORPGs try to solve these problems
Here
are some of the techniques that MMORPGs use to solve these problems.
|
Sensory realism |
Up to half the MMORPG team
is tasked with producing 3D models, textures, animations, sound recordings,
and music. |
|
User input |
Rely on the mouse or
joystick with occasional keyboard use (mostly as discrete buttons like AWSD). |
|
Players |
Expel cheaters and
griefers. Make some activities, like player killing, impossible. |
|
NPC AI |
Generally ignored. |
|
Plot maintenance |
Generally ignored,
although a few MMORPGs try to maintain a world plot. |
|
Personalization of content |
Generally ignored. |
|
The static nature of the
world |
Generally ignored. |
|
Perplexity |
Keep the perplexity as low
as possible since the user input and mass-market focus cannot handle anything
more complex. |
|
Cost of content generation |
Expensive content is
actually good for the large MMORPGs since it prevents competition. |
|
Similar to reality |
Mass market means that
reality is king. Most contemporary MMORPGs occur in a fantasy or science
fiction setting; this is in part because MMORPGs are not mass market yet, and
even the fantasy/sci-fi settings are based on the real world with a genre
veneer. (Example: Why do laser guns always look like pistols or machine guns?
Why do aliens look like humans?) |
|
The network |
Some motion prediction
algorithms. |
How
text MUDs try to solve these problems
|
Sensory realism |
Generally ignored. To a
text MUD player and author, text is superior to everything. |
|
User input |
Keyboard used to enter
English sentences. |
|
Players |
Like MMORPGs, except that
many MUDs encourage role playing. |
|
NPC AI |
Generally ignored. |
|
Plot maintenance |
Generally ignored,
although a few MUDs maintain a world plot. |
|
Personalization of content |
Generally ignored. |
|
The static nature of the
world |
Generally ignored. |
|
Perplexity |
English sentences allow
for a much higher perplexity than MMORPGs, although perplexity could still be
improved. |
|
Cost of content generation |
MUD content is very cheap
to produce. |
|
Similar to reality |
MUDs are more likely to
veer away from commonly accepted genres. |
|
The network |
Generally ignored since
it's not really an issue, the network being fast and reliable enough for
text. |
How
CRPGs and Adventure games try to solve these problems
And
you thought I was only discussing about virtual worlds... Single-player CRPGs
and adventure games have many of the same issues.
|
Sensory realism |
Just like MMORPGs. |
|
User input |
Just like MMORPGs. |
|
Players |
Not an issue. |
|
NPC AI |
Because there aren't any
other players, NPC AI is more important. NPC combatants are often more
intelligent than their MMORPG equivalents. NPC conversations are often better
too. |
|
Plot maintenance |
The plot is handled as a
"string of pearls" where the user can wander around freely in the
current "pearl" until they advance the plot and are moved to a new
"pearl". Some plots include branches. |
|
Personalization of content |
Generally ignored. |
|
The static nature of the
world |
As the plot advances, the
world changes along with it. |
|
Perplexity |
Just like MMORPGs. |
|
Cost of content generation |
Just like MMORPGs. |
|
Similar to reality |
Just like MMORPGs. |
|
The network |
Not an issue. |
How
face-to-face RPGs try to solve these problems
And
you thought I was only talking about computer games? Traditional face-to-face
RPGs (like Dungeons & Dragons) encounter these same problems.
|
Sensory realism |
Mostly imagination derived
from speech. Maps, miniatures, and occasional sketches are also used. |
|
User input |
Players communicate their
actions verbally to the GM, occasionally using hand gestures. |
|
Players |
Because only a small group
of players are in a session, and they all know one another, cheating and
griefing are fairly rare. The players (as a group) control the amount of role
playing expected. |
|
NPC AI |
The GM supplies the AI. |
|
Plot maintenance |
The GM and players work
together to produce a plot. |
|
Personalization of content |
The GM personalises the
content for the players. |
|
The static nature of the
world |
The GM changes the world
based upon the players actions and the needs of the plot. |
|
Perplexity |
The GM handles the world
simulation. |
|
Cost of content generation |
The GM produces the content,
often on the fly, making it very cheap. |
|
Similar to reality |
This varies with the
group, with some groups veering further away from reality than others. |
|
The network |
Not an issue. |
How
stories (TV, movies, and books) try to solve these problems
And
you thought I was only talking about games? TV shows, movies, and books have
many of the same issues.
|
Sensory realism |
Books rely on the
imagination for sensory realism. TV shows and movies have huge budgets
devoted to sensory realism. |
|
User input |
Not an issue. |
|
Players |
Not an issue. |
|
NPC AI |
NPC AI is
"baked" into linear narrative. |
|
Plot maintenance |
Plot is "baked"
into the linear narrative. |
|
Personalization of content |
Since there are so many
stories available, if one doesn't suit a viewer can always find another. |
|
The static nature of the
world |
World changes are
"baked" into the linear narrative. |
|
Perplexity |
Not an issue. |
|
Cost of content generation |
Books are cheap to
produce. Movies are very expensive to produce, although their costs are
dropping with technology. |
|
Similar to reality |
Mass-market TV and books
are based on reality. Books, which are less mass-market, can escape some of
the usual chains. |
|
The network |
TV has a network specially
designed for it. |
How
the Holodeck tries to solve these problems
You
thought I'd stick to reality?
|
Sensory realism |
Artificial matter. |
|
User input |
Players exist within
artificial matter, so there is no extra user input. (Although this limits
their input.) |
|
Players |
Only guests and parasitic
aliens are allowed in. |
|
NPC AI |
Great AI. |
|
Plot maintenance |
Not really specified, but
I assume the great AI has something to do with it. |
|
Personalization of content |
Not really specified, but
I assume the great AI has something to do with it. |
|
The static nature of the
world |
Not really specified, but
I assume the great AI has something to do with it. |
|
Perplexity |
Not really specified, but
I assume the great AI has something to do with it. |
|
Cost of content generation |
Not really specified, but
I assume the great AI has something to do with it. |
|
Similar to reality |
Players in the holodeck
can't even take on a different appearance. |
|
The network |
Holodecks don't seem to be
tied to a network. |
Interrelated
problems
The
act of solving one of the major problems often causes the other problems to
become more difficult... for example: Increasing sensory realism makes network
speed and reliability that much more important, since players are likely to
notice lag and dropouts.
Below
is a list describing how solving one problem makes some of the others more difficult
to solve:
|
Sensory realism |
Increased sensory realism
makes...
|
|
User input |
Increased user input
makes...
|
|
Players |
Solving player undesirable
conflicts makes...
|
|
NPC AI |
Increased NPC AI makes...
|
|
Plot maintenance |
Improving plot maintenance
makes...
|
|
Personalization of content |
Improving content
personalization makes...
|
|
The static nature of the
world |
Making the world more
dynamic causes...
|
|
Perplexity |
A higher perplexity
makes...
|
|
Cost of content generation |
Decreasing the cost of
content makes...
|
|
Similar to reality |
Diverging reality
causes...
|
|
The network |
Improving the network
causes...
|
Conclusion
A
VW can't solve all the major problems since the act of solving some problems
causes others to get worse. Most MMORPGs have decided that sensory realism is
the most important problem, so they tackle that to the detriment of the other
problems.
31 October 2004
by Mike Rozak
In,
my last whitepaper, "Virtual
World as Platform", I discussed why I thought
virtual worlds could be more than just multi-player CRPGs, as they so often are
today. Practically any game or fun activity that can be played on a computer
can be incorporated into a virtual world. The most obvious of these are: CRPG,
chat, vehicle simulations, economy games (like Lemonade), puzzles, and card
games.
Some
virtual worlds do include many of the sub-games and activities that I
mentioned. However, they provide them to the player as a kind of buffet,
letting the player kill 10,000 orcs until they're utterly bored with the task,
then shift to cutting down 10,000 trees for wood to make boats, and then become
obsessed with ruling the high seas.
I
don't think providing a buffet is the best way for a VW to offer its sub-games.
This makes it nothing more than a games-galore web site like Yahoo! games, or a
chat site like Yahoo! chat. Of course, Yahoo's services are free to players,
which makes them that much harder to compete against. Yahoo's games don't synergise,
like I mentioned in "Virtual World as Platform", but they're free.
So
how can a VW author package up valueless sub-games and activities, and produce
something that's valuable and unique, not a Yahoo! games/chat with orcs?
The
key is timing. Timing is everything...
Music
theory and virtual worlds
The
sub-games (and activities) offered by a virtual world are like the keys on a
piano.
Most
virtual worlds are so pleased they have 88 keys (well, most only have 4 or 5
keys) that they plop the player in front of the player, teach them how to
strike a key, and then let the player "go to town" and have fun.
Players
do have fun, for awhile; they press C, and then C#, get bored of that and hit
the low G, or maybe even the highest A. They don't produce music though; the
create a cacophony of notes which sounds utterly horrible unless you're into
atonal music. The players know this, so they get bored and give up.
However,
if you place a trained composer or improvisationist at the keyboard you'd never
believe it's the same music. Even making up music on the spot, the
composer/improvisationist produces music that's infinitely sweeting than the
bangings of most players. Those players that had just made a cacophony the
minute before look on amazed.
How
come the composer and improvisationist is better at the piano? For one, they
spend their whole life sitting at the keyboard... but then again, so do many
players. A professional also knows music theory, and is well aware at a
conscious and subconscious level which keys should follow one another, and
which sound good in combination. The average person is not.
Some
improvisationists, called Jazz players, use another trick; they memorise a
series of chord progressions for a song, but not the individual notes. Then,
when they're playing the piece, so long as they stay (mostly) within the chord,
it sounds good. Composers often use the same trick, although they may not admit
it. I'll come back to chord progressions later.
To
sum it up, on a piano, timing is everything. Hitting the right notes at the
right time produces a much more enjoyable experience (for the player and
listener) than playing notes willy nilly. The same holds true for VWs, I
suspect, but you'll take more convincing.
Literary
theory
When
an author writes a novel, he has several common constructs to play with:
characters, setting, action, conflict, backstory, plot, and theme. I'll add one
more that is so obvious it is rarely discussed, timing.
The
seldom mentioned, "when", is very important. I'll illustrate with a
thought experiment... Think of your favourite book. As you mentally skim
through it you'll notice the author first introduces the main character, then
the place where the character is, perhaps some backstory, and then some action.
Soon, another character is added, with some more action of backstory, following
by a bit more setting.
Now,
imagine if the author reordered the book so that all of the backstory came in
the first part of the book, followed by all of the setting descriptions,
followed by an introduction to all the characters, and finished by a
description of all the action.
The
book would be awful. If you don't believe me, read the Silmarillion, a
compilation of Tolkien's backstory and place descriptions. A portion of the
Silmarillion's backstory and places are included in the LOTR, but they're
spread throughout the book and are much more palatable in moderation.
I
haven't even mentioned what ordering within each of the categories (backstory,
location, characters, and action) does to the story. Tolkien explains the
backstory of the one ring at the beginning of LOTR so readers know how important
it is; pushing the backstory to the moment after Frodo tosses it into Mount
Doom would change the flavour of the story entirely. The same goes for when
locations and characters are introduced. And of course, action that doesn't
follow cause-and-effect, or which isn't sequential, cant be very disturbing.
Virtual
world theory
Virtual
worlds have many of the same elements as a story:
Hopefully
you are at least considering the fact that stories and VWs have many similar
constructs.
Yet
more analogies...
Virtual
worlds are not like stories though. If an author tries to force players to do
something, or puts words in the players' mouths, they get very annoyed and
leave the game.
Despite
this, authors do have some control over their players' actions. The obvious
solution is to not allow specific types of actions and claim they haven't been
coded yet... if an author doesn't want players to fish, he doesn't code in the
fishing sub-game.
More
subtle control can be imposed by geography: In a dungeon, the player doesn't
get to fight the dragon in its den until they figure out how to unlock the
den's door. They can't unlock the door until they they get the key from the
sphinx by answering riddles.
Other
techniques are also possible:
In
this sense, story writing is like composing music. In both forms of creation,
the author/composer have complete control over every aspect of the form.
Designing
a virtual world is like writing Jazz: Jazz composers write out chord progressions
and occasionally recommended rifts; they do not control what the musician will
play though, only guiding. Likewise, VW authors can recommend and encourage on
(musical-like) themes, but not force strict adherence.
Many
people reading this document won't be familiar with music theory, so I'll use a
different analogy: Story
writing is like arithmetic, while VW writing is like algebra.
Arithmetic
deals with manipulating specific quantities, where the number, 3, is always 3,
and 1+ 3 = 4. Algebra and higher mathematics deals with variables, such as X +
3Y = Z. The specific values for X and Y aren't known ahead of time in algebra.
The mathematician must solve the problem for the general sense, not for a
specific numerical solution as in arithmetic. The same goes for virtual world
design; it's about controlling the overall flow, not the specifics.
Timing
in virtual worlds
So
what does any of this have to do with timing?
As
I stated earlier, contemporary virtual worlds provide a set of sub-games (what
and how) that the player can participate in. For the most part, the sub-games
are laid out buffet style: If a player wants combat he visits the nearest
hunting grounds or dungeon. If a players wants to fish, he walks a short
distance to the nearest river. For chat, the player merely uses the built-in
location-independent chat tool and talks to anyone in the world
instantaneously. Trade is ostensibly localised, except for the omnipresent
teleportation devices that nullify any sense of locality. Etc.
This
is like writing a story where the player gets to pick and choose what elements
of the story (backstory, characterisation, setting, action, etc.) will be next.
While the freedom is great, some players will over-consume one component or the
other, and walk away with a bad taste in their mouth.
Quests,
on the other hand, are about timing and controlled use of sub-games. Despite
the appalling quality of contemporary quests, many players head straight for
quests because they begin to deliver what some players want... an expert hand
controlling how backstory, characterisation, setting, and action are doled out,
instead of random plucking at the piano. Unfortunately, contemporary quests
fall far short of what they should be. There are a few reasons for this:
1.
Current virtual words do not include enough sub-games.
Therefore, quests are limited to finding items/NPCs, combat, or delivering
items/NPCs. If, for example, the VW had a chatterbot NPC sub-game, another type
of quest could be added to the inventory; that of getting a NPC to talk. A
chess sub-game could require that the player beats the opponent at chess. Etc.
2.
Contemporary quests don't string enough sub-games together,
although this may because there aren't many sub-games. A piece of music
requires more than a few notes.
Here
is an example of what can be done in a quest when the VW has enough subgames to
play with:
1.
If players wish to travel to a distant port
city, they gather at the port and wait for a ship to leave. The ship won't
leave until there are at least 6-10 players, or maybe a player with lots of
money. This waiting game makes use of the chat sub-game since players will talk to
one another when gathered. Furthermore, they will talk to strangers.
2.
Once on the ship, which is a boring place,
the players could be asked to help out on the rigging or mopping the floor.
Turn this into a sub-game, just as fishing has been in contemporary MMORPGs.
Players wouldn't want to spend hours in the rigging sub-game, but a few minutes would
be entertaining.
3.
In the distance the players see a pirate
ship. Enter into the ship-to-ship
combat sub-game, with the players controlling steering and
cannons. If the players win, they make it to their port. If the lose, they
continue to the next step. Any PCs that die in the bombardment get resurrected
at the exit port.
4.
In a cut-scene
(story sub-game), the players manage to grab hold of parts of
the sinking ship and make it to a deserted island. The captain makes it there
too.
5.
Once on the island, the players will realise
they need food, or other supplies, plus they'll want to explore their
surroundings. This allows for several sub-games, foraging, fishing, hunting, cooking,
exploring. A hermit lives on the island; the quest can let him
be found right away, or maybe not until the next day.
6.
At night, around the campfire, the captain
could offer to tell a story of some sort, providing backstory.
7.
The next day the players will continue
exploring. They find the hermit and talk with him; being on the island for many
years, a chatterbot's
frustrating conversations are a perfect match.
8.
Eventually the players learn about a secret
cave from the hermit (which can't be found unaided). In the cave is the
hermit's boat they can use to get off the island. They find the cave, using the
exploration sub-game.
9.
Inside the cave is a monster that has taken
up residence, using the combat
sub-game.
10.
Eventually the players get the boat and
escape the island. This shows another short story segment.
While
the quest I've described steers the characters, it's much more fulfilling then
the players choosing each item individually from a buffet, or from the shorter
quests offered in most virtual worlds. Notice that many of the sub-games and
activities, such as stories and ship-to-ship combat, used in the hermit-island
quest do not
exist in contemporary virtual worlds. Removing those elements from the quest
produces a weaker experience. Re-arranging the sub-games also changes the
experience.
Deconstructing
the CRPG
Just
in case you're not totally convinced about my concept of stopping the
buffet-style sub-games, here's something to think about:
The
traditional combat sub-game in a VW can be further deconstructed into
sub-games, the most basic of which is a fight-game like Mortal Combat (although
CRPGs have much
simpler combat). In a fight game, players can select their character and their
opponent from a buffet. CRPGs and MMORPGs don't allow players to pick and
choose their opponents. Instead, they control the opponents, using PC levels
and locations as a method for control.
Which
is more fun, a CRPG where you can pick and choose your enemies and fight in
your chosen arena, or a CRPG where you wander through the world and encounter
enemies of the world's choosing?
Likewise,
an adventure game such as Myst is a world containing many puzzle sub-games.
Which is more fun, the puzzles as a buffet, or tied into a whole?
Conclusion
Just
to reiterate:
1.
There are more sub-games than just killing
monsters. (See Virtual
World as Platform.)
2.
Use sub-games that encourage synergies
between the sub-games. (See Virtual
World as Platform)
3.
Think of sub-games as a tool-set to be used
selectively to create a larger experience, not as a buffet for characters to
choose from. If you wish a buffet, consider using quests as the items on offer,
not the sub-games.
4.
Of course, you can always have sub-games
available at the buffet. You might consider having the sub-games be location dependent,
or to vary somewhat with locations, so players would have to travel (and
experience other sub-games) to get to the sub-games they're looking for.
5.
How you combine the sub-games into an experience
is very important.
6.
Quests cannot be produced from one or
two sub-games alone, just as music requires more than two
notes. Too few sub-games results in standard VW quests, aka: fedex quests.
17 November 2004
by Mike Rozak
What
is the difference between an airport novel and a classic novel?
Partly,
it's the reason why you're reading it; an airport novel is designed to kill
time while you're on an airplane or in an airport, a classic novel is assigned
to be read by an English professor.
Of
course, a classic novel will be better written, have more interesting
characters, and better plots. It will have been written sometime in the distant
past, which is why it is called "classic", since it has withstood the
test of time.
However,
plenty of airport novels are just as well written (if not better) than some of
the classics. What then allows an airport novel to avoid obscurity? Some
classic novels were merely lucky enough to be singled out by English teachers.
They may also have been written by an author that produced other classics, and
by virtue of their siblings, be raised up.
Classic
novels also have a quality that transcends an airport novel: Classics are
"food for the soul", to use a cliche term. Amidst all the interesting
characters and plotting, are ideas about life, the universe, and everything.
For example: Charles Dickens'
"Oliver Twist" was not only a quaint story about an orphan, it was a
larger exposition on 19th century England's approach to orphans, child labour,
crime, and the lower classes. An airport novel is about, well, nothing.
For example: The movie,
"2001: A Space Odyssey" is remembered while other science fiction
moves from the late 1960's are long forgotten. 2001 was about humanity's
evolution from ape, to human, to beyond, and ultimately questions if man will
become god and create its own life-form.
Continuing
on with the "food for the soul" analogy... As I see it, there are
four different levels of "nutrition" in entertainment:
What
does this have to do with virtual worlds?
Most
virtual worlds fall into the "rice cake" or "junk food"
categories. They are either used to kill time (orc-massacre marathons) or are
entertainment without substance. As such, the existing crop is doomed to
historic obscurity, other than their being remembered as pioneers.
Making
nutritious virtual worlds
What
can be done to make a virtual world more than empty calories?
I'm
not entirely sure, but I'll give it a go. Let me first revisit what makes a
classic novel or movie feel "nutritious".
How
does this translate into a virtual world? A nutritious virtual world is one which either changes
the player, and/or changes the world. Of course, this
definition has nothing to do with "food for the soul", but it's a bit
more concrete.
Richard
Bartle, in his book, Designing Virtual Worlds, spends a lot of time discussing
how players change over the course of playing in a virtual world. They
undertake the hero's quest and find their true selves over the course of role
playing and "trying on" other personalities.
My
thoughts are not as grand as the hero's quest, but the hero's quest could be
included as one of a virtual world's nutritious components.
Here
are some ways a virtual world can change
the player's real-world life:
And
change the real world:
While
a virtual world can change players and allow players to affect other players,
it also acts as a surrogate for the real world. For example, if a person wishes
to be wealthy in the real world, but cannot, he may make it a goal to become
wealthy in a virtual world instead. Some examples of the virtual world acting as surrogate
are:
Conclusions
I
have digressed a bit from my original discussion. Originally I was talking
about applying the concept "food for the soul" to a virtual world,
and ended up with a list of reasons why people like playing virtual worlds...
I
suppose I made a switcheroo.
What
I have actually come up with is a list of "non-fun" reasons why
people play virtual worlds. After all, learning a new language or owning a pet
aren't fun by themselves. However, the act of knowing a language and then
travelling to a different country is fun. And the 10 minutes a day you play
with your pet is fun, even though the 30 minutes spent feeding it and cleaning
up after it aren't.
My
suspicion is that people begin playing a virtual world because it's fun (see Evolutionary
Explanation for Entertainment), but they
continue to play a virtual world because of other the "non-fun"
reasons. (Although the virtual world must continue to still be fun.)
If
this theory holds true, then there are some ramifications:
17 November 2004
by Mike Rozak
A
few days ago I tried to imagine what virtual worlds would be like in 10 to 20
years. Of course, they'll have better graphics, better network connections,
better AI, and better everything.
However,
my mental wanderings chanced upon a few aspects of virtual worlds that may
change:
1.
The lifecycle of future virtual worlds will
shorten.
2.
The amount of time a typical player spends in
a virtual world will shorten.
Let
me explain...
Lifecycle
of a virtual world
Historically,
the lifecycle of a virtual world has been very long, even among MMORPGs. Some
MUDs have been running for 15+ years. Meridian 59 (though resurrected), Ultima
Online, and Everquest are still running after 6+ years.
I
don't think this longevity will continue in the future. In fact, I suspect it
has already stopped, even though people haven't realised the fact. UO, M59, and
EQ won't necessarily die right away, but they will fade away.
To
explain why, I want to describe three different categories of players and what
cycle of a virtual world they are attracted to:
Part
of the reason there are three types of customers for a virtual world is that
virtual worlds can only support a limited number of players at any one time.
Publishers need to limit a VW's population because, while adding new servers is
relatively easy, getting new product-support staff up to speed is much more
difficult. As a result, publishers will encourage a consistent playerbase
population rather than a burst of activity followed by a quick VW death.
For
the the last seven years, a major virtual world has come out about once a year.
Meridian 59 was first, the Ultima Online, Everquest, Asheron's Call, Dark Age
of Camelot, Anarchy Online, etc., all about a year apart.
In
the last 24 months, major virtual worlds have been coming out once every six
months. These include Asheron's Call 2, Star Wars Galaxies, Lineage II, Finaly
Fantasy, etc.
Over
the next 12 months, a major virtual world will be out once every 3 months.
These releases include Everquest II, World of Warcraft, Dungeons & Dragons
online, Middle-Earth online, Dark & Light, etc.
If
this trend continues, even at the rate of a major world every 6 months (let
alone every 3 months), the playing field will be swamped with virtual worlds.
Not only will the new ones be contending for players, so too will the old ones,
keeping up-to-date with semi-annual add-on releases.
So
many virtual worlds provides players with lots of choices. What will each
category of player do?
"Yes,"
you might be saying, "but a virtual world can keep putting out expansion
packs and stay up-to-date with the latest technology so that it never
dies?"
Expansion
packs increase a product's longevity, but they only go so far:
"Yes,
but MUDs have been running for 15 years, sometimes with the same players
sticking around, mostly for social reasons." I agree that happened in the
past, but I expect future behaviour to change:
Scale
of virtual worlds
Speaking
of mass-market players: As virtual worlds become more popular, the number of
hours that a player stays in a virtual world (before they get bored and leave)
will shrink.
Contemporary
virtual worlds are designed to keep a player around for 500 to 1000 hours. (40
hours/month x 12-24 months) For brevity, I'll just call this "1000"
hours of gameplay.
Now
for the obvious question that I've never heard asked: What percentage of the potential
game-playing population would be willing to commit 1000 hours to a game? 100
hours to a game? 10 hours? How do these players differ?
Personally,
I last between 25 and 75 hours on a game. When I play a 1000-hour virtual
world, I don't expect to stay any longer than 25-75 hours, not just because I
get bored, but because I'd rather experience the variety of ten different games
than one really long one. I suspect most game players are the same. Most
people, those whose computer game-playing experience is limited to solitaire,
might even find a 10-hour game to be a stretch.
|
Profile of users |
|
|
1000 hours |
|
|
100 hours |
|
|
10 hours |
|
You
might argue that someone with only 100 hours of time could play a 1000 hour
game; they just wouldn't get to the end. While this is possible, I don't think
it will be common. There are a few reasons:
I
haven't tried to convince you that a 100-hour virtual world can exist, let
alone a 10-hour experience; I try to do so in this document. However, if one
can exist, then the experience will be radically different from the 1000-hour
game. Here's why:
|
What players do... |
|
|
1000 hours |
1000-hour virtual worlds
are very similar to contemporary MMORPGs:
|
|
100 hours |
100-hour virtual worlds
are similar to CRPGs and adventure games, except that they're online:
|
|
10 hours |
10-hour virtual worlds
aren't similar to any marketed game, because at the moment, 10-hour games
cannot survive financially. My best guesstimate is that they'd be like short
adventure games, perhaps interactive stories not that different in feel from
Choose Your Own Adventure books, except with much more sophisticated
branching.
|
|
User interface... |
|
|
1000 hours |
|
|
100 hours |
|
|
10 hours |
|
|
Business model... |
|
|
1000 hours |
|
|
100 hours |
|
|
10 hours |
|
Hours
of content vs. number of players
Notice
the correlation between the number of hours it takes to complete a virtual
world and the number of players in a shard. If there are 100 players in a
100-hour virtual world, then statistically, each player will be 1 hour away
from the other in terms of content. At this density, players will occasionally
run into one another by accident, or if they go to a focal-point like a
village. If 1000 players are in a 100-hour virtual world then players will only
be 6 minutes apart, and will be running into each other all the time.
Graphing
the hours-of-content vs. the number of players (online at a given time)
produces an interesting table:
|
10 hours |
100 hours |
1000 hours |
|
|
0 players |
Book, TV
mini-series |
Book series, TV
series |
Long-running TV
series |
|
1 player |
Short single-player
game. I have only seen amateur text-adventures that are this short. |
Single-player game,
such as CRPG or adventure game. |
Very long-running
single-player game. This category is unlikely due to the expense. |
|
10 players |
10-hour VW that I
described above... Interactive storytelling with friends, like "Host a
Murder". |
Multiplayer CRPG
like Neverwinter Nights. |
Players will never
run into one-another unless they pre-arrange it. This category is unlikely. |
|
100 players |
Chat |
100-hour VW that I
described above |
MUD-like |
|
1000 players |
1000-hour VW that I
described above... MMORPG-like |
||
|
10,000 players |
Chat |
I
put chat into the lower-left corner because a virtual world with far more
players than content just turns into a large chat room.
If
you don't quite get what I mean by "0 players" then see Virtual
World Spectrum.
Conclusion
Producing
a 100-hour virtual world results in a significantly different experience that
the contemporary 1000-hour virtual world, as well as a different user base.
Likewise, a 10-hour VW even more different and caters to a different set of
players.
Are
100-hour and 10-hour VWs financially feasible? I don't know.
17 November 2004
by Mike Rozak
Chris
Crawford's book, "Chris Crawford on Interactive Storytelling", points
out a very useful rule about interactive fiction (or storytelling) design:
A choice is not
a choice if:
1.
The choice is made blind,
where the user has no way of knowing which is the best choice. Asking a player
if they want to choose what's behind curtain A or curtain B is irrelevant if
both curtains are identical.
2.
The effects of the choice result in
the same outcome, such as two doors that both lead to
the same room. (For example: Alice Springs' airport has 4 side-by-side,
glass, departure-gate doors that all lead to exactly the same place, a covered
pathway that guides passengers to the airstrip.)
3.
The effects of the choice all result
in "end-of-game" except one, such as the
Dragonslayer video game where one of two choices almost always resulted in
death.
I
agree with these observations.
They
effectively put an end to a Choose
Your Own Adventure (tm) style game where the player reads some
narrative, choses from two to four possibilities, which then leads to more
narrative.
The
reasons are:
1.
Each choice is a branch in a tree of possible
paths the reader can visit. Even if there are only 8 branches, with 2 choices
each, that's 2 ^ 8 = 256 possible endings, with 511 narratives that need to be
written. Obviously, this is too much work, writing 511 narratives when only 8
of them will ever be experienced by any one player.
2.
To get around this, many CYOA-style
interactions have branches that quickly reconnect with the main branch,
breaking rule #2.
3.
To save work, they also have choices that
result in instant death (or the story otherwise ending). This breaks rule #3.
Chris
Crafword, as well as other authors that have made the same point, then goes on
to dismiss this form of interactive fiction as a dead end. (He points out one
"way out" that I'll get to in a bit.)
Is
it really a dead end?
The
escape clause
Chris
Crawford's escape clause is that two paths can reconnect if they somehow affect
the narrative later on. Even if they both lead to the same room, if one door
makes the character good looking, while the other makes the character strong, and these attributes
are used later on in the experience, then the choice is valid.
In
the Stop
the buffet article I wrote, I described a slightly
different take on how to write quests for virtual worlds. Contemporary quests
inevitably start out with a bit of short narrative explaining why the quest
exists, followed by the player going to either kill a monster or deliver an
item, and are completed with the character's return and follow-up narrative.
Thus, the form for a quest is:
NKN
or NDN
N
= narrative, K = kill, D = delivery
Contemporary
virtual worlds are limited to such simple quests because they don't have many
sub-games in their palette. Most have a few other sub-games, like gathering raw
materials (G), exploration (X), and chatting with other PCs (c). If they added
more sub-games, such as conversations with chatterbots (C), and piloting ships
(S), they could produce a more complex quest. The quest I described in
"Stop the buffet" was:
NcNSKNGNCXKN
Notice
that there are no choices in the high-level narrative. Each sub-game, however,
has choices; they don't affect the high-level narrative, but they do affect the
players and their characters.
What
if I added choices in the high-level narrative?
If
I do, I run into the same fundamental problems that dog the CYOA books. One
choice leads to another, and to another, and eventually there are just too many
choices for an author to deal with.
Therefore,
I'll use the same tricks that the authors of CYOA do:
1.
I'll reconnect branches.
For example, I could provide a choice of talking with the mad-hermit
chatterbot, or just following (F) the hermit around the island. Both lead to
the same cave, where there's a monster to kill, and then the same final
narrative.
2.
I'll provide dead ends.
Actually, I already have put in dead ends; if a character dies at any of the
points in the linear quest, the quest ends (for that character).
Both
of these changes seem to break the rules, except that the sub-games, which are
components of the larger narrative, rescue the situation:
1.
Talking to the chatterbot might result
in the player characters improving their "Patience" skill, while spending
more time searching the island would result in an improved "Stealth"
skill.
Furthermore,
even if no "physical" change were made, one player might find talking
with a chatterbot more fun that tracking the hermit, while another would find
the opposite. Providing
this choice, even though it leads to the same outcome, produces a more
enjoyable experience for the player. Of course, if the player
can't deduce ahead of time which choice he would enjoy most, then this argument
doesn't hold.
A
more subtle effect of the choice is to allow player characters that have high
"Charisma" a better chance with the chatterbot, and high
"Stealth" a better chance of tracking. Thus, whichever way the character has
specialised, he will be able to complete the quest.
2.
The dead ends aren't necessarily dead
ends, only if the players fail at the sub-games.
The
new quest "formula" might look something like this:
NcNSKNGN (CX|F) KN
While
I haven't tried the concept out in reality (other than as a dungeon master
years ago), the thought experiment works.
Applying
this revised rules to CYOA and MMORPGs
Just
to be sure of my logic, I thought I'd apply my revised rules to existing
interactive entertainments...
The
reason that CYOA books can't "break" the rules like I have in my hypothetical
quest is that CYOA only has one sub-game, narratives (N). Any story with
branches is just a series of N's.
NNN (NN|N) NN
It's
not too interesting, is it? Well, it can be, but the narrative becomes
absolutely critical to the experience.
Looking
at slightly more advanced technology, you'll see that a typical MMORPG's buffet
of activities is:
(K|D|G|X|c)
Of
course, MMORPGs have simple quests that I've already mapped out. They also have
dungeon crawls where the branches in corridors provide the menu of choices for
the player. Each room is one of the sub-games, such as a monster (K), trap (T),
or puzzle (P). A segment of a dungeon could look like this:
(K(T|P) | TK | KKK)
K
The
formula represents an intersection with four exits. One exit is where the PC
came from. The other three choices lead to: A monster to be killed followed by
a T-intersection with a trap down one way and a puzzle down the other (K(T|P)).
The second choice is a trap followed by a monster (TK). And the third, is a
series of rooms with monsters in each (KKK). All three directions eventually
reconnect to allow the final fight with the "boss" monster. (K)
According
to the revised rules, a dungeon crawl makes for an interesting series of
rule-compliant choices. However,
for the dungeon crawl to follow the rules, each hallway must somehow hint at
what is down it. Shouts of evil laughter might be heard wafting
down two hallways, and maybe a short jaunt down the silent hallway would reveal
a skeleton of a previous adventurer that was caught in the trap. Without these
hints, the player must chose a hallway at random, invalidating the choice.
Asheron's Call 2 had a few dungeons that amazingly
managed to break the rule #2, about a choice resulting in different outcomes.
In many of the AC2 dungeons I visited there would be a T-intersection. Going
left would lead to a monster, and then lead back to the main trunk of the
dungeon. Going right would lead to an identical monster, and then back to the
trunk. The T-intersection was a useless choice because both choices produced
exactly the same outcome.
Of
course, in most MMORPGs and CRPGS, there isn't any hint at what's to come. The
player cannot sneak up the hallway to listen for monsters because the monster
AI is aware of the PC just as soon as the PC is aware of the monster AI.
(Perhaps because there's often no way a player can indicate they're in
"stealth" mode.) As a result, players are forced to walk blindly into
every situation. Additionally, MMORPGs and CRPGs reward players for killing all of the monsters in the
dungeon by handing out XP. Players are not rewarded for making intelligent
choices and avoiding unnecessary dangers.
The
same analysis can be applied to adventure games. The only difference is that
the palette of sub-games in an adventure game is different than those sub-games
available in a CRPG or MMORPG.
Conclusion
In
some ways, none of what I've been pointing out is new. Anyone who has designed
a dungeon for Dungeons & Dragons knows these techniques already. It's
obvious that CRPG and (most) MMORPG designers also know them, at least
intuitively.
I
intuitively knew them from my experience being a dungeon master in high school.
I never sat down and figured out why the rules worked though. Now that I have
written them down, the concept has become much clearer to me.
17 November 2004
by Mike Rozak
When
I began my effort to design a virtual world, I wrote "The
trouble with explorers". While attempting to determine
why adventure games turned into MUDs, I came up with the following thought
experiment:
1.
Write a text adventure game.
2.
Put it online and allow multiple people to be
logged into the game at once.
3.
What happens to the design?
The
problem I quickly came up with was that the
adventure game that took me one year to write, was completed in 40-50 hours
by most players. Because virtual world players spend an average of 40 hours per
month in the game, most
players would finish all my content within the month, some
sooner.
At
that point, they'd start whinging that there wasn't enough content and I'd need
a way to create content quickly to stop the whines. The solution was to add
automatically generated content, which usually amounts to combat with randomly
generated monsters.
At
that point, a MUD is born, and the adventure game dies.
The
cost of content
The
problem that online adventure games face is that content is too expensive to
produce. A text adventure takes approximately 1 man year to produce 40-50 hours of content.
A graphical adventure like Myst IV takes approximately 1-2 man years to produce 1 hour of
content.
CRPGs
are somewhat cheaper to produce per hour of content (since they are more
amenable to automatically generated content), and they have a larger audience,
so online games quickly turn into online versions of CRPGs. (This is not always
the case, though.)
But,
even an online CRPG is too expensive to create content for.
Here's
why: The average virtual-world player is online for 40 hours a month. That's
equivalent to completing an offline CRPG every 1 to 2 months. However, the
virtual-world player is only paying $15/month, while a new CRPG every month
costs the player $50/month.
Of
course, this isn't an apples-to-apples comparison:
1.
Given the $50 retail price, the game
developer only gets $15-$20. So, realistically, if the distribution and Internet
were free, an online CRPG that charged $20/month
could provide the full content.
2.
The Internet bandwidth to support a player is
a few dollars a month. Add $2/month
for the Internet.
3.
Product support is also expensive, and even
though I haven't heard a compassion, I suspect support costs for an online game
are higher than an offline game. Add a $3/month
for extra support issues.
4.
If the player were to purchase an offline
game every month, the game would come with a DVD chocked full of new graphics
and sounds. MMORPGs do not (yet) send out a monthly DVD update to their
players, so a MMORPG's audio-visual elements won't change every month. MMORPGs
solve this problem by putting out add-on packages every 6-9 months. If a DVD
were mailed to every player, every month, you'd add $5/month. I won't include
this in my guesstimates since MMORPGs do not currently mail out monthly
updates.
5.
Virtual worlds have a smaller
player-base than offline games. 500K
players makes a virtual world is a big success, while 1M-2M is a successful
offline CRPG. Fewer players mean that each player must pay more for the same
quality. How much more? Twice as much?
Basically,
if the content were kept up to CRPG standards, players would have to pay $25 - $50 per month
for their online CRPG. Since they're paying $15/month, the content either has to be of lower
quality, or it will run out.
There's
another problem with providing that much content. If a CRPG is 50 man-years of
work, then producing 12 CRPGs a year is 600 man years of work. 600 people all developing one world is
a logistics nightmare. Even if each team is given a different
region of the world to own (which will happen), items from one region can and
will be carried by PCs from one region into another, causing all sorts of game
balance issues. And what about conflicts in shared backstory that are created
as one team invents some backstory for its corner of the world that invalidates
the backstory from another part? Even if you could get players to pay the
$25-$50/month, a full-content online CRPG would still be a no-go.
Consequently,
the virtual world developer cannot provide enough content. Most users will
reach a point where they have consumed all of the content, and the content-flow
eventually dries up.
The
drought
What
happens when players run out of content?
Players
do one (or more) of the following:
The
last four bullet items correspond to Richard Bartle's player models in
"Hearts, Clubs, Diamonds, Spades: Players Who Suit MUDs", (http://www.mud.co.uk/richard/hcds.htm).
No
developer likes their players leaving or whinging, so they do what they can to
make the other activities more fun, perhaps as a stopgap measure. After all,
the players decided that's what they wanted to do, so provide them tools that
allow them to do what they want. The solutions are:
The
developer soon finds himself spending most (or all) of his time producing and
improving sub-games to keep the players occupied, and little (or none) on
content.
The
MMORPG is born from the ashes of the online CRPG.
The
"fun" in a MMORPG mostly comes from the sub-games that allow players
to interact with one another, with only a small amount of fun coming from the
content, in the form of quests. Some MMORPGs, such as Everquest II, are more
heavy on content, while some are almost entirely content-free.
The
instant replay
Did
you see what just happened? An online CRPG (which may have originally been a
online adventure game) morphed into a MMORPG. It's like a state change where
ice suddenly becomes water. Or rather, a structured entertainment suddenly
turns into an unstructured-entertainment, a sand-box.
In
the TV-world such a state change is equivalent to a TV-series, which can only
be produced at a finite rate no matter how much money is thrown at it, trying
to fill in the 6 days and 23 hours between the next episode with stuff that is
vaguely related to the original content.
Such
filler doesn't work for TV; viewers have to wait a week before seeing the next
episode. Why should it work for games?
It
apparently does work... kind of. Millions of people play MMORPGs, and while
they whinge about the grind (aka: fillter in-between the content), they keep on
playing.
What
if MMORPG players are an atypical subset of the general population? What if the
majority of potential players are turned off completely by the grind, and want
their content back?
Personally,
I think that most people would take content over the grind. I know I would.
The
other choice
When
players ran out of content, the developer consciously decided to reduce work on
new content and create PvP code, economic games, fishing, automatic content
generation, improved chat, and other grindy things. What would happen if the developers
didn't pander to their players' demands, but instead said, "Don't bother
us, we're working on content. Come back in a week or two."?
I
can tell you exactly what would happen. The
players would leave and never come back. There are a few
reasons for this:
Therefore,
the first thing that
would have to change would be the payment method. Payment would
either be one-off, or based on advertising.
The
virtual world would also need a less-rude
means of telling the user to leave, something along the lines
of, "You have just completed all the quests. We're working on a new
one titled XYZ, which will be available next Friday. You can continue to play
the game of course, either replaying old quests or hanging out with friends."
When the quest is available, players waiting on it could be automatically
E-mailed, although the E-mails should be staggered so not all the players show
up at once.
If
the company is large enough, it may have several virtual worlds and could
recommend the user try
one of the other virtual worlds while waiting for new content.
The company could even sell subscriptions to several of its virtual worlds in a
package, much a set television shows are all "packaged" into each TV
channel.
Would
this work? I don't know. Television does it all the time, but I haven't found a
virtual world that attempts it.
Ramifications
For
the purposes of this document, I will call the developer's "other
choice" an anti-MMORPG.
A MMORPG is what results when players are encouraged to stick around once the
content (quests) have been used up. An anti-MMORPG is what happens if the
players are encouraged to leave (temporarily) when the content has been used
up.
If
this anti-MMORPG
model did work, it would radically change the virtual world's experience.
Here's how:
1.
When a player first subscribes to a virtual
world they will play
intensely until the content is used up, and then only show up as new content is created.
(Or for social reasons.)
2.
As a result, players are likely to take part in several virtual worlds at
once. Most players today only play one virtual world at a time;
this has significant social ramifications.
3.
At some point, someone in the development
team will create a histogram
showing how much content a player consumes before getting bored and leaving the
world. Even with an infinite amount of quality content, the
histogram will show that 80% of players play for more than 50 hours, 50% of
players play for more than 100 hours, and 20% of players play for more than 200
hours. (Or whatever.)
4.
In order to maximise profits, bean counters will realise that the
optimum "size" for a virtual world is 150 hours (or whatever) of
content, just like book publishers know how many pages a book
should be, according to their market analysis. (... I say cynically.)
5.
As a result, the scope of virtual worlds will shrink.
Shorter duration virtual worlds will, in turn, attract more of a mass-market
audience. The mass-market audience will, in turn, want an even shorter virtual
world. Exactly how short the final experience will be, I can't say, but it'll
be much less than the typical 1000-hour experience that contemporary virtual
worlds aim for. See Steady-state
approximation for more thoughts.
6.
Shorter duration virtual worlds will
be cheaper to make, resulting in more experimentation.
There will be more variety, and some of the worlds may be experiences that
would be unacceptable with a 1000-hour game. A 10-hour VW can go even further.
For example, I'd be willing to spend 10-hours playing in a hopeless,
totalitarian-controlled world like George Orwell's 1984, but not 100 hours, and
definitely not 1000 hours.
7.
Since more people will play virtual worlds,
and since the worlds take less time to complete, more virtual worlds will be produced.
Either a few companies will produce most of the virtual worlds, and/or a suite
of virtual-world development kits will appear. See below.
8.
If a virtual world is only 100 hours long,
then somewhere between
100 and 1000 players will be in a shard to prevent overcrowding
of content. (It would be possible to have a huge shard with private instancing,
but what's the point.)
9.
If a virtual word is large, with 100,000
simultaneous players, and 100-1000 players per shards, there will be 100-1000
shards. With that many shards, developers will target individual shards at special interest groups
(a left-handed golfer's shard), or at specific
real-world geographies (Boston, NYC, LA, Madrid, and Cairo).
The locality-specific shards are convenient because people (especially singles)
can use them as a tool to meet people in the real world.
Both
a MMORPG and an anti-MMORPG
Another
possible configuration is to combine both a MMORPG and an anti-MMORPG. This
configuration is commonly done, whereby a few hundred quests are provided for
"newbies". When they graduate from the quests (aka: run out of
content) they join the rest of the experienced players in PvP.
The
combination seems to work, but there are problems:
As
the number of virtual worlds increases, will players naturally segregate
themselves into quest-oriented (anti-MMORPG) vs. PvP-oriented (MMORPG) worlds?
Will a mid-point like Everquest II still exist?
How
many MMORPGs can there be?
As
a programmer, I like the MMORPG sand-box concept, since it means that I can put
all my effort into writing code that allows players to interact with one
another in fun and interesting ways. There is no content to develop either.
(MMORPG developers will call a larger landscape, more monsters, and new spells
content. I suppose they are, in a sense, but I see them more as elements upon
which the quests, the real content, can be built.)
Looking
at virtual worlds from a business POV, I don't like sand-boxes. The problems I
have with building a sand-box are:
Virtual
world as platform
A
virtual world is internally subdivided into several modules, each one built
(more-or-less) on top of a lower module. The internal structure of a virtual
world looks like this:
|
Content - Quests, NPCs, special equipment, special
monsters.
|
MMORPG-specific - PvP code and other modules designed to
allow for player interaction.
|
|
World-dependent - Code and assets for a specific world,
based on a genre. For example, this code includes the monsters and magical
items from Middle Earth, along with the Middle Earth geography. This does not
include quest-specific material and NPCs.
|
|
|
Genre-dependent - Code and assets for a specific genre
(such as fantasy), which can be used for any type of fantasy world (whether
Middle Earth, Earthsea, etc.)
|
|
|
World-independent - Any code or assets that can be used for
any genre of virtual world.
|
|
Actually,
most virtual worlds are not divided this way because most developers don't have
the mental bandwidth to see more than a couple years down the road. Most
virtual worlds are developed as a one-off system, so all the modules as
interwoven; this is a huge design mistake. A few companies, such as Turbine,
have isolated the world-independent code from the rest. Ultimately, I think the
best solution is to divide the modules as shown because it provides for maximum
flexibility.
When
an online-CRPG development team gives up on producing content and instead
decides to produce a MMORPG, with PvP, economics games, players with building
abilities, etc., the development team stops working on the content components
of a virtual world (blue) and instead puts work into MMORPG-specific portions
of the virtual world (yellow).
At
the moment, virtual worlds are all MMORPGs, which means that not too many are
built annually. As a result, almost every MMORPG has its own home-grown
world-independent code, genre-dependent code, world-dependent code, content,
and MMORPG-specific code. A few companies are trying to sell world-independent
code, but I don't think they're being too successful. (I could be wrong.)
If,
however, virtual worlds shrink in duration (as described in this whitepaper)
and hundreds of them are produced every year, companies that sell the
world-independent components will do well. After all, if a virtual world is
short (100 hours), there's no need for home-grown technology at the
world-independent level.
Furthermore,
there's no reason that the same companies couldn't sell a genre-dependent
component for fantasy vs. science fiction worlds. They might even sell a
world-dependent component, containing a nondescript and generic fantasy/sci-fi
world. Then, any company producing a virtual world could modify the generic
world to their needs and spend more of their time on content.
This
is why I think of a virtual
world as a platform. Everything in grey (world-independent,
genre-dependent, and world-dependent) can be licensed to companies whose
strength is producing either the content, or the MMORPG-specific code.
Alternatively, one company could write and keep the grey modules in-house,
while providing dozens or hundreds of independent virtual worlds by changing
the content or MMORPG packages.
Conclusion
MMORPGs
are the way they are because designers de-emphasise content (quests) and
emphasise automatic content (monsters), chat, and PvP interactions. They do so
because content cannot be cost-effectively produced at a fast enough rate to
keep their players occupied.
If
a virtual-world developer were to tell users to take a break once they had used
up all the content, the nature of the virtual world would change dramatically,
including a reduction in size from a 1000-hour experience to a 100-hour
experience.
Would
a virtual world designed to provide a mere 100 hours of entertainment work
financially? I don't know.
17 November 2004
by Mike Rozak
(Continued
from The
anti-MMORPG.)
Creating
a virtual world (an anti-MMORPG) where players will only stay around for 100
hours introduces a new problem that contemporary 1000-hour virtual worlds
(MMORPG) don't have: How
does the player's virtual-world experience end?
In
a MMORPG, the experience ends for the player when they get bored. Since MMORPGs
want players to stay around as long possible, and since some players do,
MMORPGs don't have official endings.
However,
an anti-MMORPG would run
out of content for some (perhaps most) players. Of course, when
all the content is used up and no more will be added, the virtual world could
tell the player "That's all folks."
This
is unsatisfying for the player, however. Ideally,
the "end" is the point at which the player finally completes
something monumental in the world, like killing the evil
overlord.
There
are problems with such an ending in a virtual world; I'll get to this in a bit.
But first, I'll discuss how stories deal with endings.
Story
endings
Of
course, everyone knows how stories end... "And they lived happily ever
after." Well, not quite all stories.
A story ends when the protagonists major objective is
fulfilled, or shortly thereafter. The major objective
is a goal that the protagonist has throughout the entire story. The protagonist
is usually given this goal near the beginning of the story, and it doesn't
change unless there's a plot twist. (If you have never read any books on
story-writing then I suggest you do so.)
Furthermore,
the major objective is a
unifying device. While the world in which the story takes place
may be huge, the author only
includes narratives that lead to the major objective being accomplished,
or which somehow elucidate important aspects of the objective, characters, or
world. In some ways, the
end determines what happens in the story.
This
brings up an important question for virtual worlds: How does the author determine what
content gets into the virtual world? MMORPGs, because they have
a very limited number of sub-games (see Stop the
buffet) and because quests (the content) are only a
small part of the experience, don't really worry about what gets in. As a
general rule, any half-way decent quest-idea is implemented. However, when a world is smaller and there is
more potential variety in quests/content, determining what gets in is more
difficult. The ending may help filter out some content ideas
that do not lead to the end.
The
end of the story is met when the character "defeats the evil
overlord", or whatever the plot may be. All the content of the stories is
affected by this ending.
A
virtual world could have an end-point of "defeat the evil overlord",
but then problems arise in a multiplayer world. If player A defeats the evil overload, does the whole world
shut down? Of course not, that would result in every world
shutting down within a week of its inauguration. Instead, the evil overload somehow reappears just
in time for player B to defeat him.
In
the case of defeating the evil overload, infinite resurrection isn't such a bad
problem because players leave soon after the overlord's demise and never notice
the fact that he hasn't really died. (Of course, players know on an
intellectual level that he can't die, but it's nice to have the illusion.)
The
problem arises in the more mundane victories, such as when the player rescues
NPC Sally's cat from the tree in which it's stuck. After player A rescues the
cat, it suddenly manages to get itself stuck in the tree again so player B can
rescue it, ad infinitum. This loop is fine so longer as player A never visits
Sally again. However, if player A does see sally again, one of the following
things happens:
None
of these are really good solutions. The player knows they must happen, but they
ruin immersion and make
the virtual world feel more like Disneyland, where every one is put on a
conveyer belt to watch pre-canned entertainment.
The problem with (most) virtual worlds is that they don't
change. They can't change because change would ruin the experience for other
players.
Change
in virtual worlds
Inquiring
how a virtual world can end has led to the problem of change, since often the
act of getting to the end requires change. Most virtual worlds cannot change
though. What's the solution?
Stories,
of course, are based around change. No matter how good the writing and plot
are, if there's no change in the story, it feels unsatisfying. In a story, change occurs in several
places: The protagonist, major characters, the world, and the reader.
For a discussion about change in the reader, see Junk
food entertainment.
For
example: In most stories, the protagonist "finds himself". Major
characters will be affected and emotionally changed by the protagonists
actions. The world will be saved from an evil overlord (in a fantasy setting at
least). And, in a really good book, the reader will walk away slightly changed
by the experience.
Two
forms of stories do not
involve change. The way that they manage not to change is interesting:
How
does one apply this to a virtual world?
Change
in the protagonist (and the player)
In a virtual world, the protagonist is almost always the
player. Some players will role play and understand
the difference between the two, but they are very rare. Game designers are
aware of this and don't force players to role play. Occasionally, a game will
force the protagonist to be a distinct personality, but the effect is jarring.
(I have played a few adventure games where the protagonist has a self that
changes like it would in a story; to me, it feels like control is being
wrenched from my hands.)
Because
the protagonist is the player, and the protagonist has no mind of his own, the virtual-world protagonist cannot
be changed mentally, a key aspect of change in a story.
Instead, change must be funnelled into physical differences, or the character's
relationship in the world. CRPGs and MMORPGs go overboard with these two kinds
of changes; as the player character advances from level 1 to level 100, they
become infinitely more powerful and wealthy. Perhaps MMORPGs overcompensate for
physical change because mental change is impossible?
Unfortunately, I'm at a loss for words... The word
"mental" doesn't convey exactly the right idea. A character's ability
to learn skills like "Swordsmanship" could be considered
"mental" change, but that is not what I mean. I originally used
"emotional" change, but it didn't convey the idea properly either.
It's
not exactly true that the character can't be mentally changed; the player can
be. If the players
emotions and outlook on life are changed, then so to are the characters.
This line of thought leads back to Junk
food entertainment.
Change
in major characters (other than the protagonist)
In
a story, major characters also change, often as a result of the protagonist's
actions.
How
about in a virtual world?
In
a multiplayer virtual-world, there are two types of "major
characters". There are other players characters that are important to the
player, and there are non-player characters (NPCs).
The
other player characters can change just as the protagonist can change. Problem
solved.
The
NPCs, on the other hand, are a tricky matter, which gets back to Sally (whose
cat is always stuck in the tree):
1.
If a NPC is mentally or physically changed
by the player's actions, then the change would logically be apparent to every
other player. This effectively means that Sally's cat can
only be rescued once, by only one player, making content creation very
expensive.
2.
NPCs are usually written to be static.
More sophisticated change would require AI that includes memory, goals, and
emotions. Contemporary MMORPGs do not support such AI.
3.
Even if NPCs were given AI, their intelligence and emotional
complexity would pale compared to the illusion of intelligence and emotion
conveyed in stories, let alone real life. Changing an AI that's
obviously a bunch of algorithms isn't very satisfying.
MMORPG
NPCs, however, do not change. In a MMORPG, the only characters that can change
are other player characters, so the only meaningful interaction is with other
players. (Of course, NPC monsters can be killed, but after one has slaughtered
10,000 orcs, all meaning has disappeared.)
Later,
I'll discuss some tricks that can allow NPCs to change. They are only tricks though.
Changing
the world
If
a virtual world doesn't allow for important NPCs to change, then how is the world supposed to change
from the start of the player's experience to the end? In a
single-player game, changing the world over time is easy for the developer
(relatively speaking).
MMORPGs, however, can't allow the world to change as the
player works his way towards "the end" of his experience.
The evil overlord can't burn down a village and kill all its inhabitants part
way through to "the end". Either the village has always be burned
down, or it never will be, since some players won't have gotten to the
village-burns-down content while others have.
A
work-around that I've seen used is to have villages closer to the evil
overlord's castle be burnt down, still smouldering as though the destruction
had just happened. The
physical distance between the player character's starting point and the evil
overlord's castle (the ending point) correlates to the player's progress
towards the ultimate goal. The further east the player moves
(towards the overlord's castle), the closer to the goal he is. To hide this
simple rule, some games, like Dungeon Siege (single-player CRPG), make the path
look more like an intestinal tract. The disguise is easy to see through. While
the correlation between space and progress does often work, it isn't a great
solution.
Solutions
to change
Simply
put, virtual worlds with multiple players and hand-created content cannot
change. There are some tricks to work around this, and even allow the illusion
of change.
First,
I'll list some tricks that I've seen MMORPGs and single-player games use:
Some
other possible solutions exist:
Back
to "the end"
The
reason I digressed from my discussion about the "end" of the game and
talked about change, is that the two are interlocked. If the NPCs and world
cannot change, then any ending will be unsatisfactory because when the player
reaches the end he won't feel like he has accomplished anything. Even when
being entertained, people like to feel as if they (or the character they're
watching) have accomplished something.
To
make an overly-simple heuristic: To
figure out what quests/content should be in a virtual world, first determine
what "the end" will involve. How should the player's character have
change? How should the NPCs change? How should the world change? And how should
the player himself change?
Once
you know the ending, you can filter all your potential content/quest ideas
through these requirements. Does getting Sally's cat out of the tree really
lead to the overthrow of the evil overlord? If it doesn't, then get rid of it
or change it so it does; maybe Sally rewards the player with an important item
or tidbit. Maybe Sally's cat has a magical collar that the player can pilfer,
or go back later an ask to borrow from Sally. Maybe Sally's cat shows up at the
end and pushes the evil overlord over the cliff into a pit of boiling lava.
(... I don't think so.)
While
such filtration of content/quests is necessary, it is not sufficient. If the
player doesn't see the world changing as they complete their quests then they
will feel impotent. If the evil overlord doesn't wreak havoc on the world in
front of the player's eyes then the evil overlord will feel impotent. To
prevent this impotency, the NPCs
and world must change. Since, in a multiplayer world, the NPCs
and world cannot fundamentally change, some clever tricks must be used to hide
the static nature of the world.
Back
to "the beginning"
Stories
spend the first part of the book introducing the protagonist and setting up the
situation so that the protagonist wants to reach "the end". The
beginning of the story must answer the question: Why does the protagonist want to
overthrow the evil overlord?
A
virtual world with an ending must likewise answer this question.
As
we all know, the author of a story choses (or designs) a character whose
motivations include a desire to reach "the end". Frodo Baggins wanted
to defeat the evil overlord Sauron because he was a nice guy who wanted to
protect the shire, and because he was forced into to... no one else wanted to
carry the ring except Boromir.
In
a 10-hour virtual world, the player's character will be given a specific
identity and reason for being the protagonist, such as a choice between playing
Batman, Robin, Bat-girl, or Cat-woman.
A
1000-hour virtual world (a MMORPG) lets the user choose their own identity and
customise it to the Nth-degree. After all, the player will be stuck with that
character for an awfully long time (unless there is permanent character death).
A
100-hour virtual world (an anti-MMORPG) will be in-between. The player may be
given a choice of character templates who might wish to defeat the evil
overlord, and reasons for their disliking him. The player will have enough
choice in his character so that he will be able to choose abilities that he
likes (some people like fighters, other thieves, etc.) while still having a
character with motivation and ability to go after the evil overlord. Characters
must also have enough variety that when two players meet they won't be twins.
Players
don't need to limit themselves to one character though. Most MMORPGs already
have a tradition of several characters per player, so the same is possible in
an anti-MMORPG.
Multiple
characters might even be desirable... they could be used in a
"karmic" manner. Perhaps the first character the player creates is
limited to a peasant. Once the character has completed a set of quests specific
for the peasant "class", the player is informed that he can create a
"freeman" character. The whole virtual-world experience could be a
series of "reincarnations" from peasant, to freeman, to noble, to the
overlord himself. Maybe the peasant will think the overlord is evil, while the
overlord will think himself good. This would make for an interesting plot
twist. Stories use the related approach of jumping into different characters'
heads, but a virtual world could do a much better job by having the player live
each of the characters' lives.
Conclusion
If
a virtual world is only 100 hours long, it will have an ending. If it has an
ending (and even if it doesn't), the NPCs and world must change to be
satisfying. Traditionally, MMORPGs have neither endings nor change, while
single-player computer games have both endings and change.
Ultimately,
a 100-hour virtual world starts looking a lot like a single-player adventure of
CRPG game except that players will encounter other players wandering around
inside the world. They will be able to chat and team up with other players, but
are unlikely to enter into PvP conflicts.
Is
this viable? I don't know.
Maybe
I'm barking up the wrong tree. Maybe the solution is to copy what's already
being done: Those players that want a responsive world or 100-hour experience
should play single-player
games (adventure or CRPG), while those wishing a longer
1000-hour experience should play a MMORPG.
Maybe any attempt at an in-between is futile.
There
are some advantages to an "in-between" solution though:
24 November 2004
by Mike Rozak
A
few years ago there was a movie about a corn farmer who heard a voice in his
head saying "Build it, and they will come." So, just like
any normal person would do, he built a baseball field in his corn field, and
teams of ghostly baseball players showed up to play.
"Build
it, and they will come" is also an implicit design model of many virtual
worlds, where authors build a virtual world and hope that (a) people will just
wander into the new virtual world, and (b) some of the wandering people will
like it enough to stay. Of course, neither of these statements are necessarily
true; A virtual world needs advertising to attract players, and good design to
keep them.
One
question which virtual world designers don't seem to ask much (at least
publicly), is "Who
will come?"
Maybe
they don't ask because the answer is obvious: 20% male teenagers and 50% 20-something males.
That's the trend with MMORPGs, and to a lesser extent, single-player computer
games. (The other 30% are divided amongst females and aged 29+ males. See http://www.nickyee.com/daedalus/archives/000194.php
for the numbers.)
Why
are teenagers and 20-something males so attracted to MMORPGs?
Or,
to reverse the question: What
features would be ideal to attract teenagers and 20-something males?
(NOTE: There are quite a few holes in this whitepaper's
arguments. I almost didn't put it up, but I think it's worthwhile despite the
holes.)
Attracting
teenagers to virtual worlds
When
I sit down and list what features would most attract a stereotypical
teenager, here's what I come up with:
MMORPG
features are a great match for male teenagers. Despite the monthly fee, around
20% of MMORPG users are male teenagers.
20-something
males
The
other demographic that seems to be abundant in MMORPGs in virtual worlds are
20-something males. I want to redefine the category slightly, into
"competitive adults" and "non-competitive adults".
Competitive
adults are mostly male (very few females); with a tendency towards being single
males, aged 20-29. They are Richard Bartle's "killer" and
"achiever" player types.
Non-competitive
adults are both male and female, and fit into the "socialiser" player
type.
Married
players can also be grouped into competitive and non-competitive adults, but
when they have children (which is a fairly common result of marriage), their
free time plummets and they are less likely to play virtual worlds.
Competitive
adults
The
typical competitive adult in a MMORPG is a 20-something single male. If I
wanted to create a virtual world for stereotypical
competitive 20-something males I would do the following:
MMORPGs
do pretty well covering the requirements of competitive adults. They do miss a
few areas, most noticeably being the lack of real women (although virtual ones
abound).
Non-competitive
adults
Non-competitive
adults are male or female, aged 20+. Most of the gameplaying kind are single,
so I'll focus on them for the stereotype.
Non-competitive singles
have very different requirements than competitive adults...
It's
obvious why competitive players like virtual worlds: Competition with real
people (as opposed to AIs) is more intense and meaningful. They cannot get the
same satisfaction from playing a single-player game.
Why
would a non-competitive person wish to endure the technical headaches and
financial costs of playing a virtual world?
Socialisation,
of course.
I
suspect the main reason that many non-competitive singles would partake in
virtual worlds, as opposed to playing a single-player computer game, is to meet
other singles with similar interests, preferably of the opposite gender.
Entertainment is also important, although I couldn't say which is more
important, entertainment or socialisation.
You
could argue that non-competitive singles don't behave this way at the moment,
and I'd agree. However, if a world were targeted at them, and it made the
obvious marketing choice of creating a shard
per city, then non-competitive singles would use virtual worlds
as meeting places.
If
you agree with this hypothesis, then a virtual world designed for
non-competitive singles
would have:
Married adults without children are
more likely to use virtual worlds as an entertainment than a virtual nightclub,
although many will still be interested in meeting people. Married adults may
have less time to play a virtual world, though.
Those
with children
are unlikely to have any time at all to play. If they do have time, they may
wish to spend it online with their children. (See below.)
Other
age-groups
Just
in case you haven't noticed, I just segregated the user-base into different
age-groups and figured out what a stereotypical
member of that age-group would want in a virtual world. Stereotyping players
based on age group divides the population into:
That
only leaves the "children" stereotype to discuss...
Children
(and their parents)
Children
are not customers in their own right since their parents inevitably do the
purchasing. As a result, both the children's and parents' interests need to be
taken into account when designing a virtual world:
Other
stereotype categories...
Of
course, stereotyping your market based on age will only get you so far. It's
better than nothing though, as long as you don't believe your conclusions too
much.
Other
divisions for stereotyping target markets exist, each with their own ways of
attracting specific users:
Distance
from reality
Most
MMORPGs are fantasy or science-fiction based. This is interesting, because most
television shows and movies are based on near-reality...
Let
me explain what I mean by "near reality" and a few other definitions:
Far-reality
includes:
The
reason why having most virtual worlds based on fantasy or science fiction
realities is surprising, is because most adults don't seem to like realities
that are too distant from "reality". (Children don't seem to mind
though.) Just take a quick survey of television shows and movies... Very few TV
shows are based on reality, since that would be boring. Most are cop shows,
hospital shows, and reality-TV shows, which are all near reality. A few
occur in the near past, the near future, or include minor amounts of magic and
alternate realities.
Most
television shows and movies plant themselves firmly in a neighbouring reality.
Very few venture further...
Basically,
the further away an
invented reality gets away from "reality", the less mass-market
appeal it has, which is why virtual worlds based on fantasy and
science fiction are surprising. (Also surprising is that reality-based virtual
worlds, like the Sims Online, have done quite poorly.)
I
suspect there are reasons why fantasy and science-fiction are the preferred
virtual world though:
So
what does this have to do with "Build it and they will come"?
I
suspect that if you build a world based on near-reality, you'll get one type of
player. A world based on distant realities, such as a dream world, will attract
a completely different type of player.
You
can boil this analysis down even further into a two-dimensional graph, with one
axis being the amount of alternate reality in the world, and the other the
amount of change to the laws of physics. The further from (0,0) a virtual world
gets, the fewer adults will be interested.
Conclusion
Build
it, and they will come... Yes, but you need to know who is "they"
before you can build it. A baseball field built in a corn field will attract
ghostly baseball players. A golf course in the Australian outback might attract
entirely different sorts.
Most
contemporary virtual worlds are built to attract:
1.
Teenagers and 20-something competitive male
players.
2.
Technically minded players (as opposed to
artistically minded).
3.
Players that like a weakly alternate world
(fantasy or science fiction) with some weak changes to physics (magic and
spaceflight).
Virtual
worlds designed for these demographics have attracted the largest populations.
Worlds
targeting other demographics have been less successful:
Demographics
affect more than just the number of players that will be attracted to a virtual
world...
The
specific activities that the world provides are affected. For example, a
car-racing sub-game would appeal to teenagers (because they don't yet spend an
hour a day driving their car to and from work) and competitive adults (as a
race). To attract people interested in alternate-reality you'd need to change
the car to a spaceship, submarine, or battleship. To appeal to alternate
physics crowd, the spaceship would need to warp space as a defence, or employ
other non-Newtonian physics. To appeal to artsy types, players would need to be
able to customise the car (or spaceship) with accessories and paint-jobs. (See Virtual
World as Platform and The
attraction of impossibility.)
The
demographic also affects how long a player will be willing to spend. Teenagers
will want longer games than most adults. (See Steady-state
approximation.)
The
sub-games and duration of a virtual world, in turn, affect everything about the
world.
Maybe
asking who "they" are is a very important question...
5 December 2004
by Mike Rozak
In
The
Anti-MMORPG, I concluded that a virtual world with 1000
hours of content wasn't viable, not just because of the expense, but also
because only a small percentage of the original player base would be around by
the time the 1000th hour of content was reached. After all, if only 80% of the
users finish each 100-hour chunk (which is a high guesstimate), then only 10%
(0.8 * 0.8 * ... * 0.8) will make it to the end, since each 100-hour chunk of
content relies on the previous 100-hours.
However,
I just thought of an out... one used (albeit poorly) in many virtual worlds:
A virtual world could be composed of independent but
intertwining storylines. After all, many
novels occur in New York City, but they certainly do not require readers to
have read prior NYC-novels.
Some
MMORPGs already use a similar technique: Evil characters begin in Evil-land,
and good characters beginning in good-land. Evil characters and good characters
each experience different quests, and consequently different storylines.
This
division potentially allows the game developer to sell two packages: one called
"Be a good guy" and the other called "Be a bad guy". Each
one could be 100-hours of content, and a satisfying experience (with an ending)
in itself. Players that really liked their first experience in the virtual
world, could purchase the rival package and get another 100-hours of enjoyment.
Those that had enough after the first pass wouldn't feel like they were missing
out on anything, just as someone who has failed to read all of the novels based
in NYC finds it easy to forget about NYC and read LA-based stories. Content
development is cheaper for the virtual world company since it can use some of
the content from good-land (like models of good races) in evil-land, and vice
versa.
Current
virtual worlds don't take full advantage of intertwined stories though:
So
the next time you're
designing a monolithic virtual world where any player can go anywhere and do
anything, consider the alternatives. It's possible to have a
virtual world with thousands of hours of content, divided up into shorter
storylines targeted at specific personalities.
5 December 2004
by Mike Rozak
Virtual
worlds are commonly categorised into those that a player vs. player
(PvP), or player vs.
world (PvW). In a PvP world, the fun comes from interacting
with other players, using the world as a medium for the interaction. In a PvW
world, the fun comes from interacting with the world that the author has
created.
A
third category of world exists, which is not so much "versus" as "creates" or
"changes". Many players like to create objects in a
world, and ultimately change the world. Worlds such as "A Tale in the
Desert" and "Second Life" are often about creation.
Furthermore,
several different elements of a virtual world can be used to entertain PvP,
PvW, or creation players. These are:
I
created a graph where one axis is a combination of PvP, PvW, and creation,
while the other axis involves the elements of the world:
|
Player
interacts with other players and their creations |
Player
interacts with the authors and their creations |
Player
becomes an author and creates elements of the world |
|
|
People:
Directly |
Chat, bulletin
board |
Posts on bulletin
board for feature requests |
Players cannot
create other players, although they can recruit them. |
|
People:
Characters |
PvP combat,
economics games, role playing |
Some games have
deities and major characters played by the live team |
A service that
allows players to create interesting characters for other players? |
|
NPCs:
Hand generated |
Attacking a
player's pet or henchmen |
Conversations with
NPCs, and quests |
Allow players to
acquire pets or henchmen and "program" the AI for them. |
|
NPCs:
Automatically generated |
VWs currently don't
allow players to create automatically generated NPCs, such as guards or
armies, but they could. |
Monster bashing |
Allow players to
hire and program automatically-replaced guards and monsters for their private
castles and dungeons. |
|
World:
Hand generated |
Players enjoy other
players' housing or (in the case of Second Life) player created content. |
Enjoying the
world's puzzles, backstory, 3d models, etc. |
Allow players to
create housing, sculptures, artwork, and even object scripts. |
|
World:
Automatically generated |
VWs currently don't
allow players to create automatically generated world content. |
Wandering around
the wilderness (usually automatically generated). |
Allow players to
create the code that creates the world. (I doubt many players will be this
skilled, and any that are will be hired by VW companies.) |
Questions...
So
what does this mean?
I'm
not sure. However, the chart raises some questions, such as:
Can
they co-exist?
Another
question that arises is whether players who want different experiences can
coexist? Can a world be both PvP and PvW? Can a PvP world allows players to
create content? Or is PvW and a creation world possible?
In
an attempt to answer this question, I listed out major design decisions that
would be beneficial for a PvP, PvW, and creation-based world.
Player
vs. player
To
make a player vs. player world you need:
Player
vs. world
To
make a player vs. world game you need:
Creation-based
world
A
world that fosters creation would have:
PvP,
PvW, and creation in one world... the compromise
Unfortunately,
putting PvP, PvW, and creation in the same world produces conflicts, some of
which are unsolvable. May virtual worlds try to cater to all three and produce
a familiar compromise. (Items in red reflect serious conflicts between PvP,
PvW, and creation.) :
The
feedback problem
Creating
a world solely targeted at PvP, PvW, or creation removes these conflicts, but
does a world with fewer conflicts lead to an uninteresting world, as Richard
Bartle has observed?
The
PvP, PvW, and creation conflicts illustrate an even larger problem in virtual
worlds:
A virtual world attracts certain personality types.
Because people in a virtual world can see and interact with one another, the
personality types interact and often positively or negatively impact the
personality types attracted to the world. This sets up positive and negative
feedback loops that are difficult to predict. Furthermore, the personality
types of the existing player base affects what features the developers are
asked to implement.
To
use a traditional example: I like watching animated movies like Shrek. However,
I don't like watching them in theatres because I have to put up with young
children crying. Therefore, I watch Shrek at home. In a virtual world, I can't
help but invite all the other viewers into my living room, so to speak. Would I
watch Shrek if I couldn't detach the experience from the crying children? Would
some people, such as other children, be attracted to Shrek more because of the
other children than the actual movie?
According
to Richard Bartle's experience, a world with only killers (a sub-set of PvP)
will tend to die out, while a world with PvP and PvW players will be more
stable. Similarly, worlds with only socializers tend to die.
So,
while it's possible to design a world exclusively for PvP, PvW, and creation,
such designs may not be optimal. Worlds that attract a combination (such as EQ1
and WoW) seem to have more players than specialised worlds (such as WWII
online, Uru Live, and Second Life). (EQ2 seems to be solely PvW; it'll be
interesting to see how many players it attracts.)
5 December 2004
(Revised 13
December 2004)
by Mike Rozak
I
had some more thoughts about my essay on Choice.
The choices I discussed last time were choices that occurred within a quest.
What about the choices
that glue quests together? How are such high-level choices
designed?
Weak
choices
The
standard MMORPG is topographically designed to be a large landscape with hills,
mountains, rivers, and oceans. Within this landscape are gateways (aka: doors,
dungeon entrances) to smaller, interior worlds. The interior worlds have walls
and doors that restrict movement.
The
"choice topography" for a MMORPG is similar to its terrain. At any
point in the outdoor landscape, the player has the choice of moving NSEW. In
the outdoors, players don't have many more "valid" choices beyond
these four directions... Why?
Don't
forget, valid choices are those that can significantly
affect the outcome of the experience.
Other
valid choices are occasionally available: For example, a player may choose to
sit around and heal up. (Note: Healing rates in one region of the world are
almost always identical to everywhere else, so healing is location-independent.
Wouldn't the choice to sit and heal be more meaningful if certain parts of the
world produced better healing? Maybe a character that sits in the shade by a
pleasant stream would heal up quicker than on sitting out in the sun. A player
would then have to chose where to heal, not just to heal.)
One
would think that wandering monsters provided a choice about whether to attack
them or not, but monsters (and other players), are location-dependent. They
exist at a point in the 2-dimensional terrain, and only react to the player's
character when he is a short distance away. Because monsters' AIs always seems
to attack, you could claim that the choice of moving north towards a monster is
the same as choosing to attack the monster. And, you could further argue that
the choice of moving north to attack orc A, is essentially the same as moving
east to attack orc B, except that at the end of the combat, the character has
either moved north or east of his current location.
MMORPGs
further limit choices by logically placing like monster species in the same
region of the world. This means that the part of the virtual world populated
exclusively by giant rats only allows players to move and avoid attacking a
rat, or move and attack a rat. If there were giant hedgehogs, flying squirrels,
and a handful of other nasties, the player would have more choices. (Assuming
that attacking the other monsters actually took different skills and
strategies.)
So,
with a few exceptions, each point in the wilderness allows the player a choice
of moving north, south, east, or west. Attacking a monster is implicit in a
PC's movement, and healing really isn't that much of an issue.
A
large world will be 100 km x 100km, or 100,000m x 100,000m. Realistically,
every 10m x 10m section is a choice of location, so a wilderness map has 10,000
x 10,000 choice nodes where the player can chose to go NSEW. Even though a
player only has four choices at each node, with 100 million nodes, they still
have a lot of choices.
I
claim there aren't really that many choices though. Let me postulate a rule:
A choice whose consequence is easily and reliably
undoable is not a choice.
Since
the character can quickly walk east, and then just as quickly return to the
same spot by going west, moving east does not count as a choice. If a monster
were east of the player, or even if a monster might be east of the player, the
choice might matter. However, in the wilderness, a player can see the monsters
long before they're forced into combat, so there is no chance that the monster
"might
be east of the player". A player almost always choses to engage or not
engage a monster.
Of
course, if the player walked 1 km to the east, that would be a significant
choice because walking 1 km east and then undoing it by walking 1 km west is a
lengthy process. If this is the case, which eastward-step was the one that was
the choice? (Aka: Which straw broke the camel's back?)
Oops...
I'll rewrite my rule:
A choice whose consequence is easily and reliably
undoable is a weak choice.
Therefore,
while the MMORPG wilderness provides players with billions of choices, they are
all weak choices,
and don't count for much.
Interior
exploration is slightly better though, because a monster could be hiding around
the corner, and doors might only let players pass through one way. Still, the
choices are still fairly weak.
As
a general rule, any
place in a contemporary MMORPG that can be gotten into can easily and reliably
gotten out of. Unless, of course, the user makes an
exceptionally stupid decision and runs head-long into a dungeon that's labelled
as "very dangerous". Chris Crawford argued that providing players obviously stupid
choices don't count either, since no one will ever choose them.
(Incredibly stupid choices also lead to instant death and an end to the story,
which likewise invalidates the choice.) I tend to agree.
Just
as an aside, MMORPGs also weaken other choices beyond just movement:
String
of pearls
If
you have read any books about adventure game or CRPG design, you'll have read
about "the string of pearls" approach to design. They define a
"pearl" as any region of the world that PCs can freely wander around.
My
definition of a
"pearl" is any region of space where the choices to move about are
all weak. The "string" part occurs where the player makes a choice that cannot
be easily undone.
An
adventure game, such as Syberia, used the string of pearls approach. The first
pearl of the game involved figuring out how to get a clockwork train to
operate. When the player finally got on the working train she entered the
string, which was a cut-scene that placed the player in a new pearl. This new
pearl occurred at a university; there was no way for the player to go back, and
(once again) to go forward the player needed to get the train moving again.
Pearls can have more than one exit;
Syberia could have had an airplane that would also allow the player to leave
the pearl. But if there are several exits from a pearl, do they end up in
different locations?
If
they wind up in different locations then content is wasted since the player
cannot go back (except by replaying the game). If the strings all quickly end
up in the same location then what's the point of the choice?
The
laws of choices resurface... if there is more than one exit from a pearl then
the multiple exits form a choice. Since it is a choice, it most be a valid
choice. Strings leading to dead end pearls are not choices. Strings that
quickly reconnect to the main path invalidate the choice. Etc. The strings must
lead to significantly different experiences, even if they ultimately reconnect.
A choice which moves the player into a new pearl is a
high-level choice because the choice results in the loss
of significant amounts of content that the player will no longer be able to
access. (Other types of high-level choices exist, which I'll discuss later.)
The pearl is not always a physical location; sometimes it's as ephemeral as a
choice of following the dark side or light side of Star Wars'
"force".
Of
MMORPGs, pearls, and other things
If
you acknowledge that MMORPG geography is composed of weak choices, and that a
pearl is a collection of weak choices, then contemporary MMORPG's are just a single pearl.
They have no high-level choices in their topography.
Not
quite...
MMORPGs
do have high level choices. Most of their choices are mid to low-level however,
like choosing to fight a monster of flee from it. Most high level choices in a MMORPG
don't come from the content, but from the social relationships in the MMORPG.
Saying or doing the wrong thing (or right thing) to another player could lead
to irreversible changes in the player's experiences.
To
use a fairy-tale example: Being rude to a needy low-level player could turn out
to be a serious mistake when months later the low-level player is now the
uber-player and looking for revenge. (Technically, this scenario presents an
invalid choice because the player has no way of knowing that the beggar in
front of him will become king. But that's life...)
Some
other important choices that MMORPG players make are:
Again,
MMORPGs come up fairly weak in the choice department. Social relationships are
obviously strong choices. A player's choice of race, class, and realm are also
important, but they're invalidated because a player is forced to make them when
they first start. Some MMORPGs solve these problems by allowing players to
choose their class and realm later on in the game. One could also argue that
players commonly toss out characters, allowing them to make reasonable choices
on their second attempt.
Are
there any other high-level choices that MMORPGs could present to players?
None
of the ideas I presented are really new. I've seen them used in various places,
but it's nice to see them written down.
Conclusion
Choices
occur at many levels: Sub-games (like combat) are full of choices about what
combat move to use, etc. These choices are bracketed by the mid-level choices
in quests that I described in my last write-up, Choice.
Mid-level choices are affected by high-level choices that I just discussed. And
of course, high-level choices are affected by what game a player choses to
play.
For
a choice to be strong,
it must include consequences that cannot easily be undone. For a choice to be high level, it must be
based on major consequences that are very difficult to undo.
Ultimately,
choice is about consequences, and a virtual world is limited to one or more of
the following consequences:
Low
and mid-level choices frequently result in small losses of time and less
enjoyable experience.
High-level
choices, to be important, have to threaten larger losses. Unfortunately, for
one reason or another, none of the possible consequences get used. Losing 10's
to 100's of hours of play would be unacceptable to most players. Likewise, if
an "unenjoyable" consequence causes players to do 10 hours of math
problems, many will leave. Developers don't like having large chunks of content
lost to players since it costs money. And no developer will try and cause
players to lose friends.
As
a result, most MMORPGs
don't have any high-level choices other than those created by social
relationships in the world. Poor design doesn't help, either,
since some of the few major choices a MMORPG provides, such as the character's
race or class, are made by new players who don't have any inkling of the
consequences of their choices.
5 December 2004
(Revised 8/12/04)
by Mike Rozak
Seeing
as I'm thinking about running a small virtual world, I have spent many hours
figuring out areas where small virtual world providers (with 10K users) have an
advantage over large virtual world providers (with 1M users), and vice versa.
Obvious
tradeoffs
According
to my definition, small VW providers will have about 10K users and large VW
providers will have around 1M users. That means large VW providers have
approximately 100x as
many users as small VW providers; since small VW providers
won't be able to charge much more than large VW providers, that also means
large VW providers have 100x
the resources to invest in the world.
Despite
their small budgets, small VWs need to produce entertainment at a
"quality" that's on-par with large VWs. Luckily, "quality" is subjective, and
depends upon the user's perspective. Some people think great
graphics are a very important part of quality, while others do not. Some
players may rank customer support higher. Etc.
This
100-fold difference in resources forces small VWs to make the following design
concessions:
Other
cost-cutting techniques
Small
VWs have to cut costs as much as possible. Here's how:
Ramifications
of lower quality graphics, animations, and sound
Once
a VW decides to use lower quality eye candy, there are some ramifications:
Ramifications
of a niche market
A
niche market allows a small virtual world to incorporate:
Ramifications
of fewer players
Small
VWs will have fewer players, which allows for:
Advantages
of large virtual worlds
Amongst
all the advantages of small VWs, I also listed several advantages for large
VWs. Large VWs will have some other advantages:
Conclusion
Small
VWs will provide different experiences than large VWs, just as books are
different than movies. The key differences between small VWs and large ones
are:
In
other words, when considering what a large VW will be like, think of McDonalds and commercial television.
Small VWs will be more like neighbourhood
restaurants or novels.
5 December 2004
by Mike Rozak
Being
a programmer, I feel like I'm wasting my skills when I work on content, like
drawing 3D models, designing conversations for specific NPC encounters, etc.
I'd much rather be programming, since that's what I'm skilled at... and after
all, anyone can create content.
Of
course, this last statement is a lie. The truth is, that I know I'm very good
at programming, and I know I'm not as good at content creation.
Furthermore,
while not anyone can create content, virtually every virtual world will be
based on hand-created content. There
will be many more hand-crafted virtual worlds than there will be
automatically-generated ones because writing all the generation
algorithms and dealing with all the unexpected interactions is a very difficult
task for a very small market. (I expect that most players will prefer
hand-generated content.)
As
a programmer, I'd still like to create a world that is 99% computer generated.
It just sounds fun... The algorithms to automatically generate content are a
fair amount of work to do, but once they're done, the content flows out of them
without any effort. (Writing the code to automatically generate an X, such as a
tree or dungeon, is usually 10x - 100x as much work as producing a single
version of X. So, if a world has 100 or more versions of X, automatically
generated X's are a time saver.)
Contemporary
virtual worlds already use a bit of automatic content generation, most notably:
Of
course, I think big, and can go even further:
By
the time all these systems have been implemented, I would have a virtual world
that is functionally equivalent to most hand-generated MMORPGs, for only a
fraction of the cost.
Unforuntately,
computer-generated content has a major problem:
Some
people do enjoy playing in automatically generated worlds, such as Rogue, which
lets players adventure through a randomly-generated dungeon. Automatically
generated content has the following advantages:
Boiling
down the list, it's clear that automatically generated worlds attract the
following people:
I
suppose that, in the end, it's all a tradeoff, and the decision depends upon
what you're interested in, and what kinds of users you wish to attract.
9 December 2004
by Mike Rozak
A
3D computer graphics image is generated from hundreds of equations and
algorithms that simulate how light reflects and refracts off of objects. There
are so many equations and algorithms that it's easy to be overwhelmed.
However,
computer graphics has a single unifying equation, called "The
Rendering Equation". I could write it out, but most people would be
afraid of the math, so I'll use words instead. All the rendering equation says
is that the colour and brightness of a pixel is determined by the light
reflected, refracted, or shined into that pixel by the objects in the scene.
The light reflected or refracted by the object (to the pixel), is in turn
determined by light reflected, refracted, or shined by other objects in the
scene. Repeat as many times as necessary.
One
simple equation makes sense of all the 3D graphics buzzwords, such as
radiosity, ray tracing, global illumination, etc. The reason why all these
buzzwords exist, however, is because the rendering equation is very slow (to
draw). The buzzwords are just optimisation techniques whose complexity masks
the simplicity underneath.
The
authoring equation
In
an effort to understand how to write a good story, I read several books about
the subject. For the most part, the authors come to the conclusion that stories
are about conflict, and provided a few other tidbits of wisdom. When I read
game development books, the authors reiterated the same pearl of wisdom,
"Stories are about conflicts". The game-development book author would
often reverse the logic and conclude, that if stories are about conflict, then
conflict results in a story. (I suspect game developers like to reach this
conclusion because the it puts a game on a pedestal with Hollywood movies.)
Both
of these conclusions are wrong. Stories are not about conflict, although
conflict is an a very common technique in stories. And even if stories were
about conflict, adding conflict to a game would not necessarily result in a
story.
My
techie brain came up with a different definition for what makes a good story,
one that works on a more fundamental level. It involves three parts:
1.
At some point a potential reader must be
convinced to read the story. Therefore, the story's plot must be summed up into a few sentences
that are good enough to entice the reader to buy the book and begin reading.
If
you look at the back of any book, you'll see a such a summary. For fantasy books,
it's usually of the form: "Jelson, an unassuming Frobin, is minding
his farm when one day he discovers a magical shoe that changes his world
forever. Journey with Jelson as he meets up with the wizard Lootish, and
eventually confronts the evil overlord, Kalziban, who a shape-shifter of
devious cunning."
To
a potential reader, the proper names are absolutely meaningless, so they may as
well be replaced with the variables, X, Y, and Z. Once the names are removed,
it's easy to see how little information is conveyed in the summary... The
previous summary boils down to "X finds a magical shoe, meets wizard
Y, and confronts the shape-shifting evil overlord Z." I could easily
replace "shoe" with "M", since the shape of the magic item
is irrelevant too.
Is
this enough information to make an informed decision about the book? No.
With
so little information, people's brains do pattern matching against all the
other summaries they've seen and conclude, "This is just like story X,
but with a
shape-shifting bad guy." It is upon this "delta" over
previous works of fiction that readers make their choice, along with the
author's reputation. If they liked books about X, and they like the idea of a
shape-shifting bad guy, they'll buy the book. If they didn't like X or can't
see how shape-shifting bad guys makes for a more interesting story it gets put
back on the shelf.
I
find this conclusion a bit depressing, but that's the way it generally works.
Consequently, a story must be a derivative of other stories the reader has
experienced. If it is completely different than anything the potential reader
has experienced, the summary won't convey any information at all, and most
readers will put the book down. A few will persist, and they may eventually
tell their friends, "Try it, you'll like it," but this will produce a
very slow ramp-up in book sales.
2.
Once the reader has purchased the book, the
narrative must maintain
the reader's interest throughout the duration of the book.
That's all; one fundamental rule. Conflict is not necessary...
Conflict
doesn't hurt though, along with a number of other literary devices. For a list
of some devices, see Evolutionary explanation for
entertainment, The attraction of impossibility,
and books about how to write stories.
Basically,
a story keeps a reader's interest by introducing a number of characters
designed so that the reader will quickly like and/or empathise with them. The
characters' narratives are then followed by the story. The narratives are
designed to place the characters in interesting situations so that the reader
(as their friend) wants to hear what happens. (If the author doesn't manage to
make the reader like or empathise with the characters, then the reader won't
really care what happens, and won't stay interested.) The interesting
situations usually involve conflict, romance, challenges, danger, etc.
I
won't go into any more detail because so much is written about the subject...
just don't believe it when the authors say that conflict, or any other
technique is a requirement. They're not. What is a requirement is to maintain
the reader's interest.
3.
After the reader has finished the story, it's
distilled and shelved into long term memory. The long-term memory of the story must be satisfying.
If it isn't, the reader
won't purchase any more books by the author, and the reader won't recommend the book to
friends. An author who doesn't get return readers or recommendations
won't do well.
How
does a story produce a good long term memory?
1.
The first rule is to provide the story promised by the
summary in #1, along with the norms of stories such as happy endings.
Otherwise, the reader feels cheated.
2.
Make sure the story produces memories worth remembering
in long-term memory, preferably enjoyable ones. Long term memory tends to
remember dangers and
unique occurrences.
For
example: I saw Bambi when I was four. The only memories I have of Bambi are his
mother's death, the forest fire, thumper talking about wobbly legs, and all of
Bambi's friends leaving him to go "twitterfitting" (which I saw as
social abandonment). The rest of the movie need not exist as far as my memories
are concerned, although if the rest of the movie didn't exist then the scenes
that I remember wouldn't be as compelling, and I wouldn't have remembered them.
Long-term
memory also ferrets out
inconsistencies and parts of the story that seem to be irrelevant.
Consequently, a good story is very tight and all parts of the story are
important for the conclusion to be reached. After all, an important part of
(artificial) intelligence is identifying what data is erroneous or irrelevant
and tossing it out of the training set.
3.
Only make
the story long enough so that the important scenes have impact,
and so the reader feels like he's gotten value for money. A story that's too
long will either bore the reader so much they don't finish, or be so boring
that all they remember about the story is how bored they were. (How many books
that your high-school English teacher assigned only left memories of boredom?)
That's
it, a nice simple rule that would drive any literature major up the wall with
its techie-ness. Of course, simplicity is an illusion and the devil is in the
details.
If
you look at my three requirements from the right angle, they almost look like a
recipe for evolution:
1.
An individual animal must be attractive
enough to mate. After all, "good looks" are just a biological summary
of a creature's DNA.
2.
An individual animal must survive long enough
to breed, just like a story being interesting long enough to get readers to
finish it.
3.
A individual animal must successfully raise
its offspring... A story must create good long-term memories that cause readers
to buy the author's next book or recommend it to a friend.
Virtual
worlds and the authoring equation
If
the authoring equation is applied to virtual worlds (or any computer games),
the following conclusions can be drawn:
1.
The "summary rule" causes almost
all virtual worlds to become clones of one another since VW summaries must be
worded so that users get the impression: "It's like Everquest, but with better graphics and more
crafting." A virtual world that cannot be compared to its
predecessors so easily may sell in the long run, but at first its sales will be
slow.
2.
A virtual world must keep the player's
attention. I have written up several papers with thoughts about this: See Evolutionary
explanation for entertainment, The
attraction of impossibility, Stop the buffet,
Junk food
entertainment, Choice,
and Choice 2.
The
technique of using choice
as a way to keep a player's interest is important to point out
because it differentiates virtual worlds (and computer games) from stories,
which have no choice. If a game provides no choices, players will
(subconsciously) feel like they're reading a story, and a poorly-written one at
that. A choice-less game is like chocolate cake without any chocolate in it...
it's a let down.
Not
to sound too pessimistic, but a virtual world only needs to maintain a player's
interest long enough so that (a) they recommend the world to friends (see
below), and (b) they produce such strong social ties within the world that they
are reluctant to leave. After that, virtual worlds can get boring, as they
often do. Stories, because they are only recommended once they're completed, and
because they produce no social ties, must keep a player's interest all the way
through.
3.
A virtual world must also (theoretically)
leave a good long-term memory. However, contemporary
virtual worlds break my long-term memory rules in interesting ways.
Because
contemporary virtual worlds last so long, approximately 1000 hours, players recommend the world to friends
long before they're finished playing. People reading novels
rarely recommend them until they have finished with the novel, and if asked
about the novel part-way through they'll say, "It's good so far, but I'm
not done. Ask me in a few weeks." VW players cannot provide the equivalent
reply of, "I'm not done. Ask me in a year."
The
real reason why players recommend
VWs to friends before completing them is because they want their friends to be part of
the VW, since playing with friends makes the VW experience more
enjoyable. (This tendency also provides problems for a 100-hour virtual world,
as written up in The anti-MMORPG.
A person will probably play a VW one or two weeks before recommending it to
friends. It then takes the friends another week or two before they purchase
their own copy. By that point, the original player has finished 40-80 hours of
content. If the content is only 100 hours long, there won't be much point
recommending the VW to friends.)
A
VW author wouldn't want players reviewing the VW when they're finished with it
anyway... because virtual worlds have no endings, players usually stick with
them until long after they get bored, which means one of their long-term memories of the
VW experience is that it was boring. Players often whinge that
"Virtual world X was great when I started, but then the player base
changed and became full of idiots, and the live team ruined it by adding
feature Y." (Usually in more graphic terminology.) Did the player
really leave because of these changes, or because they became bored, and the
changes are used to rationalise their decision?
Conclusion
My
analysis about what makes a good story or virtual world seems a bit mercenary.
I'm basically saying a good story or VW is one that manages to attract
readers/players, and keep them interested long enough to form strong memories
so they'll recommend the story/player to their friends.
The
concept has evolutionary similarities and ties into memes,
which are viral ideas. Stories and virtual worlds are just very-long viral
ideas.
9 December 2004
by Mike Rozak
One
of my cousins had a huge
toy room with an order of magnitude more toys that my brother or I had.
Whenever I visited his house, I saw the toy room as nirvana, and thought
someone with that many toys would never get bored.
As
an adult, I know that conclusion to be false; kids can be just as bored with
100 toys as they are with 10 toys. In fact, I've come up with some fundamental
observations about toys:
1.
A toy room with more toys will provide more
entertainment, but the amount of entertainment is not a linear
relationship with the number of toys. Thus, twice as many toys almost always
results is less than
twice as much entertainment.
This
law is not always true:
o 12
toys received all at
once for Christmas will result in 4-8 weeks of entertainment
for all the toys. 12 toys received once
a month over the year will produce 1-2 weeks of entertainment
per toy, or 12-24 weeks of entertainment.
o Some
toys are synergistic. 1 Lego set provides 1 hour of entertainment. 2 Lego sets
provide more than
2 hours of entertainment because the pieces from each set can be combined in
new ways.
2.
A toy room is more fun when enjoyed
with a bunch of friends. Old toys are rejuvenated when
friends stop by to play.
3.
If an adult joins in the play, toys
can be kept fun longer, perhaps an infinite amount of time
longer. Unfortunately, adults cannot spend all day playing (or they get bored
of it), so adult-mediated play is a rarity.
The
reason why adults greatly improve a toy's fun is because:
1.
They have more experience, and are able to come up
with new ways of playing with the toy that children haven't thought of.
2.
They're an authority figure, and can direct the
imagination a bunch of chaotic kids into more sustainable activities.
3.
Their play is selfless. Adults make play decisions
designed to make play more enjoyable for the kids, not necessarily themselves.
Because adults cannot spend all their time
playing, they sometimes create scavenger
hunts, which a pre-programmed play sessions. A scavenger hunt
has the kids play with a series of toys in context to provide more meaning.
Scavenger hunts aren't as good as a real adult, but they're usually better than
a free-form toy room.
When I was 12, I began programming games on
the Apple ][ after I read an issue of Byte magazine that had an article about
Zork, along with the Basic program for a simple adventure game. Ever since,
computers have become an everlasting
toy for me. I could be locked up in small room, and as long as
I had a computer, I would keep myself entertained.
I
don't know if a programmable computer is the ultimate toy (for me), or if at 12
years of age my brain matured to the point of being able to entertain itself.
Virtual
worlds and toy rooms
As
I have stated in "Virtual
world as platform", I often think of virtual
worlds as being collections of sub-games. Or in other words:
a sub-game in a
virtual world = a toy
a virtual world = a
toy room
If
this is the case, then my observations about toys infer the following
observations about virtual worlds:
1.
Offering more sub-games will allow a
virtual world to hold a player's interest for longer, but not linearly.
Twice as many sub-games do not provide twice as much entertainment, unless:
1.
The sub-games
are rationed out to players over the course of time, such as
quarterly updates with new features, or limiting sub-games to quests.
2.
The sub-games
are synergistic: Combat is fun. An economics game is fun. If
the two are combined they're even more fun.
2.
A virtual world is more fun when one's
friends are around. To a lesser extent, having
unfamiliar players as well as enemies also makes them more fun.
3.
A virtual world with a game master
(filling the role of adult) is more fun than free-form play.
Game masters work well in tabletop role-playing games, but aren't economically
viable in most online virtual worlds. A few worlds have extensive game
mastering, and many have occasional game mastering. Most have employees that
they call "game masters", but who are really in-world product support
staff.
4.
A quest (or adventure game, or offline
CRPG) is the same as a toy-based scavenger hunt.
Quests are not as good as having a real game master around, but they're usually
better than free-form play.
5.
What is the everlasting sub-game for a
virtual world? Is it the act of creating a world? I don't
know.
Toy
categories
Over
the thousands of years that people have been inventing toys for children, they
have only created a handful of toy categories, between 10 and 20 depending upon
how you count. Some of the toy categories, as listed by E-toys, are: Action
figures, animals and stuffed toys, arts and crafts, blocks and building sets, dolls,
DVD and VHS, etc. Each category has thousands of variations. For example:
Action figures can be GI Joe, superheros, Bionicles, transformers, etc.
Virtual
world sub-games likewise fall into categories such as combat, economics games,
chat, puzzles, etc. These two can be subdivided into thousands of variations.
I
perceive several weaknesses in contemporary virtual worlds:
1.
They don't
provide enough sub-games, and put almost their effort into
combat. It's like a toy room with only a handful of toys. See Virtual
world as platform.
2.
In general, contemporary virtual worlds all
provide the same small
set of sub-games. Therefore, they cannot differentiate themselves by their
choice of sub-games. Instead, they differentiate themselves with eye candy,
genre, and by the details
of the sub-games. One virtual world will have GI Joe action
figures, while another one superheros, and a third will be transformers. Yes,
there's a difference, but not much of one.
3.
Most contemporary virtual worlds subscribe to the "sandbox"
theory of design, which postulates that if players are provided
with enough toys they will make their own fun.
Because
virtual worlds don't have many sub-games and they are just sandboxes, then
their only differentiator is item #2, the specifics of each sub-game.
I
am always looking for ways to differentiate, so I advocate more sub-games (#1)
and a break from the sandbox theory (#3), as well as unique implementations of
sub-games (#2). More sub-games and different implementations are easy enough.
However, to escape the
sandbox, I need to have a game master, but since an army of
game masters is too expensive, I need one based upon artificial intelligence.
Replacing
the game master
Being
a computer programmer, I try to solve every problem I come across by writing a
program. Obviously, I want to write
a program to replace a game master...
At
its most basic, a program to replace a game master (or adult) would pull out a
sub-game (toy) for the player to enjoy. After a fixed amount of time, the
computerised game master would distract the player and pull out a new toy.
Repeat the process. It's a simple idea that would only work if the players were
utterly stupid, which they're not.
I
can think of a few improvements though:
1.
Have the AI game master try to determine how much the players are
enjoying the sub-game. If they're still having fun, stay with
the sub-game a bit longer. If they're "looking" bored, either try a
variation of the game that the players haven't seen (such as having them kill
giant rats when they get bored of killing ordinary rats), or abort the sub-game
ahead of schedule and try different sub-game.
2.
Intelligently determine which sub-game
would be good to use next. If kids are getting bored with GI Joe
action figures then pulling out a Spiderman action figure might work, but a
much different toy, such as the spaceship, might work better.
3.
Find a better way to distract the
change in sub-games. If kids are becoming bored with GI
Joe action figures and all an adult has to go with is a flying saucer toy, then
have GI Joe and friends be abducted by aliens. From there, play with the flying
saucer. When the kids get bored of that, the adult can have the flying saucer
drop GI Joe and friends on an alien planet with giant trees that look like
lamps, or forget about GI Joe entirely and have the flying saucer be attacked
by a giant space dragon.
4.
Repeat.
This
is easier said than done...
Determining
if players are enjoying sub-games
An
adult easily knows when children get bored. The same goes for tabletop game
masters. A game AI acting as an game master would find this problem much more
tricky. To do this, an AI would need to:
Personally,
I prefer the "ask the players if they're having fun yet" approach,
but only ask once in awhile. To do this:
1.
Divide the virtual world into hundreds
of quests. (Most virtual worlds already do this.)
2.
When a player finishes a quest, ask them to rate it from 1 to 5.
The polling is a bit intrusive, but not that bad.
3.
Use various mathematical techniques (whose
names I have forgotten) to cluster players' preferences for quests and come up
with a model of what
type of quests a player would like. Amazon.com uses this for
recommending books, and while the recommendations aren't perfect, they're
pretty good.
The
selection process is a bit tricker than Amazon.com's because it needs to take
into account the player's location and his character's skill level, as well as
the player's overall progress in the "storyline" of the VW (if there
is one).
4.
Either recommend
these quests to the player, or somehow guide the player
character to the quests. For example: If a virtual world learns that a player
likes to rescue pet cats, it would have NPCs from the pet-cat-rescuing quests
seek out the player from miles around.
Some
people do not like being second-guessed though; The virtual world would need to
provide an option so players could turn off their personal game master AI.
Choosing
the next sub-game
In
order to choose the next sub-game for the player, the AI game master needs to
know:
1.
The player's frame of mind. Thus, the AI must
have a "theory of
mind" about what the players are thinking and what their
moods are. At its simplest level, if the player is obviously getting bored of
killing rats, swapping them over to killing giant rats won't be much of a
change. Having them solve puzzles or sail a ship would be better.
2.
What the players' characters can
handle. There's no point in bringing out the dragon
"toy" if it will instantly incinerate all the players' characters.
Likewise, forcing a thief PC to fight a NPC knight in an arena isn't too fair.
3.
Where the storyline of the virtual
world is leading. If the storyline dictates that the
players must meet up with the evil overlord's henchmen at some point, is now
the right time for him to appear? (If you don't believe in storylines then
ignore this statement.)
How
can AI solve these problems? I'm not entirely sure, but here are a few ideas:
1.
Manually classify each quest
(or sub-game), such quests with "lots of combat", or "lots of
puzzles". (Or maybe ask players classify the quest type when they finish
it.)
When
a player finishes one quest, determine what quests the player could undertake.
If more than one quest is available, try to recommend the quest that is the
most different from the last quest the player completed. (Of course, if the
player doesn't like puzzle quests, then don't recommend puzzle quests, even if
they'd make a good change from killing things.)
2.
When players complete a quest ask them how difficult they thought it
was. Plug the difficulty, along with the players' skills, into
some clever AI and produce a heuristic. When considering the quest for future
players, plug in their characters' abilities and guesstimate how difficult they
would rate the quest. If it's too difficult (or too easy) then don't recommend
it.
Some
players, with the same character skill as others, may still find quests too
difficult or too easy. The AI will need to guesstimate how skilled the player
is (as opposed to their character) in relation to other players.
3.
The quest's recommendation score is
also affected by how important it is for the storyline,
and whether it must be run in order for the story to proceed.
A
user may sometimes have to complete one out of two possible quests for the
storyline to advance; choose the one which the player would enjoy the most. For
example: To kill the evil overlord the player may need to acquire a cloak of
morphing. Since all players must eventually acquire the cloak, a virtual world
might provide 2-4 quests that result in the player getting the cloak, but
recommend only one of them to the player.
Which
quest the AI recommends next can also be affected by:
Transitioning
between sub-games
You
may not have noticed, but the examples I gave used quests as atomic units, not
sub-games. The reason is simple:
Producing AI to transition between quests is fairly
simple. Producing AI to transition between sub-games is very tricky.
To
transition the player from one quest to another, the AI merely needs to (a)
unobtrusively inform the
player that the quest exists, and if the quest is a long ways
away, (b) find a way to
get the player there. Both of these problems have simple
solutions, although more complex solutions would produce smoother (and less
obvious) transitions for players. For example: A really clever way to get a
player from point A (where they finished their last quest) to point B (where
the next quest is), is to use a small quest in-between that coincidentally
transports the players from A to B.
Transitioning between sub-games is much more difficult
because there are more transitions; a player may
transition from one sub-game to another dozens of times an hour. If these
transitions are not 100% smooth, players will notice them and become annoyed at
the artificiality of the system. Players will also notice transitions between
quests, but because a transition is only needed once every few hours, the
transition won't grate as much.
To
do a proper sub-game transition system, the game master AI needs to have a huge
expert-system knowledge base. For example: Lets say the AI determines that a
player is getting bored with killing orcs, and decides that a car chase would
be more fun for the player. How does the AI explain the transition? Do some of
the attacking orcs suddenly jump into a car and take off down the road? Where
did the car come from? Where do the players get their car? Can orcs even drive?
What if the players were attacking a heard of wildebeests? Do they drive? What
is a car doing in a fantasy setting anyways? What happens if the players decide
to keep the car they pilfered?
To
make things even more complicated... What happens if there are no roads nearby?
Does the AI invent a road? If it does, what will players think when they
suddenly see a road appear? What do players in a different group think when a
road suddenly appears, and only a few moments later a carload of orcs comes
hurling towards them on the newly invented road? What happens when the world is
filled with too many roads?
And,
that's not the end of it... What happens if the car-load of orcs, in an attempt
to escape their attackers, have a head-on collision with a dragon that another
group of players was attempting to kill? The dragon ends up with a broken leg
and must be put down, while the orcs are all flattened and rushed off to
hospital. The experiences for both the orc-chasing players and the
dragon-slaying players have just been ruined.
That's
not the worst: If the game world starts dealing with individual sub-games, it
should conceptually tie these sub-games together into quests. (It doesn't have
to, but people seem to like having overarching goals.) Creating automatic
quests is fairly tricky, and not something that contemporary AI is up to,
except for simple quests.
Automatically
generated quests
If
you read, "Automatically
generated content", you'll know that I like the
concept of automatically generating quests. I don't think, however, that such
quests will be very interesting, and they will pale in comparison to the
hand-written ones.
Automatic quests could easily be written and effectively
used for transition quests, however. In the
example I used above, where a quest is needed to get the player characters from
the city where one quest ends (A) to the distant city where the next quests
beings (B), an automatic quest would be a cinch... I can imagine a few
scenarios:
Of
course, players will realise that the quests are automated, but I don't think
they'll mind the occasional automatic quest.
Theory-of-mind
in quests
Even
though only a small percentage of quests can be automatically generated, every
quest can be tailored to its players by understanding what the players like:
Did
I go off topic?
How
did I get from toy rooms to game-master AI?
Simple
really...
1.
A toy room is a source of entertainment, but
it's much more entertaining when an adult selflessly partakes in the play.
2.
Virtual worlds are conceptually toy rooms.
They're more fun when a game master is selflessly intervening to make the world
more fun.
3.
Since game masters are too expensive to
employ, an AI will have to do. (Some
people may object to AI second-guessing their desires, so provide players an
option to turn off the AI.)
4.
Given the lack-of-sophistication of current
AI, the AI's functionality is limited to recommending quests.
5.
Important quests must be manually generated.
However, simple automatically-generated quests are possible, and are
particularly useful as "bridging quests" between the
manually-generated quests.
6.
Even important quests can be minimally
adjusted to take the players' likes and dislikes into account.
Some
additional thoughts...
If
several players frequently team up into parties, how does the AI handle each
individual's different motivations? Are there quests specifically designed for
teams vs. loners, and does the AI use its knowledge about the party?
If
an AI notices two players (or small parties) that seem to have similar habits,
does it try to get them to bump into one another? Is there a gender and age
bias? (See Build
it and they will come.) What if a player wants to play
alone?
Does
the AI attempt to produce conflict by encouraging enemies occasionally run into
one another?
Alternatively,
does the AI pretend it's Charles Dickens and have the same NPCs reappear from
time to time? Some favourite NPCs from the past could be kidnapped by enemy
NPCs from the past.
Do
the quests, with the help of AI, attempt to intertwine the players'
experiences? Maybe one quest will call for a character to steal an item from a
NPC. The NPC might then choose to hire another PC to get his item back. Which
PC out of 1000's is targeted for the retrieval quest?
Is
the amount of PvP in a quest also monitored? Does the AI remember how much PvP
a player likes and recommend quests based upon that?
How
does the AI attempt to create strong memories, as mentioned in The
authoring equation? Are some sub-games only brought out
occasionally in order to create an extra-sharp memory of them? Or are
combinations of sub-games used? Or the context? How are other players involved?
Are important choices necessary for strong memories?
How
are inconsistencies and extraneous quests minimised? The storyline that the
player selects is one method. (See Intertwined
storylines.) Are there others? Are there sub-themes to
the storylines that should be emphasised for specific players?
If
a player only follows recommended quests they will finish in (lets say) 100
hours. If they play all the quests in the world, play with several characters,
or try several storylines, they might get 300 to 1000 hours of entertainment
out of the virtual world. Does this solve the problem presented in The anti-MMORPG?
Does this make the virtual world too expensive?
The possibilities are limitless...
and that's the point. Adding a game master AI can significantly enrich the
world. Some players won't like it; they can either turn off the game master, or
go back to their sandboxes.
29 January 2005
by Mike Rozak
Since
learning about Richard Bartle's player models (Hearts, Clubs,
Diamonds, Spades: Players Who Suit MUDs), I've tried to
find or invent alternative player models, not because I think categorising
players as socialisers, killers, explorers and achievers is wrong, but because
having only one accepted player model imposes blinders/limits on designers.
It's like having a toolbox with only one tool.
In
the past year, I have come up with many categorisation schemes, but this one,
"The player pyramid"
is particularly interesting because it not only describes why players visit virtual worlds and how the
player types interact with one another, but it also implies a business model. (I
don't necessarily like the implications of the business model though.)
I
came up with the player pyramid when I combined disparate thoughts that I had
posted in two previous articles. The first article discussed player motivations
(from The
dream machine and The
attraction of impossibility), while the second was about
different population levels in virtual worlds (from Virtual
world spectrum).
For
those of you who don't wish to read the previous articles, here's a summary:
Combining
the two
If
you peruse the list of goals and fantasies listed in The dream
machine and The
attraction of impossibility you'll notice that the different
goals and fantasies which players wish to fulfil require virtual worlds of
different population levels:
The
pyramid
Categorising players by the population of the virtual
world that they need to fulfil their goals and fantasies produces a pyramid.
Most people are happy with linear fiction, forming the base of the pyramid. On
top are those people whose needs are met by single-player games. Above those
are the multiplayer gamers, game-like virtual world players, and world-like
virtual world players. Each group's population is progressively smaller:
|
World-like VW players |
||||||||
|
Game-like VW players |
||||||||
|
Multi-player
gamers |
||||||||
|
Single-player gamers |
||||||||
|
Linear fiction readers/watchers |
||||||||
(One
reviewer pointed out that the shape isn't necessarily a pyramid, citing the
popularity of MMORPGs in Korea.)
The
player pyramid reveals some interesting relationships between the player types:
Players at the top of the pyramid require that there be
players underneath them, or their
goals/fantasies won't be fulfilled. For the most part, players on the base of the pyramid
would rather not have any players above them because players
higher on the pyramid detract from their experience. This is a major source of conflict.
This
relationship is similar to Richard Bartle's observations that killers need to
have socialisers and achievers to prey upon, but socialisers and achievers
don't like the killers. Likewise, socialisers need achievers, although
achievers could care less about socialisers. In the player pyramid, killers
would be at the top, with socialisers below, and achievers and explorers at the
bottom.
The
bargain
The obvious solution to the pyramid's conflict is to
create virtual worlds that caters to players on the base of the pyramid, and
let the world-like players be damned. After all,
the top of the pyramid is a small market that does nothing for those below them
except make life difficult.
This
solution is already practiced:
At
first glance, all seems lost for players who wish to play in world-like worlds.
However,
world-like players do have something that players lower down the pyramid
want... real-world money.
It's
a matter of supply and
demand. If a world-like gamer wishes to have their goals and
fantasies fulfilled, they must have a larger population of players below them
who do not
want to be inn-keepers, kings, or outlaws. In a way, these other players are part of the
world-like gamer's entertainment. Even if players at the base
of the pyramid don't think of their experience as being entertainment for
someone else, they often resent the inconveniences imposed on them so that
world-like players can have fun. Given a choice, players whose goals and
fantasies are fulfilled at the base of the pyramid chose single-player games,
multiplayer games, or game-like virtual worlds, not world-like virtual worlds. Money
works wonders though...
One way world-like gamers can get a supply of inn
patrons, vassals, or victims is to pay them to play. Of
course, no world-like player is going to pay other players $10/hour to play a
role. However, they will (and do) subsidise them:
Some
other subsidised marketing models are possible:
Example
of design and business model
The
player pyramid implies some design constraints and business models for creating
a world-like VW. Here is one
example of how the design could be incorporated into a virtual world: (I have
chosen it because it's distinctly different than Achaea's or Second Life's
approaches.)
Of
course, very few (if any) of the specifics of the implementation are new ideas.
Ramifications
of "the bargain"
If
a virtual world design-team decides to have world-like players subsidise the
players below them, then there are ramifications:
Alternative
business models
The
player pyramid's business model isn't the only one available to virtual worlds.
A couple other well-known models exist:
Conclusion
In
a virtual world that subscribes to the player pyramid:
1.
Players on the top of the player pyramid
(looking for a world-like VW experience) require
a large base to fulfil their goals/fantasies. Those at the base (looking for a
game-like VW experience) would
rather not have any players above them.
2.
Players looking for a world-like VW
experience (on top the pyramid) will pay
for the game.
3.
Players at the base (those who prefer
multiplayer and single-player games) will be able to play for free (or very
cheaply), at the expense of having the inconveniences of a world-like VW
imposed on them.
4.
A large number of players at the base of the
pyramid is necessary to
attract and keep players at the top.
5.
The main purpose of content (quests,
dungeons, etc.) is to attract a steady stream of players into the base of the
pyramid, those who like multiplayer and single player
games. These players will only stay around for 100-200 hours, or until the
content is consumed. Thus, the content must be renewed every few years to
re-attract them.
6.
The player-vs-player
elements of the virtual world provide interesting ways for world-like players
to interact with one another and players lower on the pyramid.
4 March 2005
by Mike Rozak
I
am in the process of designing my virtual world. One of the questions I have
recently asked myself is, "What
happens after I ship?"
Of
course, I will need to spend time fixing bugs and handling customer issues.
But
what about new features?
New content?
Improvements to the graphics and sound engines? (A.k.a. "eye candy").
These questions lead to an even larger one: How long can I keep a virtual world alive by trickling in
new features, content, and eye candy?
I
have to update my features, content, and eye candy, since without the updates
my virtual world would die a fairly quick death. Once all the content was consumed by
current users, they would leave and never come back. New users
would appear, but, over
time, the outdated eye candy would reduce their numbers. (A
top-10 MMORPG with state-of-the-art eye candy has approximately 100 times as
many users as a top-10 text-MUD, with 25-year old eye-candy.) And even if I did
manage to keep my eye candy up to date, I'd eventually run out of people that hadn't yet visited my
world.
The
"ever-expanding" world
Historically,
virtual worlds release
frequent feature, content, and eye-candy updates: MUDs are
continually improved and expanded upon by their authors. MMORPGs release update
CD-ROMs once every 6-12 months, with new clients, rendering engines, 3D models,
textures, and sound effects.
One would expect a virtual world to live forever if it
produced sufficient updates, but they don't seem to.
Ultima Online came out around 1998, peaked at around 200K users a few years
later, and its numbers have been gradually declining for the last five years.
Everquest released a few years later, peaked at around 400K users, and its
numbers are gradually declining. I suspect the same has happened to text MUDs,
although I have no numbers.
Both
Ultima Online and Everquest have shipped many expansion packs to keep their
features, content, and eye-candy up-to date. As a result:
Yet
they still die...
In
fact, they're dying at a
more rapid rate than their player numbers would indicate. The
number of people that play virtual worlds has been growing at 30%-50% per year, yet Ultima Online
and Everquest have only managed to keep their populations constant.
If their populations were growing at the same rate as the market, they would
merely be "treading water". They aren't even doing that. What happens
when the virtual-world market growth stalls? If new players weren't continually streaming into the
market then Ultima Online and Everquest would be shrinking at a rate of 20%-30%
a year!
What
is going on?
Both games have deeper feature sets and are larger than
the latest-and-greatest. Everquest's eye
candy isn't even that
far behind World of Warcraft's. (WoW's eye candy is behind the times though,
and far behind Everquest II. EQ II has only half the players, partly because
its eye candy requires too high-end a computer.)
Digging
their own graves
Why
are Ultima Online's and Everquest's populations falling, let alone failing to
grow by 30%-50% a year?
Here's
my theory... Every time a virtual world releases an expansion pack, the
following happens:
1.
The cost of the game goes up
since players now have to purchase yet another package. A more expensive game
means fewer players. Companies
easily counteract this by rolling the expansion packs into
their main game box that new players purchase; only existing players need the
expansion pack.
2.
The new eye candy increases the user
base. Of course, some users don't have the
hardware necessary for the the new eye candy, but on the whole, eye candy is
beneficial unless it's too bleeding edge, like Everquest II's.
3.
New features are added, such as more player levels,
pets, and player housing. The
new features revive the interest of current players.
Potentially new players, however, find new features to be a negative since they
inevitably make the game more difficult to play. When too many features are added to a
virtual world, the learning curve is enormous and new users shy away.
To
make matters worse, the players entering the virtual world market today are
less "hard core" than previous waves of new players. Thus, they
dislike complex UIs and feature sets even more than the old players. So, not only do the expansion packs
dissuade potential hard core players, they are a huge negative for the newer
wave of non-hardcore players.
4.
The new content added by the expansion
packs are enjoyed by existing players because they have already completed the
old content. However, from the point of view of new
players, the virtual world already had 400 hours of content before the
expansion pack added another 100 hours. The virtual world now has 500 hours of
content...
The
problem with so much
content is that the virtual world becomes an insurmountable obstacle.
More content just makes it that much harder to "finish" the world.
For
example, I am not
a hard core gamer. I will play a game for 50-100 hours and then get bored. I
end up finishing half the games I play, and leaving the other games 30%-50%
from the end. If a virtual world has 400 hours of content, I'm only going to
get through 25% of it, at most. Adding another 100 hours means that I'll only
get through 20% of it, and will get bored soon after I've graduated from
killing rats to killing giant rats. When I play a RPG, I want to be killing the
evil overlord by the time I get bored, not just giant rats!
Again,
increasing the amount of
content is doubly detrimental because the newest wave of
virtual-world players are not
hard core. I suspect they have longer attention spans that I do, with most
playing between 100 and 200 hours.
Furthermore,
the more content a
virtual world has, the more expensive it becomes to upgrade its eye candy and
features. After all, if a world has 1000 different 3D models
for monsters, converting to a new 3D engine that supports more polygons and
bump mapping could require 1000 models to be rebuilt and retextured. Likewise,
adding a new feature could easily break one of the 1000 quests a world has,
requiring the testing of all 1000 quests after a new feature has been added. A
virtual world with only 500 monsters models and 500 quests has a much easier
time adding new eye candy.
Virtual worlds are digging their own graves when they put
out expansion packs because the expansion packs gradually
focus the virtual world towards an ever-shrinking class of niche players: Those
players who have already spent large amounts of time in the virtual world, and
new players with huge amounts of time on their hands.
However,
if virtual worlds don't
put out out expansion packs, they'll die an even quicker death...
It's like a war movie where the bad guy points a gun at the captured soldier
and makes him dig his own grave. Soldiers inevitably dig their own grave,
hoping that the delay will provide enough time for plot twist to occur.
The
plot twist
At
the moment, I can think of three answers to the "What happens after I
ship?" question:
1.
The ever-expanding world
- The virtual world digs its own grave, as above.
2.
The Phoenix approach
- The virtual world
refuses to put out expansion packs and accepts a quick death,
fading to obscurity in a year or two. This solution isn't all bad because the
virtual world company can take the money it would have spent building expansion
packs, and put it into a new world, with the latest and greatest features,
content, and eye candy. The approach mimics the mythical, Phoenix, which is
reborn from its own ashes.
The
Phoenix approach is the one most commonly used by linear fiction and
single-player games, with the profits from one movie/game being used finance
the creation of an entirely different movie/game. I won't bother going into any
more detail since it's such a common approach.
3.
Continually remodelling
- See below.
A
"continually
remodelling" world still releases frequent expansion
packs, like an ever-expanding world, but with a twist:
1.
The expansion
packs would be free to all players, which inevitably means a
free download.
2.
They would include new eye candy,
although the eye candy would never be able to keep up with the latest and
greatest.
3.
New features would be added, but other
features would be removed. Introducing a ranger class that
could own a pet, along with the pet feature, might cause the necromancer class
to be removed. Removing existing features is needed to ensure that the world's
learning curve doesn't become too steep.
4.
Likewise, as new content is added, old content would be removed.
Again, this ensures that the world is accessible to new players.
This
approach is more like remodelling
than adding on.
In
architectural terms,
the ever-expanding world approach begins with a normal-sized house and continually
adds onto the house, eventually producing a monstrosity with 3 kitchens and 16
bathrooms. The continually-remodelling world approach keeps the house roughly
the same size, but occasionally remodels the kitchens and bathrooms to keep up
with the latest styles and gadgets. The Phoenix approach tears down the house
once in awhile and rebuilds a new one on the same spot.
Ramifications
of continually remodelling
Continually
remodelling has some important ramifications to the virtual world:
8 March 2005
by Mike Rozak
A
recent discussion on Terranova
got me thinking about altruism and its role in virtual worlds, particularly how
it pertains to the world
vs. game nature of a virtual world.
However,
before I discuss altruism, I will spend some time "boiling down"
linear fiction, single-player games, and multiplayer games into their
fundamental constructs...
Boiling
them down...
At
its most fundamental, a piece of linear
fiction (aka: story)
does the following:
1.
Provide a setting and "laws of
physics" that govern the world. Does the
story take place in the 18th century or the 25th century? Is there magic or
warp technology?
2.
Get the reader to like one or more
non-player characters (NPCs) in the fictional world.
3.
Have the "liked" NPCs make
interesting choices and undertake interesting actions.
The choices and actions aren't necessarily interesting for the NPC though; they
are designed to be interesting for the reader.
4.
Have the world, NPC, or other NPCs
change in interesting ways as a result of the NPCs actions.
Again, the change must be interesting to the reader.
One
of the ways to make the change more interesting is to make it believable but
unexpected. A good story teller never gives the reader exactly what they
expect.
5.
In a story, all the changes lead the "plot" to its final
conclusion. Those choices and changes that are not needed to
reach the conclusion are generally removed from the story.
6.
Each story has a theme, which is the
ultimate message of the experience, such as "You walk though history and
are part of future histories." The theme permeates the work.
The
fundamental elements of a single-player
game are:
1.
Provide a setting and "laws of
physics" that govern the world. This is
the same as with a story, although setting and physics seem to be more important
in a single-player game than a story... Could this be because the other
elements (see below) aren't handles as deftly by contemporary game designers?
Or, are world and physics inherently more important to a single-player game?
2.
Get the player to identify with his
player character (PC).
Unlike
linear fiction, games do not usually try to get the player to like any NPCs;
Artificial intelligence isn't sophisticated enough to make likeable NPCs. At
best, cut scenes or other pre-programmed scripts are used to make NPCs
likeable, but the effect pales compared to linear fiction.
3.
Provide interesting choices and activities
for the PC to undertake. The choices and actions are mainly
designed to be interesting to the player, not the player's character.
An
activity might be interesting in a game but not a story: Real-life hiking is an
interesting activity to partake in because it requires mental concentration to
avoid slipping, tripping over rocks, and being bitten by snakes. It is
dreadfully boring to read about though.
4.
The world, PC, and NPCs change in
interesting ways as a result of the PC's actions. Generally,
changes occur only after the PC correctly performs a specific action, like
pressing the correct button, killing a monster, or saying the right phrase to a
NPC.
5.
Single-player games have conclusions
and plots, just like stories. The player's choices,
activities, and their results are designed to lead to this conclusion.
Players
make an unwritten
agreement with the author that they won't try to subvert the
built-in plot (too much) if the author provides them with an enjoyable
experience. Of course, in most games, it's impossible for a player to subvert
the plot beyond refusing to advance it.
Occasionally,
a single-player game allows for several different conclusions, letting the
player's choices affect the outcome. There are never more than a few possible
endings though, so in reality, the player has very little choice
in the matter.
6.
Some single-player games have themes,
although most are the same chiche, "You too can save the world."
Multiplayer games are like single-player games, except
that other players perform some of the roles that NPCs do linear fiction.
A multiplayer game does the following:
1.
Provide a setting and "laws of
physics" that govern the world. Setting
and physics seem all-important to multiplayer games. Again, could this be
because virtual-world authors don't know how to use the other elements? Or is
it a fundamental difference?
2.
Get the player to identify with his
own PC and to make friends with other players. Again,
NPCs are not yet sufficiently intelligent enough for players to like them.
3.
Provide interesting choices and
activities for the PC to undertake.
4.
The world, PC, other PCs, and NPCs
change in interesting ways based on the players choices and actions.
Change
that results from player to player interaction is believable, and often
unexpected. Change resulting from PC to NPC interaction is sometimes
unbelievable, and usually too predictable. Thus, the changes brought on by
player to player interaction are (usually) more satisfying, and have fewer
pre-programmed limits. However, "interesting" also implies
"appropriate to the world", which is unlikely, since most players
don't role play.
5.
Multiplayer games don't seem to have
plots, at least for the moment. This is a strength
and a weakness.
It
is a strength because some players don't
like making an unwritten
agreement with the author. It's a weakness because many of the
player's choices and actions lead to naught. In a story or a well-written
single-player game, all choices and actions have a purpose.
I
suspect that as multiplayer games try to attract a more mass market audience,
they'll need a plot. See The End.
6.
Theme?
What theme? Most multiplayer games don't have a real theme. Themes are
particularly difficult to maintain over long periods of time and with thousands
of players trying to subvert the theme. Does this mean that virtual worlds
can't/shouldn't have a theme?
You
may be reading some of the observations I made about multiplayer games and
thinking, "That's wrong. They don't really work like that!". You're
right. A typical MMORPG divergs from my predictions:
1.
Setting and physics are everything,
to the point where most MMORPGs seem to have little else.
2.
MMORPGs don't do enough to get players
to meet other compatible players. This is
easy enough to remedy.
3.
They provide very few choices, and
very few varieties of activities. See Choice,
Choice2,
and Virtual
World as Platform.
4.
The player's character and other player
characters can be changed by the player's choices and actions, but the world and NPCs are generally
static.
5.
Contemporary MMORPGs include quests, which are
"mini-plots". Personally, I like quests, but I don't know if they
work well in the grand scheme of things. I
sometimes wonder if quests are vestigial remains from single-player games.
Example:
Early written fiction was often written as epic poems. Poetry was used in
bardic tales, the precursors to written fiction, as a memory aid and because
music was played with narration.
6.
Themes, if they exist at all in a MMORPG, are
incorporated into the quests.
Altruism
and NPCs
From
the "Boiling them down" section, it's obvious NPCs are necessary for linear fiction
and single-player games. Without NPCs there would be far fewer
interesting choices and their effects, and little plot or theme. Without NPCs
all you have is a world, and the laws of physics that govern it.
However,
a multiplayer game
(theoretically) doesn't need NPCs at all. Everything that a NPC
does can be accomplished by a PC, but much more intelligently and believably.
After all, NPC AI is so poor that NPCs are limited to mindless roles like
cannon fodder.
NPCs are not replaceable, and
provide a vital role in multiplayer games:
1.
They always stay in character,
adding to the believability of the world as a whole. Very few players role
play.
2.
A NPC works 24/7.
Players are only logged on an average 20 hours a week (with more mass-market
players on for less time). While it's possible to get players to work in shifts
to fulfil a role, they each bring their own personality and knowledge to the
role, making for an inconsistent experience... "Why are there 18 different
shop keepers for one store?", or "Why does the shop keeper have 18
different personalities?"
3.
NPCs are designed to be altruistic.
Some players are altruistic too, but most are self-centred...
Most players make choices and undertake actions that are
fun or beneficial for themselves. Some will uncaringly destroy the
enjoyment of other players just for their own enjoyment. A few will purposely destroy
another player's enjoyment.
NPCs are designed by the author to be altruistic and make
the game fun for the players. Even in
cotemporary MMORPGs, where NPCs are incredibly stupid, NPCs are still designed
for the players' entertainment. A
monster is designed to be easy to kill, and not to run away to
save its own life, nor to call in all its buddies within shouting distance.
A
virtual world can be created without NPCs, but there are consequences:
1.
If few players are logged on, a
NPC-less world is desolate and boring. This causes
players that log on to quickly log off, which ultimately creates a feedback
cycle that produces an empty world.
2.
The virtual world becomes very
dangerous, with self-centred players running around in
groups killing one another. NPCs could do the same, but their altruistic
programming forces them to stay in a portion of the world appropriate to their
level of difficulty.
3.
To make the world less dangerous,
authors provide rules of engagement and safe zones at the expense of allowing
players' actions to have interesting effects. Dark
Age of Camelot only allows PvP combat in certain areas and only against
official enemies, creating a everlasting battle with no possible victory and no
real consequences. World War II Online has official enemies, but
doesn't limit where the combat occurs, creating a more dangerous game where
players' actions have consequences. World War II Online has fewer
players than Dark Age of Camelot... Is this a consequence of the
heightened danger?
4.
A virtual world can be designed to
completely eliminate danger and (hopefully) attract mostly altruistic players.
I believe this is what A Tale in the Desert does. In the game, players
try to cooperatively build a civilisation. There is no combat, and (as far as I
know) no sanctioned PvP of any kind. A Tale in the Desert doesn't
attract many players though... Is this because it's designed for more
altruistic players, a rare breed?
World War II Online
and A Tale in the Desert
both attract a small but enthusiastic group of players.
I suspect the reasons for this is that they also allow players to change the world,
something which other MMORPGs do not allow. Most players don't seem to care if
they can change the world, but some do, enough that they're willing to accept a
higher danger level or to exist in a world where they must cooperate with one
another to achieve anything.
Interestingly,
neither game relies on NPCs for entertainment, contrary to most MMORPGs. Does
this mean that the more
NPCs in a world, the more static it will become?
The
opportunity costs of NPCs
NPCs are beneficial to a virtual world because they're
altruistic. However, their existence lead to static worlds, which means that
players' choices and actions ultimately come to naught.
The
reasons why NPCs result in a static world are obvious:
1.
Interesting NPCs (quest givers, shop
keepers, etc.) are a lot of work to create, and
authors can't afford for their work to be lost just because a player decides to
kill them or otherwise make the NPC irrelevant.
For
example: It takes a bit of work to create a NPC that hands out a quest to
"kill all the orcs on the other side of the hill" to players. If the
quest-giving NPC is killed, then players may never know to attack the orcs on
the other side of the hill. If the orcs are all killed, then the quest-giving
PC is out of a job. Either way, the world can't afford to change.
2.
Uninteresting NPCs (monsters) are easy
to create, but must still be spawned. Ideally,
players should be able to make a concerted effort and kill all the orcs in the
world to be rid of them, permanently changing the world. However, if all the
orcs are killed, what do players do for entertainment?
MMORPGs
prevent all the orcs from being killed by continually spawning orcs in one area
of the world. Unfortunately, this leads to a static world since nothing the
players can do will eliminate the orcs. The orcs' altruistic programming that
prevents them from leaving their spawning region makes for an even more static
world.
3.
Quests (which are a by-product of
NPCs) also ensure that a world is static. They too
take effort to create, and authors don't want to waste the effort once the
quest has been completed by just one player.
I
have heard of several solutions to this problem:
1.
Get rid of NPCs altogether. World
War II Online and A Tale in the Desert do this.
As
described previously, removing all NPCs has its own problems.
2.
Improve the artificial intelligence
behind NPCs and the code that creates NPCs. Make sure
that if all monsters are about to be killed off, at least some manage to
escape. Allow non-monster NPCs to react to their environment and make
intelligent decisions. Furthermore, let NPCs create their own quests based on
their goals and needs.
Even
though AI can be improved beyond what is seen in contemporary MMORPGs, AI has
its limits. The world will still be static, but on a higher level of
abstraction than contemporary MMORPGs. The NPCs and quests will appear to be
dynamic, until players realise that the same quests and NPC templates keep
reappearing in different disguises. Furthermore, the AI must be designed so
that it's "altruistic" but not a pushover, however one does that.
I
haven't seen this implemented.
3.
Hire human GMs to continually tweak
the world, spawning NPCs where they see fit, and providing
some higher-level intelligence for NPCs. Unfortunately, this can become very
expensive.
Volunteer
GMs would work too, as long as they remain altruistic. Giving ordinary players
power over NPCs poses a problem, since the controlled NPCs would lose their
altruism and fail to fulfil one of the reasons they're in the world.
A
major problem exists with human GMs: The
players must believe that the GM is impartial. As soon as the
GM shows favouritism the players will revolt. The same rule applies to AIs, but
it's easier for players to believe that an AI is impartial.
Wish
was going to implement such GMs, but it was cancelled during beta, citing low
player numbers. Again... Does
the mass market player want to change the world?
4.
All of the above.
An author can reduce the number of NPCs (per player) to create a more dynamic
world but more dangerous world. AI can be improved. Payed GMs or altruistic
volunteers can guide the AIs.
Encouraging
altruism in players
Despite
all I've written, player's aren't completely self-centred. Virtual worlds often
try to encourage altruism:
1.
They encourage
players to become altruistic role players, by guiding them
towards acceptable behaviour and rewarding them with experience points or loot.
Most MMORPGs do so already by handing out experience to players that kill
monsters, but their efforts might be directed more effectively.
2.
Multiplayer games provide tools so
that altruistic players are welcomed and provided more influence.
These include constructs such as guilds and mentors.
3.
Some virtual worlds award role-playing
points (related to altruism) for players who are
doing a good job role playing.
4.
Another possibility is a world-design that attracts altruistic
players. I'm not exactly sure how to accomplish this.
A
quick summary
To
sum up what has been covered so far:
1.
A virtual world without enough
altruistic PCs and NPCs becomes dangerous, and attracts a smaller audience.
Free-for-all PvP MUDs don't have too many players.
2.
The danger can be removed or reduced
with hard-coded rules, but then players are left in a less-interesting world
that is often static. Ultima Online originally
had open PvP, but was ultimately forced to impose PvP restrictions. In Dark
Age of Camelot, the PvP restrictions produce a more static world; none of
the three realms can ever win the PvP war.
3.
Although altruistic players are rare, altruism can be encouraged with the
proper tools and using appropriate rewards. Role-playing
points, guilds, and mentors are a common technique.
4.
NPCs, which are (always) programmed to
be altruistic, can counteract the PC's self-centred-ness. Interactions
with NPCs are pale imitations of interactions with real players though.
Furthermore, the more
NPCs in a world, the more static the world.
5.
Improving NPC AI can reduce the amount
that NPCs constrain world change and make
the world less static. Improved AI also makes the NPCs more interesting to
interact with.
Consequently,
a safe and interesting
(mass-market) world that doesn't rely on altruism is static.
(Such as World of Warcraft or Everquest II.)
So
what?
What's
the big deal if NPCs aren't intelligent and a virtual world is static? Most
players don't seem to mind not being able to change the world; Everquest
(I & II) and World of Warcraft own much of the US market. In all
three worlds, players run around, undertake lots of action, and pretend that
they are changing the world even though the static nature of the worlds is
obvious.
Obviously,
I can't argue against the fact that most players don't seem to mind static
worlds. A few thoughts do arise though:
1.
Players didn't mind sprite-based graphics
when their computer could only produce sprite-based graphics. They got "spoiled" by
3D-accelerators and now expect more. They might do the same for
static vs. dynamic worlds.
2.
Mass-market virtual worlds may be able to get
away with a static environment, but niche
worlds might require dynamic worlds to compete. The trend
already points strongly in that direction.
3.
Applying Richard Bartle's player model: Achievers and explorers,
who are more world-oriented, are also more interested in dealing with altruistic NPCs.
Socialisers and killers would rather interact with players, who are more
interesting than NPCs. Socialisers
are altruistic, while killers are self-centred.
4.
Looking at the player pyramid
in this light reveals that players
at the bottom of the pyramid are happy with existing AI, while those at the top
require more complex, human-like AI. As AI improves, players at
the top of the pyramid will have less and less need for players below them.
5.
Damion Schubert's response
to the player pyramid is a version where altruistic
players occupy the top layers. My player pyramid assumes more
self-centred players would impose their needs on players below, requiring that
players at the top of the pyramid financially subsidise the experience of those
at the base, if they wish to attract any players to be a base. If altruistic
players could be found to fill the top layers, the altruistic players would
actually improve game-play for those at the base and a different business model
could be used.
9 March 2005
by Mike Rozak
I
have been concerned about how
much one virtual world can differentiate itself from another.
The virtual worlds out there (MMORPGs and MUDs) and not too promising; they're
almost all variations on the Tolkien or D&D fantasy theme.
If virtual worlds can't differentiate themselves then the
market will ultimately collapse into a few games,
just like the market for office software only has a few competitors out of the
hundreds of office products that once existed. After all, two word processors
can only be so different.
Points
of differentiation
Worlds
have several broad categories that they can use for product differentiation:
Differentiating
multiplayer features
When
I look around existing virtual worlds, I notice an amazing lack of differentiation
amongst the multiplayer features. The standard multiplayer features are:
Even
the specific implementations of the features aren't that different from world
to world...
Such lack of variation worries me,
because if multiplayer features can't be differentiated sufficiently between
worlds then "multiplayer" virtual worlds may not be differentiable.
Since
existing virtual worlds make poor role models, I decided to come up with my own
list of multiplayer features: First, I wrote down a list of reasons why a person would
chose to play a multiplayer game over a single-player game.
Then, I listed some ways
that the features could be differentiated from one another.
Below is the list:
|
Reason
for playing multiplayer game |
Ways
of differentiating |
|
Be part of a group. |
|
|
Change the world.
(This is a multiplayer feature since few people seem interested in having a
private world that they can change as much as they wish.) |
|
|
Compare rank with
other players. |
|
|
Gossip and hearing
about what other players are going. |
|
|
Hang out with
friends. |
|
|
Entertain other
people with poetry, song, dance, music, acting, artwork, or writing. |
|
|
Experiment with
different personalities (Richard Bartle's Hero's Journey) |
|
|
Griefer |
|
|
Lead people. |
|
|
Learn a new
language. |
|
|
Meet new people. |
|
|
Mentor or teach
people. |
|
|
Organize people. |
|
|
Playing games
against human opponents is more challenging, fun, or rewarding. |
|
|
Practice for
real-world encounters with people. This could include getting over shyness. |
|
16 March 2005
by Mike Rozak
The
more I try to boil down the virtual world experience into a set of simple
constructs, the more intertwined the constructs become. This document describes
some of the intertwined relationships that seem to arise.
Technology
At
the lowest level, virtual worlds are based on technology, such as:
Modern
virtual worlds almost all have the same technology categories, although
different virtual worlds emphasise some technologies over others.
Sub-games
and activities
The technologies are combined together to create
sub-games
and activities such as:
The better the technologies available, the
"better" the sub-games can be. An
equivalent sub-game can still be written with poorer technology, but it seems
to attract fewer players. (For example: Text adventures are much faster to
write than graphical adventures, but graphical adventures attract more players.
The same goes for text-based Rogue/Hack RPG games, and graphical equivalents
like Diablo II. The difference in the experiences is mainly that the graphical
adventures/CRPGs have a much better graphics engine.)
Sub-games are combined to form other sub-games:
Not
only are sub-games combined together, but they vary slightly by location (space) and time.
For example: The combat sub-game varies geographically because players fight
lions in the savannas, and cave trolls in the mountains. Trade also varies
because some goods are cheaper and more readily available in some places than
others.
Sub-games can vary substantially from game-to-game.
A puzzle in Zork is significantly different than a puzzle in Myst.
Synergy
between sub-games
A collection of sub-games is combined to produce the
final "game". What sub-games are
used and how they're combined has a lot to do with the aggregate experience of
the game.
Combining some sub-games create synergies that greatly
improve the overall experience: Single-player
CRPGs almost always combine the combat, trade, quests, and dungeons sub-games.
These sub-games seem to go well together, just like peanut butter and jelly.
Some sub-games create anti-synergies that weaken the
overall experience: Puzzles and card games are rarely
found in single-player CRPGs. Combat is rarely found in adventure games.
(Garlic is good in pasta sauce, but doesn't work well as an ice cream flavour.)
Of
course, the synergies and anti-synergies are all in the eye of the beholder.
(Some people like garlic ice cream, at least theoretically.)
Single-player games tend to be based on just one
sub-game, while massively multiplayer games tend to include as many sub-games
as possible, including crafting the kitchen sink.
I suspect this occurs because, as I'll discuss later, different sub-games
attract different player types, who in turn have their own synergies that
counteract the anti-synergy of too many or dissonant sub-games.
Furthermore,
some sub-games are more
fun as single-player experiences, while others are more fun with multiple players.
The "chat" sub-game is very boring when there are only AI's around to
talk to (aka: Eliza), but enjoyable with other people.
Consequently,
single-player sub-games seemed to be grouped into "games" of the
following genres:
Massively
multiplayer sub-games seem to combine best into:
Sub-games
attract different single-player gamers
Every player has their own set of likes and dislikes for sub-games.
Some players like combat, while others prefer puzzles, and yet others prefer
both. If you grouped players by what types of sub-games they prefer, I suspect
that you'd end up with the standard game genres listed above. After all, 30
years of trial-and-error has shown which genres (combinations of sub-games)
sell best.
A
mixed-genre game, which doesn't have all the sub-games expected of a genre, or
which extraneous sub-games, results in a weaker game and fewer players.
Just
remember though, that genres are quick ways to label large groups of players.
They are only vaguely correct, and no matter what combination of sub-games are
chosen, a sub-set of players will enthusiastically like that specific
collection of sub-games. The group may be very, very small though.
Sub-games
attract different multi-player gamers
As
I wrote up in Differentiation,
players are attracted to
multiplayer games because the multiplayer games fulfil a need that single-player
games do not. Some people play MMORPGs because they like to
compete against other players, as opposed to AIs. Other players like MMORPGs
for socialisation. Etc.
If
you create a chart that lists the different motivations for players wishing to
play multiplayer games, compared to the sub-games, you'll see that some multiplayer motivations are best
fulfilled by some sub-games.
In
the following chart I list a few motivations and a few sub-games. In each cell
I have placed a capital "X" where I think that players with the
motivation would really enjoy the sub-game, a lower-case "x" where
there is some appreciation, and a blank where the motivation can't be fulfilled
by the sub-game. If you don't agree with my sub-games, motivations, or X's,
then create your own graph; the principles remain the same:
|
Chat |
Combat |
Trade |
Quests |
Puzzles |
Card games |
Spacecraft |
|
|
Hang
out with friends |
X |
X |
x |
x |
x |
X |
x |
|
PvP |
X |
X |
x |
x |
x |
X |
|
|
Role
playing |
X |
x |
x |
x |
|||
|
Be
part of a group |
X |
X |
x |
X |
x |
||
|
Rank/competition |
x |
X |
X |
x |
x |
x |
|
|
Entertain
others |
X |
||||||
|
Griefer |
x |
X |
x |
||||
|
Leader |
X |
X |
x |
x |
x |
||
|
Meet
new people |
X |
x |
x |
X |
x |
X |
The
specifics of the chart are less important than the general conclusions:
Each
motivation interacts with other motivations
Since
players inhabit the world together, they affect one another's experiences. A
world whose sub-games attract armies of griefers, will in turn scare away socialisers,
whose experience is negatively impacted by griefers. (Richard Bartle pointed
this out in his player models.)
I
have created a chart of my guesstimate of these relationships using the
previously-listed motivations. Each row shows the list of motivations, and the
columns indicate how much players of the row's motivation are affected by
players with the column's motivations. (Thus, the column for griefers shows
negative or neutral results for all other players since no one likes them, not
even other griefers.) Each cell is filled with "++" for a very
positive effect, "+" for a slightly positive effect, blank for neutral,
"-" for slightly negative, and "--" for very negative.
Again, if you disagree with the motivations or their effects, change them to
suit yourself.
The
"warm bodies" columns means that the player doesn't care what
motivates the other player so long as the other player is around. "Warm
bodies" also include players in the virtual world who really want a
single-player experience. Griefers and entertainers, for example, find the the
more players in the world, the better. (See The Player
Pyramid.)
|
Warm bodies |
Hang out |
PvP |
Role play |
Group |
Rank |
Ent. |
Griefer |
Lead |
Meet new |
|
|
Single
player gamers |
- |
+ |
+ |
-- |
||||||
|
Hang
out with friends |
++ |
+ |
+ |
+ |
-- |
+ |
||||
|
PvP |
++ |
+ |
||||||||
|
Role
playing |
+ |
+ |
++ |
++ |
- |
+ |
||||
|
Be
part of a group |
++ |
-- |
++ |
|||||||
|
Rank
/ competition |
+ |
++ |
+ |
- |
||||||
|
Entertain
others |
++ |
+ |
- |
-- |
||||||
|
Griefer |
++ |
- |
- |
- |
-- |
|||||
|
Leader |
++ |
- |
- |
|||||||
|
Meet
new people |
++ |
+ |
+ |
-- |
++ |
Example interpretation of graph:
People visiting the world who would really rather be player a single-player
game dislike having other players around because they detract from gameplay.
Role players and entertainers could add to the entertainment value. Griefers
are a strong negative because they are the antithesis of what single-player
gamers are looking for, a safe and predictable experience.
Example interpretation of graph:
Players who like PvP combat (or other sub-games), like to have other PvP
players around to compete with, as well as players who are interested in their
rank, since they'll accept a PvP challenge. Unlike most players, PvP players
don't mind griefers since griefing is a demented form of PvP, which the PvP
players are willing to partake in.
Example interpretation of graph:
Griefers want lots of warm bodies around to grief. They don't like players that
hang out together or in groups because they're too difficult to harass;
isolated prey is much easier. They don't like PvP players who will fight back.
And, they especially don't like other griefers because too many griefers scare
away all the prey.
Again,
the specifics don't matter, but some general conclusions are important:
One
of these days I'll write a small program to simulate all the player
interactions. This would allow me to predict what would happen if I added or
removed a sub-game.
Unfortunately,
too many constants must be guessed. (I'm sure you disagree with at least half
of my X's, +'s, and -'s). The effects of changing a constant are so non-linear,
that a simulation would probably produce inaccurate results.
So
what's the point of this document if using it as a template to design a
modeller won't work?
Intertwined
relationships
Some
conclusions can be drawn...
Changes
to one part of the system affect all the other parts:
1.
Changing a technology affects all the
sub-games based on it.
2.
Changing sub-games effects sub-games which
are based upon other sub-games, such as quests and dungeons.
3.
Changing, adding, or remove a sub-game
affects what kind of single-game players are attracted to the world.
4.
Changing sub-games also effects how well they
fulfil multiplayer motivations, and what kind of world-like players are attracted.
5.
Changing the demographic mix of multiplayer
motivations produces a feedback cycle (positive and negative) which affects
what players are attracted to the world.
Furthermore:
There
are also implications for mass-market vs. niche-market worlds:
1.
Mass-market worlds emphasise eye-candy
"technology", while niche-market worlds don't have
the money for the eye candy. Consequently, they emphasise other technologies,
such as AI.
2.
Mass-market worlds tend to dumb-down the
sub-games as well as minimise the number of sub-games to keep the UI simple.
Furthermore, mass-market games minimise the number of sub-games because they're
too expensive to produce with the appropriate eye candy.
3.
Fewer sub-games in mass-market games
means that quests and dungeons, which are based on sub-games, aren't as
"good" as those in niche-market worlds.
4.
Fewer sub-games results in a narrower
set of player motivations being met by the sub-games.
In turn, fewer player motivations results in fewer players of motivations being
attracted to the world.
In
ecological terms, mass-market games are like savannas. They are a relatively
simple ecosystem of player motivations. Niche-market games are more like rain
forests, with complex ecosystems of player motivations and sub-games. Most of
the Earth is covered by savanna and other simple ecosystems, while most of the
Earth's diversity is in its rain forests.
5.
Mass-market worlds probably won't even
try to meet niche-market player motivations because the
market is too small.
6.
Mass-market worlds will be more
game-like, while niche-worlds will be more world-like.
6 April 2005
by Mike Rozak
For
awhile now, I have been considering what advantages text MUDs have over
graphical MMORPGs. (... Mainly because the authoring system that I'm creating
is a hybrid text/verbal and graphical system, so it can take advantage of
text's strengths.)
Books
vs. movies
Before
exploring the newer medium of virtual worlds, I thought I'd see how linear
fiction handles the dichotomy between text (books) and graphics (movies).
Strengths
of books over movies:
Weaknesses
in books (compared to movies):
Other
people's opinions about the strengths of text MUDs
In
his book, Designing Virtual Worlds, Richard Bartle lists the following ways
that text MUDs are better than graphical MMORPGs:
A
few months ago I posted a user poll on www.MudMagic.com,
asking "Why do you prefer MUDs to MMORGs?" (MudMagic is a text-MUD
web site.) I only had 10 slots, so I limited the choices to the following:
|
Smaller community |
5.65% (24) |
|
Role playing |
18.82% (80) |
|
More innovation |
9.65% (41) |
|
More imaginative
worlds |
10.35% (44) |
|
MUDs have deeper
gameplay |
23.53% (100) |
|
Blind; I use a
screen-reader |
3.53% (15) |
|
Cheaper |
12.24% (52) |
|
Don't need
expensive PC |
3.76% (16) |
|
MMORPGs don't like
my OS |
3.53% (15) |
|
I prefer MMORPGs |
8.94% (38) |
Some
respondents wrote additional comments about why they prefer text-MUDs,
including:
|
|
|
The
web-page for this non-scientific poll is http://www.mudmagic.com/polls/view/31.
From
the MUD-Dev mailing list:
Some
of my own thoughts
I
came up with my own list: (I have eliminated those items duplicated above.)
6 April 2005
by Mike Rozak
What
makes a game fun? Raph Koster's latest book, A Theory of Fun, proposes some
ideas which I tend to agree with.
I
thought I'd different approach to the problem: Look at some computer game categories and identify the
defining "fun" characteristics of the category.
Scanning
through some game review web sites, I came up with the following game
categories:
MMORPGs/MUDs
also come in a few different varieties:
What
makes it fun?
What
makes each of these categories of games fun?
The
social aspects of MMORPGs and MUDs also translate into elements of fun:
Main
elements
If
I boil down the main elements, I come up with:
The
social reasons for playing are:
Many
of the elements of "fun" are opposites, such as inductive and
deductive reasoning. If I combine opposites together, the list now looks like
this:
6 April 2005
by Mike Rozak
I
thought I'd spend some time stating some obvious facts about the powers that players are given to
affect a virtual world...
Player
powers
Players
have the following basic powers in a virtual world:
Notice
that character control, influencing player characters, and influencing NPCs
are online activities
that must be done by a person real-time. Creating static content, scripts,
and AI can be done offline,
and affect non-specific players at a later point. I'll reference online and
offline activities later.
Player
categories
Virtual
world "players" fit into the following categories...
The
matrix
For
any virtual world, it's possible to create a matrix of player powers vs. player categories.
Each cell can be filled with the specific abilities that a player has. (By the
way, Richard Bartle categorizes worlds based on persistence vs. player category
in his book, Designing Virtual World.)
|
Author |
GM |
Trusted
players |
Ordinary
players - Friends |
Ordinary
players - Enemies |
Ordinary
players - Neutrals |
|
|
Character
control |
||||||
|
Influence
other players |
Table-top |
PvP |
||||
|
Influence
NPCs |
||||||
|
Create
static content |
||||||
|
Create
scripted content |
PvE |
Creation |
||||
|
Create
AI |
I
haven't bothered filling in this matrix for a specific game. I did colour the
cells and label them; see below.
Interestingly,
if you examine a game and ask "Where does the fun come from?" you'll notice
some trends:
Some
other observations:
6 April 2005
by Mike Rozak
As
I stated in The
authoring equation, computer graphics thinkers have come
up with an all-encompassing mathematical equation that explains 3D rendering.
Most of the computer graphics lighting techniques that you may have heard
about, such as radiosity, global illumination, ray tracing, motion blur, etc.,
are just approximations to the full solution described by the rendering
equation.
Since
I like to examine the underlying principles of ideas, I keep wondering if
there's a similar all-encompassing "equation" that explains virtual
worlds...
Here's
my latest attempt.
What
a virtual world is...
A
virtual world is...
1.
A virtual place
simulated on a computer.
2.
The place is populated by various "players" visiting
the world. The most common type of "player" is an ordinary player. Other
types of players exist though, including the author, members of the live team, and
trusted players. (See Player powers.)
3.
Each player has the ability to control
their avatar, affect the world, and affect other players, either directly or
indirectly. The type of control depends upon the player
type. Authors, for example, have much greater ability to change the world than
ordinary players.
4.
Players visit the world because they
want something out of the experience.
The
most common "desire" is entertainment
from the author's content, which the author created using their
authorial player-powers. Most players also want to play with friends, or meet
new people. Some want to run inns. Others wish to be kings. Many want to
dominate others. The author and live team "play" (in part) because
they wish to earn real-life money.
The
players' desires are conscious,
sub-conscious,
and maybe even something unknown
or unwanted to the player when they first begin playing, but
which is ultimately "good" for them (such as Richard Bartle's
"Hero's journey"). Does the the author's choice of what is
"good" for the player mimic the "theme" of a novel? (More
on this in a future writeup.)
To
state the obvious: If the player's desires didn't somehow
involve other players, the players would probably be playing a single-player
game instead of dealing with all the headaches of the Internet and other
players.
5.
When players try to fulfil their
desires, they often annoy other players in the process. A player fulfilling his
desire occasionally benefits other players.
For
example: A player who wants to role play is annoyed by non-roleplayers, and
vice-versa. A player who wants to grief annoys just about every player he
meets. A player who wishes to be an innkeeper and chat with his customers
annoys those customers who just want to buy a beer and get back to adventuring.
Of
course, not all players cause each other grief, and some get along quite well;
A player who wishes to play Florence Nightingale is appreciated by wounded
fighters. A player that wishes to to sing bawdy songs at an inn is usually
welcomed by the inn's visitors, although some customers may disagree.
How much one player's desires affects another player
depends upon the individuals and their particular moods and
circumstances. Some people may not like being healed by Florence Nightingale,
while a small minority of others might actually enjoy being griefed. Annoyance
can be conscious
and sub-conscious.
See
The player
pyramid and Altruism.
The
equation
Looking
at virtual worlds in this light makes "the equation" to solve fairly
obvious... The goal of a virtual world is to:
Sustainably maximise the fulfilment of the players'
desires (aka: conscious and sub-conscious enjoyment of the VW), while
minimising the amount that players get on each others' nerves.
(To make matters more difficult, players getting on each other's nerves
sometimes leads to fulfilment of the players' conscious or sub-conscious
desires.)
Of
course, this is easier said than done.
Some
obvious solutions
Some
solutions naturally follow:
1.
The author and live team need to
produce the "entertainment" that players consciously and
sub-consciously expect from them. Such
entertainment is usually in the form of hard-coded content, scripts, and AI...
what most people would call the PvE game. Some virtual worlds (see below) rely
on the live participation of the author and live team, while others have no PvE
content whatsoever. (See Player powers.)
2.
The world should be designed to
attract "complimentary" player desires.
These could be self-attracting desires, A <-> A, such as players
looking to meet people wanting to meet other players wanting to meet people.
They could be mutually beneficial relationships, A <-> B;
Players who wish to be leaders must be in worlds with players who wish to be
led, and vice-versa. Or, there can be more complex relationships of A ->
B -> C -> A. See Intertwined relationships.
The player
pyramid discusses one possible relationship, where
some players pay the real-life bills in order to have the other players around.
3.
The virtual world's advertising should
do its best not to attract players that won't fulfil their desires in
the world. Attracting the right players is also
important, but is (in many ways) secondary to not attracting the wrong players. If the
wrong player visits a virtual world they will not enjoy their experience and
will tell ten friends how lousy the world is, just like people who see a movie
they didn't like. Unlike movies, players
who aren't enjoying themselves will also make life miserable for the virtual
world players who are playing and enjoying the world. (Note:
Similar behaviour does happen in movie theatres... Occasionally, a disgruntled
movie viewer will provide a running criticism of the movie to everyone sitting
in neighbouring seats.)
4.
The virtual world's advertising should
attract a "balanced" distribution of players.
If more players wish to be innkeepers than there are inns, some of them will be
unhappy. Damion Schubert pointed this out in his response
to the player pyramid.
5.
The world should attract and empower
altruistic players. Some people are naturally friendly
and altruistic, and are a net benefit to a virtual world. The virtual world
should attract these players and give them powers to maximise their beneficial
effect. Other players (griefers) annoy virtually everyone else and should be
kicked out. (See Damion Schubert's response
to the player pyramid.)
6.
The powers provided to various types
of players should be designed so the players can attain their desires.
If a world attracts players who wish to lead, the world should provide
tools/abilities so the players can actually lead. A world that attracts players
who want to be innkeepers should provide player-run inns.
Alternatively,
an option for "looking to group" ensures that players that wish to
meet other players can easily do so.
The
powers might be dependent on other players, so that player A cannot fulfil his
desires with his own powers, but must convince player B to use his powers.
7.
Players should have a choice about
what they want to do, and who they typically interact with.
This allows players interact with people that help fulfil their desires, and
avoid annoying ones.
8.
The world should naturally funnel
players to meet other compatible players. Guilds
ensure that leaders and followers meet up. Likewise, PvP players are given
their own PvP region of the world so they don't kill a PC that doesn't want to
partake in PvP. In some worlds, fantasy races with strong personality associations
attract people of like-personality into the same region of the world. (Giving
players exactly what they want isn't always best though. See below.)
9.
Players are rewarded for fulfilling
the desires or other players, and penalised for being annoying to other players.
Players naturally reward and punish one another through in-character actions,
such as as insults, avoidance, and PvP combat.
Role
playing virtual worlds sometimes provide each player with role-playing points
that he/she can award to another player that he/she thought did a good job role
playing. The points could just as easily be handed out for "making my
experience more enjoyable". Negative points could be awarded for
"being a jerk"... so long as the true jerks aren't allowed to label
other people as jerks and their friends as enjoyable, or the world will turn
into a world of jerks, which is fine if that's what you're after.
Rewards
can be simpler than this; in the case of a virtual world with player
inn-keepers, making goods less expensive at the player-run inn than the NPC-run
inn acts as a reward for players who visit the player-run inn.
10.
Role playing points and/or real-life
cash payments can be accumulated and used to "purchase" potentially
negative abilities. For example, if a player accumulated
enough role playing points they could become king, which would allow them to
impose their will (to an extent) on other players. Inevitably, such an
imposition would be a minor annoyance to a large number of players. To become
king, and thereby mildly annoy large numbers of players, the player had to do
good deeds, either by getting lots of role-playing points or by paying
real-life bills, so on the whole they make the world a better place to live
in... This system acts like the inverse of Hindu theology, where the
positive/negative actions of this life affect what you're reincarnated as.
Instead, the positive actions of this life allow you to act negatively in your
next life.
Notice
the semi-interchangeability
of role-playing points and real-life money. Does this mean that
virtual worlds which are expensive to run (ones with plenty of eye candy) will
value wealthy contributors above those who earn role playing points, and vice
versa?
11.
Worlds with democratic player politics
have a similar effect. A player who desires to be elected
will only be elected if he makes enough other players' desires come true.
Unfortunately, democracies inevitably lead to factions where 51% of the players
are getting their desires met, and 49% are unhappy with the current administration.
Role playing points used to purchase kingdoms could produce similar outcomes.
Notice
that these solutions
almost all require that the player makes a conscious choice,
which means that the player will tend to fulfil their conscious desires and often fail to heed their sub-conscious
or "good to have" desires.
Examples
Applying
this model to contemporary virtual world genres (partially) explains why they
succeed:
If
this model is applied to single-player
games, as shown at above, then you'll see that a single player
game is a world with one player and an author in absentia. The absent author is
limited to hard-coded elements, scripted elements, and artificial intelligence.
The author "learns what the player desires and dislikes" by providing
the player a choice of play styles in the guise of races and classes, as well
as choices throughout the game.
A
story is
0-player world in which the reader has no input. The author of a book or movie
can only use hard-coded elements; scripts and AI are not possible in books.
Furthermore, the author cannot even ask the viewer what they're interested in,
so instead, the author designs the content to satisfy a stereotypical
reader/viewer. (Theoretically, a piece of linear fiction on a computer could
ask the reader what type of experience they want, and use scripts and AI to
tailor the fiction.)
A
more complex solution
If
you have a hammer, all the world's problems look like nails...
All
of the "obvious" solutions are already used in contemporary virtual
worlds. Let me propose a more complex (and robust) solution that may be a bit controversial:
Create an AI (or members of the live team) whose job it
is to figure out what a player desires, and hook him/her up with other players
(or content) that fulfils the player's desires, while also fulfilling the
hooked-up players' desires.
In
other words,
Create a god/director for the world whose purpose is to
make the world fun based upon its knowledge of each individual. Much of what
the god/director does is act as a "dating" service, arranging
"chance" encounters between players (or content).
Basically,
the AI's job is to maximise the fulfilment of desires while minimising the
amount that players get on each others nerves. For example: It needs to get
chatty players to visit the inn run by the chatty innkeeper, while steering
rushed players to a vending machine.
This isn't any easy task, and isn't likely to be solved
anytime soon.
The
first problem is finding out what the player desires:
1.
If you ask players what they want, they will
be able to state their conscious desires. They won't be able to tell you their sub-conscious desires,
and won't have clue
about unknown desires that would be "good" for them.
2.
Players may not be able to put their
desires into words or settings that an AI can understand.
3.
Players may lie about their desires.
4.
Players' desires change from day to
day, hour to hour, and minute to minute. A virtual
world can't possibly ask players what they want every 10 minutes. Polling the
player once every few weeks might work, since the player's desires probably
fluctuate around a mean.
5.
Some desires should not be fulfilled...
Even though players may think they want to fulfil a desire, they will be
unhappy if they actually do.
It's
the old saying, "Be
careful what you wish for. You might just get it."
Or,
to paraphrase Frank Herbert in his Dune series: Most people will tell you they
wish to know the future, but what they really want is to know which lottery
number to pick and how to avoid accidents. They don't really want to know
everything that will happen to them.
Somehow,
the AI (or live team)
needs to watch a player's actions and reactions, and guess what the player is
trying to get out of the virtual world experience... I said it
wasn't doable in the near term.
Furthermore,
the AI (or live team)
must determine what annoys the player so that content and encounters with
potentially annoying players can be minimised. Attaining a list
of "annoyance" factors comes with the same difficulties as
determining the player's desires.
Once
the AI (or live team) knows a player's desires and annoyances, it needs to steer the player to the
content or players that will be most compatible. An even more
complex solution would have the AI create
content designed for the player, just like a table-top RPG GM
does.
If
this wasn't complex enough for an AI, the
AI will probably need to be very subtle about its machinations.
Many players will resent an AI playing matchmaker, even though they might
appreciate the results. If the players know they are being manipulated, or in
what specific ways, they might rebel and purposely try to subvert the AI. Griefers will find all sorts of ways
to subvert the system. For example: If a griefer is given a
quest that's obviously designed to benefit another player, this provides the
griefer with information about what the other player wants, and can be used by
the griefer as a weapon against the other player.
Table-top RPG's successfully implement this more complex
solution because the GM (acting as the AI) only deals
with four to six players, and knows them well enough personally that he can
deduce their desires and dislikes. The players know such machinations occur,
but accept it because the GM is their friend, and is trusted so long as he
doesn't go too far.
Even
though its a long way off, the idea is not that far fetched... Amazon.com uses data mining and
analysis to produce reasonable (but imperfect) book recommendations
based on what books you have already ordered. When I log onto Amazon.com, it
recommends that I purchase a collection of books about 3D rendering and game
design, along side a Thomas the Train electric train... I have ordered
Christmas gifts for my nephews through Amazon.com, and the AI isn't intelligent
enough to realise that the gifts weren't for myself.
6 April 2005
by Mike Rozak
Stories
typically have a "theme", such as "If you work hard you'll
succeed," "Don't accept apples from strangers," or
"Never trust a man that sells you magic beans." Personally,
I like themes. The MMORPGs and MUDs I've played don't seem to contain any theme
other than, "Kill enough monsters and you'll grow up to kill even
stronger monsters."
How does one add theme to a virtual world?
The
problem
If
I wanted to add a theme to a story, I'd do the following:
1.
Have the protagonist (or secondary
characters) make decisions and act upon them.
2.
The results of the actions would be adjusted
to agree with the theme.
1.
If the character chose well, something
positive would happen to the character or his friends/family.
2.
A poor choice would produce the opposite
result.
For
example, the theme of Herman Melville's "Moby Dick" is that "Revenge
(against a large white whale) is a bad idea." As the book progresses,
Captain Ahab increasingly shirks his job as manager of the Peaquod, whose
responsibility is to kill whales, get their oil, and make money. He becomes
more obsessed with Moby Dick, and ultimately brings himself, the ship, and its
crew to ruin.
Another
example: I can imagine a book in which the protagonist changes religions, such
as from Christianity to Hinduism. If the theme of the story were "Stay
true to your roots," such a change might result in ostricization from
the character's family, friends, and society. If the theme were "Follow
your heart," bad things would happen to the character until he
followed his heart and changed religions.
In
a virtual world, the author can give a player the option of taking revenge, or
of changing religion, but the
author can't control the outcome very well. For example:
1.
The author can't cause anything
bad/good to happen to the player's NPC friends or family....
Not only does the PC not have any NPC friends or family, but if he did, he
wouldn't really care what happened to them. Even if the player did care, the
plot device would only work once.
2.
There can't be any significant
world-changing consequences because, due to the nature of virtual
worlds being shared by thousands of players, the world can't be significantly
affected by any single player. A single-player game could have consequences,
but not a player-vs-environment virtual world.
3.
The author can't guarantee that a bad
choice will lead to bad consequences. The choice
can lead to negative probabilities, but then players will (rightly) whinge that
they've been given a choice between two options, and one option is inevitably
inferior. (See Choice.)
4.
In a player-vs-player or
player-with-player world, a player's choices could have all sorts of
interesting (and relevant) effects on other players.
Unfortunately for the theme, its up to the other players to decide how to
respond. The author has no input, so any attempt at the author's version of
theme is tossed out the window. (One of the reasons that players like PvP
worlds is that their actions have consequences.)
Rethinking
theme
While
virtual worlds are horrible at tying specific results to actions, they're great
at algorithmically calculated results, and very good at having choices change the "physics" that
the player character experiences.
In
the Moby Dick example, the author can't guarantee the loss of life, ship, and
crew, but the author can design the world's "physics" such that:
1.
The PC needs collect so much whale oil
or he won't be hired as captain again.
2.
Moby Dick is a nasty whale
that has a good chance of destroying the ship, along with the PC and crew.
3.
The PC gets a non-monetary reward
(XP?) for killing Moby Dick. XP may not be necessary as a
motivation, since the satisfaction of getting revenge might be player driven,
not PC driven. However, if the author specifically designs in the choice,
"Kill Moby Dick or make money," then both choices must have, on
balance, equally good/bad outcomes. Since killing Moby Dick is more dangerous
than killing lots of smaller whales for their oil, killing Moby Dick must
provide some additional reward. It can't be money though, since choosing to
collect whale oil returns a monetary reward.
The
choice is then left up to the player. There are no guaranteed outcomes though.
Alternatively,
in the religion scenario:
1.
Different NPCs may be friendlier
towards PCs of one religion or the other. Choosing
the change religions means that NPCs that used to like the PC may not, but
other NPCs may suddenly like the PC. It's up to the player to decide how to
deal with the change.
2.
PCs following the Christian religion
may have different restrictions and abilities than those of the Hindu religion.
Likewise,
the player can make the choice and experience the results of their choice. A
guaranteed negative outcome for one choice, while possible, will cause the player
to (rightly) complain.
I
chose the change-religion theme because it's very similar to the choices a
player makes about his character's race, class, armaments, etc. In a virtual world, many important
choices don't lead to positive or negative consequences for the player.
Instead, they result in changes to the "physics" under which the
player's character operates. A fighter can wear armour and
wield large weapons, while a magic user must forgo weapons and armour, but can
cast spells. The fighter and wizards exist under different physics regimes. A
fighter attacking with a sword experiences slightly different physics than one
attacking with a mace since a sword works better against plate armour, while a
mace is more effective against chainmail.
An author can impose a theme on the virtual world by
controlling the specific consequences that result from the player's choice. The
consequences must balance out, or it won't be a valid choice.
(Obviously-idiotic choices excepted.) (See Choice.)
A
world that wishes to make a point about religions would allow the player to
select a religion, and make the difference between religions severe. For example:
1.
NPCs are more or less friendly to PCs based
on the PC's religion.
2.
PCs have dietary restrictions based on
religion, that pose minor but frequent problems.
3.
PCs have religious duties, such as attending
Church or praying toward Mecca five times daily. Can PCs do anything but
worship on their holy days?
4.
How does a PC's religion affect the
character's resurrection after being killed?
5.
How does a PC's religion affect their conduct
in the world? In combat? In economics? (In medieval Europe, Christians weren't
allowed to charge interest, but Jews were, which is why they became bankers.)
6.
What special abilities does a PC get from his
religion?
7.
Etc.
What
I propose is different
to what contemporary virtual worlds offer. Even though they let
the players make choices about their race, class, guild, and sometimes
religion:
1.
Contemporary virtual worlds rarely
make the consequences of a choice severe enough to make them noticeable.
For example: Classes are watered down so they're similar. Even if fighters
aren't allowed to cast magic, so many magic items are handed out that it makes
little difference. Magic users are archers who shoot fireballs instead of
arrows. Etc. Ultimately, one class is pretty much interchangeable with another.
Races are even more identical, and only vary in their appearance.
2.
Contemporary virtual worlds tend to
use the same consequences as one another. Fighters in
world A have similar advantages and disadvantages to fighters in world B. The
same with magic users, clerics, etc.
A
thematic world might model fighters after medieval knights, and only allow them
to initiate an attack against other knights or heathens, and require that they
rescue all damsels in distress. A thief (or brigand) could attack anyone, but
wouldn't have the weapon training, and would be disliked everywhere. A different
thematic world might model fighters after Samurai, or Massai warriors,
including their skills and belief systems.
Reality
vs. fantasy
This
brings up another point. MMORPGs err on the side of fantasy and "low
impact"; MMORPGs
give players the experience that the players expect, and don't try to add any surprises.
They don't want players whinging that their magic user can't attack a monster
using a sword, or that their fighter can't use magic. Consequently, and over
the decades, they water down the restrictions until there is very little
difference in the choices that players make, and very little possibility for
"theme". The world loses its "flavour" and becomes like
McDonalds' "Spicy Chicken Burger", which isn't at all spicy.
My preference is to err on the side of reality, or at
least "flavour". A fighter should
be a very different experience from a magic user, cleric, or thief. Being a
dwarf should be very different than being an elf, human, or halfling.
As long as reality is not too onerous, some doses of
reality should be thrown into the experience.
If a player wishes to own an inn, fine. However, make them maintain a store
room full of goods, hire NPC employees, deal with repairs after bar fights,
attract patrons, etc. Or, if the player chooses a religion, they must
experience some of the sacrifices that come with the religion. Adding a bit of unexpected reality
turns the game into a learning and thinking experience, which
is what a theme is all about. Without
a theme, the experience is just empty, tasteless calories, a kind of
"McWorld".
"Good-for-them"
desires
In
Virtual
world equation, I talked about virtual worlds fulfilling
players' desires. Some desires are conscious and some sub-conscious. I also
brought up the point that some desires might not be known to the player, but
might be "good" for them. One example is Richard Bartle's
"Hero's journey", where players use the anonymity in virtual worlds
to experiment with different roles and determine who they really are.
Stories
frequently embed "good-for-you healthy bits" in their sugary
entertainment. For example, if you read Moby Dick, you're going to learn a lot
about the whaling industry of the 1800's, whether or not you wanted to. Upton
Sinclair's "The Jungle" is a social documentary about immigration and
poverty. Even a medieval fantasy novel will contain some truths about the
middle ages.
Virtual
worlds can also include "good-for-you healthy bits"... They can
encourage players to:
6 April 2005
by Mike Rozak
I
mentioned role-playing points in Virtual
world equation, and neglected their identical twin,
experience points...
Traditional
MMORPGs and MUDs reward experience
points (XP) to PCs whenever they kill monsters or complete
quests. The origin of this goes back to pen-and-paper Dungeons & Dragons.
The
fictional justification is that the more monsters your PC kills, the more
skilled your PC becomes at killing them. In other words, practice makes perfect.
This makes sense.
The
game-play reason for experience points is that they're used to advance the PC
to its next level, or the PC's skill to its next level. Levels are a useful
game-play device for the following reasons:
I
agree with using levels (class or skill) as a gameplay device. However, in my
opinion, awarding XP for
killing monsters is a stupid thing to do. (I stand virtually
alone here; Almost all MUDs and MMORPGs award XP for killing monsters.) Here's
why I think it's stupid:
Role
playing points
XP should be handed out when players do their part in
making the world are more-enjoyable place for other players.
This can be done by:
Conclusion
I like to boil down a problem to "first
principles". Sometimes the "first
principles" my thought experiments produce agree with current practices.
Sometimes they disagree.
In
this case, my thought experiment has produced first principles that vehemently disagree with MMORPG's
contemporary use of experience points.
Is
my thought experiment correct? Could 99% of all MMORGs, with millions of happy
users, really be wrong?
Awarding
experience points for killing monsters seems to lead to "the grind",
which is prevelent in 99% of all MMORPGs. Awarding experience points for
behaviour is common in roleplaying MUDs, so I suspect it leads to more
roleplaying, and to an experience in which "players are part of the artwork". Most players will
probably resent this; role playing text-MUDs, after all, are a very small
market.
17 April 2005
by Mike Rozak
To
state the obvious, a
standard MUD/MMORPG has the following features:
This
ubiquitous list appears in virtual every MUD/MMORPG "feature list"
web page, along with their documentation's table-of-contents. The implementation specifics change from
game to game, such as what races or spells are available, but not the generalities.
(Some MUDs/MMORGs don't even have different races and spells, relying on the
same humans, elves, dwarves, halflings, and fire-ball spells.)
In
abstract "game" terms, this means that most MUDs/MMORPGs are
essentially the same game, but with different window dressings. In his book,
"The Hero with a Thousand Faces," Joseph Campbell says the
same about many hero myths. Hence, the title for this article; While there are thousands of MMORPGs and
MUDs, they are really the same game with different faces grafted on.
I
found such homogeneity disturbing, so I spent time searching for different
"genres"
in descriptions of the thousands of existing MUDs and MMORPGs. It seems that a
handful of genres covers almost all MUDs and MMORPGs:
I have labelled these sub-genres because it's possible to
produce one virtual world and customise the shards of the world to produce
different sub-genres. For example: World of Warcraft has mostly PvE shards,
some PvP shards, and a few role playing shards. Aficionados of a sub-genre will
point out that failure to specialise in a sub-genre produces an inferior
experience; Many MUDs/MMORPGs specialise in only one sub-genre.
The
"Standard MUD/MMORPG" genre attracts the bulk of the players
(80%-90%), with PvE worlds being the largest sub-genre.
When
I asked about this issue on the Mud-Dev
mailing list, several people replied that other genres undoubtedly exist, but
that they haven't yet been discovered. This could be because of:
Looking
at the problem in a different way
Since
I couldn't find other genres, I thought I'd do a thought experiment to bypass
my near sightedness...
I
asked myself: What
fundamental technologies are used to produce a MUD/MMORPG? The
answer:
These
statements are generalised... Text MUDs don't use graphics, and many MMORPGs
are still based on 2 1/2D sprites. However, 70%(?) of all virtual
world gamers are using 3D accelerated engines, and this percentage continues to
increase. Likewise, not all virtual worlds have artificial intelligence, sound,
a scripting language, persistence of the world, or the ability to seamlessly
transfer new content/software. The trend, however, is for their inclusion.
Now
that I know what technologies are used to make a virtual world, my next
question is: What genres
of entertainment can be produced using such technology?
To
use an analogy: A book is made from paper and ink. An origami crane is made
from folded paper (and perhaps some ink). An origami crane is obviously not a
book. However, a pop-up
children's book has the properties of both an origami crane and
a book, so it is a book? Most
people would agree that it is a book. Without stepping "outside
the box" to a different level of abstraction, pop-up children's books
would never have been invented. Can different virtual world "genres"
be discovered using a similar technique?
Existing
single-player game genres
Once
a developer has developed the technology necessary to produce a MMORPG, the
following single-player game genres can "easily" be created from the
same technology: ("Easily" is a relative term.)
Some
genres require different technologies and can't be easily produced from a
MMORPG's components:
What
else can be done?
I
brainstormed some other games (and entertainments) that could be created, using
essentially the same technology that's present in any MMORPG. These ideas are half-baked, and most won't work.
They do illustrate some
possibilities, and (at the very least) a different way to
approach the problem:
But
are they really "virtual world" genres?
Some
of the ideas I described are virtual worlds, but most don't subscribe to any commonly
accepted definition of "virtual world", even if the
definitions are stretched beyond recognition; they are not virtual worlds. If
it's any consolation, they do use the same
fundamental technology that's needed for a virtual world.
If
you can get past the fact that virtual world technology is being used to create
something that is not a virtual world, you face another hurdle: Undoubtedly, most of the ideas won't work.
One or two of the ideas might succeed, and might provide a welcomed alternative
to the standard (and cliche) MMORPG formula that's used today. Unfortunately, I
can't tell you which ideas will work, if any.
Even
if none of the ideas work, the approach of trying to build something new with the
technology pieces from a MMORPG might prove fruitful.
26 April 2005
by Mike Rozak
In
my continuing
quest to discover alternative genres to the standard virtual world, I have come
up with another approach...
The
parlour
The
most basic virtual world is a chat
room, so I'll begin there.
Imagine
that you create the ultimate chat room. It has every conceivable chat feature
that you'd want in it, including voice-chat, E-mail, BBS, rendered avatars with
full emote abilities, and a traversable world with fantastic scenery and sound
effects.
Unfortunately,
as we all know, players will eventually become bored with the chat room. Often
times, players will get in a "rut", hanging out with the same
friends, and never meeting anyone new. (Meeting new people is difficult and
somewhat stressful.) The same old friends night after night eventually becomes
tiresome.
To
make meeting new people easier, and to alleviate some of the boredom with
existing friends, the chat room adds social
games, such as cards, checkers, and maybe even chess.
Players
eventually get bored of these social games, and want an continuous supply of new
games that they can use for entertainment, socialising with friends, and
ice-breakers to meet new people.
At
this point, a chat room can take a variety of approaches to the game problem,
with two opposite poles:
1.
It can add sub-games where a group of people
chose to enter the sub-game and can subsequently exclude new entrants. Card
games and board games all act this way.
In
virtual world terms, this turns the chat room into a "lobby". Players meet in the lobby, and then go into
their own private rooms to play sub-games. The rooms' walls
might be transparent, letting other players watch, but players still have the
right to control who is let into their sub-game.
2.
The sub-games could be playable by all
players in the world. Players don't
need to sign up to play, nor can they be excluded. Everyone is
in the same game; in virtual world terms, this extreme is a "sand box".
The
sand box
If
a virtual world consistently adds sand-box style sub-games, it eventually turns into a standard
MUD/MMORPG, with features such as: Guilds, PvP combat, PvP
trading, creation, housing, crafting, resource collection, etc. (Note: I'm intentionally leaving out quests;
I'll get to those later.)
When
a lot of players are placed into a world, their desires
and activities often conflict with one another. Role
players will get annoyed with non-role players. Only one guild will be able to
slay the dragon of uber-loot when it spawns once every two days. Twenty players
will want to fill the one slot allocated for a king. Etc. Basically, desirable resources are scarce and
contested, whether they're dragons, kingdoms, inns, or
intangibles like being top on the character ranking.
The
virtual world designers (or some algorithm) must figure out which players get
to use/control the resources, and hence, which players are able to fulfil their
desires and goals. The mechanism that decides which players get preference is
very important, and how
much the mechanism favours a player affects how "powerful" the player
is.
Players
realise this. Once they have determined a goal/desire to fulfil, players try to acquire the power
necessary to overpower other players competing for the same resource.
Basically, they do what it takes to be successful, whatever their definition of
success is.
Many
years ago, I concluded that to be successful (in real life), you need luck, skill/intelligence,
and/or hard work.
(The definition of success varies from person to person.) Thus, if someone
wishes to be a millionaire, they can win the lottery, be smart enough to invent
a best-selling gadget, or work 16 hours a day for the rest of their lives. A
combination of two or three of the factors improves one's chance of success.
Designers need to decide what factors will make a player
a success, and consequently, how power is achieved and goals/desires fulfilled.
The following mechanisms are possible:
I have been using the term "power" a bit too
loosely. I have treated "power" as a
one-dimensional value, but it actually represents a very complex and
highly-dimensional comparison. The same problem exists in physics, where the
term "energy" is conveniently tossed about, despite there being many
forms of energy: kinetic, potential, heat, etc. Energy can be converted from
one form to another, but the conversion is always lossy. Likewise,
"power" can be converted from one form to another, but it is always
lossy because there are many different forms of power, and one player's
valuation of one form of power is different from another player's.
My
intuition tells me that a world should allow luck, skill/intelligence, hard work, and altruism to be
factors capable of achieving power. Maybe even real-life money should
play a role. If this is the case, then contemporary MMORPGs are severely
deficient on their use of luck and altruism, and nearly as deficient in
skill/intelligence.
Some
other tricks can be used to make the power flow more efficiently, and thus
produce a better game.
The
lobby
The
opposite approach to the sandbox is to allow people in the chat room to enter
their own private worlds
and play games with one another. These games can be any
computer game playable by a small group of people. CRPG and adventure games are
the most obvious sub-games, but auto racing, flight simulators, space combat,
and real-time strategy games are also possible.
If
the lobby just acts a gateway to traditional computer games, why would a player subscribe to a
lobby-like virtual world and not just buy the stand-alone games individually?
Why would a player wish to partake in a lobby-world
instead of a sandbox?
What
are the advantages for
the author?
What
are the disadvantages?
In-between...
instanced dungeons
What's
between a lobby and a sand box? A
world with instanced dungeons, like GuildWars.
The
difference between GuildWars and the lobby I described above is that nothing
goes into or comes out of the sub-games in the lobby. When a player starts a
lobby sub-game, a new
character (appropriate to the sub-game) is generated with all its equipment,
and is destroyed (along with the equipment) when the sub-game finished.
GuildWars allows players to bring their own characters into a sub-game along
with their own equipment, and likewise, changes to their character or equipment
is brought out of the sub-game.
An
instanced dungeon
is a sub-game where players can bring characters and equipment in and out.
The
advantages
of players being able to bring
their characters and equipment into the sub-game are:
The
disadvantages
are:
In-between...
quests
The
next step up from
instanced dungeons are quests and areas of the world that are
typically only visited by one group at a time. Quests are semi-private
sub-games that other players can barge in on, if they wish.
Quests
have the following advantages
over instances dungeons:
And
the following disadvantages:
Conclusion
This
thought experiment has provided a few interesting results:
19 May 2005
by Mike Rozak
This
writeup is a rehash many of the thought experiments I've had in the past year,
and explains why (I think) traditional MUDs and MMORPGs "work". If
you haven't read other parts of my search for other genres,
you might wish to do that now.
The
quest
Before
I talk about traditional MUDs and MMORPGs, I'll explain why single-player CRPGs
work. However, to explain why CRPGs work, I first need to explain how quests
work, since CRPGs are basically a series of quests.
A
quest
is:
1.
A series of sub-games,
such as combat, travel, searching, puzzle solving, and talking with NPCs. The sub-games should be reasonably fun
in themselves. (See Raph Koster's book, "A theory of
fun".)
2.
The sub-games are tied together by a narrative that explains why the
specific sub-games are to be played, and which often explains
the order they're played in.
3.
The narrative provides a motivation for the player's character
to undertake the quest, such as "To rescue the princess."
4.
The quest also provides a motivation for the player,
such as "Find the sword +5 of demon slaying."
Combining
a series of sub-games together into a quest, and providing a reason for the
sub-games to be played, introduces some story
into the world., and makes the experience more fun. Quests also prevent players
from overdosing
on any particular sub-game to the point being sick of it.
Single-player
CRPGs
Single-player
CRPGs are basically a series
of quests that ultimately lead to a grand finale.
CRPGs
work because:
1.
Quests are fun.
2.
The variable
reinforcement scheme of experience points and loot encourage
play.
3.
As characters level up (and become
more powerful), the sub-games are changed with the
addition of new skills, magic spells, items, and monsters. These changes
prevent the player from getting bored.
4.
Character levels and meta-quests provide goals that go beyond just finishing
the shorter quests.
5.
Other sub-games appear
when the players get loot and experience points from quests, such as resource
allocation.
6.
CRPGs provide a sense of advancement, self-worth,
completion, and escapism.
7.
Plus, they're a great time waster.
Traditional
MUDs and MMORPGs
Traditional
MUDs and MMORPGs "work" because:
1.
They're CRPGs,
and are successful for the same reasons that CRPGs are successful.
2.
MUDs and MMORPGs allow people to play with their friends,
or meet new friends.
3.
Because thousands of players exist in the
same world, new
sub-games appear. These include trading, crafting, castle
sieges, ship-to-ship combat with a crew, etc.
4.
Competing against other players is
more challenging, and much less mind-numbing that
computer AIs.
5.
In CRPGs, players kill monsters and collect
loot so they can level up, get new skills, and fight new monsters... which
basically results in interesting changes to the sub-game just as they were
getting bored. MUDs and MMORPGs also take advantage of this connection between
level and evolving the sub-games.
However
MUDs and MMORPGs also
use monster slaying for the "grind", along with
crafting and some other sub-games. The grind is a mechanism that determines how
much power
a player will have relative to (and to be used against) other players.
Basically, the more work a player puts into their character, the more power
they have, and the better able they are to outrank other players and fulfil their
desires.
6.
MUDs and MMORPGs provide ways for players to wield their power
and fulfil their social desires. These include: PvP combat and
various rankings (alpha-male syndrome), running guilds, controlling cities,
organizing events, playing politics, etc.
7.
Players vary in how much they care about the
social (power) aspects of the game. Some
just want a single-player game or just want to play with
friends. Others have
desires that can only be met by having other players around.
See The player pyramid.
To
handle this dichotomy, many traditional MUDs and MMORGs create two games in
one. The first involves lots of quests and is basically a single-player CRPG
that can be played with friends. The second layer, often referred to as "the elder game",
is about the power aspects of the world. Those players who are not interested
in the power plays (or long-term socialisation) leave when they run out of CRPG
content. The elder game won't work without non-power players around, so players
interested in the elder game must play in worlds with single player CRPGs. They
simply find a way to get through the CRPG aspects of the game as quickly as
possible. Often this includes buying in-game power with real-life currency.
Why
adventure-game virtual worlds don't work
In
The
trouble with explorers, I tried to determine why Uru
Live failed. The culmination of all my thought experiments (to date)
provides the following explanation.
1.
Adventure games are also based on quests.
However, adventure-game quests use a large
variety of puzzles instead of a few sub-games. CRPGs repeat the
same sub-game over and over again. Adventure games try to make every sub-game
unique.
2.
The problem with puzzles (of the adventure
game variety) is that once they're solved, they're solved. They can only be played through once,
and their solutions are quickly posted on web pages.
This
characteristic has many ramifications for a virtual world...
3.
CRPG combat is more fun when played
with a group of friends, or against real people. Solving
adventure game puzzles with friends it not necessarily more fun, and often it's
less fun, since one
player seems to come up with most of the solutions, leaving the
other players as dead-weight.
4.
If player A plays alone and solves all the
puzzles in a quest, he
cannot replay the quest with player B (who has never played
it), because A already knows the solutions to all the puzzles, and has to spend
his time trying not to look impatient.
5.
Since puzzles are inherently about the
player's intellect, and not about something the player's character knows, adventure games don't include
"levels", losing one of the devices that make CRPGs
fun.
6.
Because the solutions to adventure game
puzzles can be posted on the web, they
cannot be used for "the grind", and thus can't be used to control which players
have power. Without a device to acquire power, the elder game
isn't viable. If another device is added for the elder game, such as resource
gathering, then the two games become completely different and disjoint; they
might as well be in completely different worlds.
Basically,
adventure games don't become any more fun when played with friends, and there's
no way to use an adventure game as a base for the elder game. You can create a virtual world based
on an adventure game, but players will play through the content in 20-50 hours,
mostly in isolation, and then leave because there's no elder game.
Note:
Puzzle Pirates includes puzzles, but is not an adventure game. It uses a
handful of puzzles to create "the grind", instead of
relying on the combat sub-game. Other than the substitution of sub-games, it
follows the same formula as traditional MUDs and MMORPGs.
Some
thoughts to take away
A
traditional MUD/MMORPG is written to the following formula:
1.
Figure out a set of desires that
players have which can only be met by a virtual world with thousands of other players.
Not surprisingly, these desires are usually social in nature, such as running a
guild, being number one in a player ranking, or being able to beat up on other
people. (The top of the player pyramid.)
2.
Create a game (or activity) that is
fun to play single-player, and even more fun to play with a group of friends.
The game must also withstand Internet latency and bandwidth, and satisfy
condition 3. Ideally the game should lead to new sub-games (like economics)
when used in a world with thousands of players. (The bottom of the player
pyramid.)
3.
Figure out how to use the game as a mechanism to
determine how much power players at the top of the pyramid get.
Players use their power to out-rank other players to control resources they
need to fulfil their desires. Thus, produce "the grind", where the
player with the most time (or money) on his hands wins.
4.
Produce a win-win relationship that
binds the top of the pyramid to the bottom. The top of
the pyramid must offer something that the bottom wants, and vice versa. In many
virtual worlds, the players at the top subsidise play for players at the
bottom.
Some
virtual worlds seem to put more emphasis in #3 and skimp on #4, or vice versa.
For example, a traditional MUD/MMORPG is almost all #3. Second Life is almost
all #4. I'm not sure why this tradeoff exists.
19 May 2005
by Mike Rozak
In
my continuing quest to find alterative genres for virtual worlds, I recently
had a bit of an epiphany... or at least came up with a different way of looking
at the problem...
Genre
= what makes it fun
Don't define genre by
the code functionality, as I did in The game
with a thousand faces, but define genre by what makes the virtual world fun.
Wielding
this definition, I came up with the following genres:
|
Primal |
The virtual world allows
players to fulfil
their primate and animal urges that aren't met in real life. Virtual worlds are particularly good at fulfilling
socialisation urges/needs, such as friendship,
belonging to a tribe, gossip, rivalries, child rearing, and being the alpha
(fe)male. |
|
Reptilian |
Primates evolved from
reptiles, which have even more basic drives. An occasional hit of adrenaline and violence is fun. Food and sex could also be
included. |
|
Comedy |
Comedies do very well on
TV and at the movies. Comedy doesn't seem to translate well into games or
virtual worlds though. |
|
Inductive |
These worlds require that
players do the same
thing over and over again, with variations on a theme.
The repetitive action keeps the player in familiar/safe territory, and the
variations require just enough mental energy to make the game interesting. In
many ways, this type of activity is similar to what humans have been doing
since the beginning of time, hunting
and gathering... which is the main sub-game in most MMORPGs. |
|
Cool new stuff |
Movies have an unofficial
(and critically derided) genre, the "Special effects movie". For the
most part, people see SFX movies because they're visually (or acoustically)
new. I don't know if this rates as a virtual world genre, but it might. |
|
Creation |
Players enjoy creating
their own objects, or otherwise changing the world. |
|
Deductive |
Fun comes from
encountering entirely new sub-games and learning how they work. Each sub-game
requires a different solution. |
|
Experience (and exploration?) |
The world tries to portray
an authentic
experience for the player to partake in. Flight
Simulator (not a virtual world) is an excellent example. The
"fun" of Flight Simulator is having all the real-life controls of
Cessna or Boeing 747 in front of you, and flying the simulated air plane.
This is different from the "fun" of a combat flight simulator,
where the game doesn't try to be realistic, just adrenaline packed. Worlds: Role-playing worlds
and historically
accurate worlds. |
I
ordered the list
according to my best guess at the genre's market size, for TV,
games, or virtual worlds. For example: Commercial television is loaded with
shows from the top of the list, but has relatively few mysteries (the current
being CSI, which is part "cool new stuff") and very few period
dramas.
Some popular TV/movie genres are conglomerations of other
genres; Cop shows, for example, are often dramas
(primal) with some deductive and experience elements. Some genres don't seem to translate to
virtual worlds at all; News broadcasts are informative, which
removes the interactivity from virtual worlds. Changing the information into
experience might work though.
Of
course, most virtual
worlds can be categorised into a few different genres, and
include minor elements of all genres. Everquest II is primal, reptilian, cool new stuff, and inductive. Second
Life emphases primal
and creation.
Genres
or player types?
To
demonstrate why genre (using this definition) is important, let me give an
example. Lets say you introduce a new feature: Sailing ships and the ability for one player to be
captain while other players act as crew.
You
need to ask yourself, "Why does the player wish to be captain?" and
"How does that motivation affect the implementation of the captain
sub-game?"
Described
this way, my genres look
an awful lot like player types, although they differ from Richard
Bartle's killers, socialisers, explorers, and achievers, or Nick Yee's
facets. (In this document, I actually lump killers, socialisers, and some
achievers into the "primal" category.)
If
you try to create a world that caters to all genres, then my genres ultimately turn into player
types. But, to create a world that caters to all genres (player
types), you have to do one of the following:
Of
course, there is one other option:
All four of these solutions are viable,
but they all have tradeoffs. If the restaurant business is any guide...
The
other problem...
Virtual
worlds are not entirely like restaurants; restaurants (almost) never get in the
situation where a dish is in limited supply and only a small percentage of the
customers that want to order it are allowed to get it.
Inevitably,
if a world has 100
players, 20 of them will wish to captain their own ship, but there will only be
5 slots. (You could create more captain slots, but then the
world wouldn't have enough players to be crew.)
How
does the world decide who gets to be captain? (Copied from The
parlour, the lobby, and the sand box.)
Designers have the same problems here as with genre
coverage. A world can allow "all of the
above" as ways to be captain, or it can specialise in certain types of
players.
Does
that mean that the criteria used to determine which players get power ends up
producing another dimension to the genre?
Conclusion
It
seems like genre can't be defined by codebase, or what player types a world
caters to, or how power is distributed. There are too many degrees of freedom:
I
suspect that "genre" will fall out of the choices as more worlds
become available. Single-player games and MMORPGs already have some
associations forming. For example:
Undoubtedly,
more genres and sub-genres will follow.
17 June 2005
by Mike Rozak
Below
are some dimensions that can be used to classify games:
Theme:
Available sub-games and activities:
Characters:
Miscellaneous:
17 June 2005
by Mike Rozak
In
his books, Chris Crawford points out that to design a game, you must ask the
question, "What
will the players do?" Although it's a simple question,
many games don't seem to have a decent answer to this question.
When
I think about the question, "What will the players do?", some related
questions come to my mind:
I
have discussed the idea of "What players want to do" in some previous
articles, such as in Virtual
world equation, where I provide several examples of
desires/goals that players wish to fulfil. However, I assumed that players would eventually be able to
fulfil those desires, as they imagined them. This is not necessarily the
case...
To
illustrate my points, I'll draw on two example desires/goals that a player
might have:
Working
towards a desire/goal
When
I was young (around 10 years old) and was GM-ing my first Dungeons &
Dragons campaign, I quickly realised that my players wished to be powerful
within the world, and able to defeat any monster they came upon. Not wishing to
disappoint them, I handed out some fabulous magic items that made their
characters virtually invincible... As you have undoubtedly guessed, it ruined
the game.
Likewise,
if a mountain climber were instantly teleported to the summit of Mt. Everest,
they would be very unhappy. However, someone wishing to run an inn might not
mind being given an already-working inn to run, because the fun part for them
is the destination.
When
designing the path to achieving a desire/goal, the designer might want to
consider the following:
Experiencing
the desire/goal
What
happens when the player achieves his desire/goal? What is the summit of Mt.
Everest like? What does an innkeeper really do for his job?
After
the desire/goal has been achieved
At
some point, all mountain climbers (except the dead ones) must leave the summit,
and an innkeeper must give up his inn. What happens then?
Complexities
Throughout
this document I have discussed a player's desires and goals in somewhat
simplistic terms:
What
does this mean?
20 July 2005
by Mike Rozak
When
designing a virtual world, the topic of "story"
vs. "game", or narratologist vs. ludologist, continually surfaces.
It is an inescapable problem: People
want to interact with a story (which includes intelligent NPCs,
a world designed to maximise the telling of the story, and coincidences
galore), while they
don't want to accept the consequences of a story (lack of free
will).
The
more I ponder the problem, the more I conclude that it's endemic to reality,
not just virtual worlds.
I
already wrote up some thoughts in Story and plot vs.
freedom in virtual reality. Here's a quick summary:
1.
In a world (a collection of physics,
geography, etc.), there
are practically an infinite number of outcomes for "what
happens". Star Trek likes to call these outcomes "alternate
realities".
2.
Most of these outcomes (alternate
realities) are uninteresting to the reader/player of the world.
What makes an outcome (un)interesting is reader/player specific, but I
investigate the idea in an Evolutionary explanation for
entertainment, along with many other writeups in DRT.
3.
A story is a narrative chosen from one
of the more interesting outcomes (alternate realities) that can be produced by
a world. Since the author of a story is also the
inventor of the world's physics, geography, history, etc., he has a pretty good
idea what outcomes are possible and which lead in the most interesting
directions.
4.
A game leaves all the choices up to
the player. Unfortunately, a player doesn't know much
about the world. He might know the physics, but he doesn't know too much about
the world's geography and characters, or where the character are and what
they're doing at any point in time. The
result is a series of choices that usually result in uninteresting consequences,
since (after all) most uninformed choices lead to uninteresting outcomes.
5.
What the player really wants is a
world with choice, where every choice leads to an interesting outcome;
which is a combination of a story and a game.
Unfortunately, a mixture of story and game is like mixing
oil and water.
1.
If
a player is expected to make choices that usually result in
interesting outcomes, the
player must know a fair amount the world he is in.
2.
The more the player knows (or the more
intelligent the player), the better the player is capable of determining if a
choice is a good choice (or a bad one) based on a "how
interesting is the outcome" metric.
3.
If the player can clearly identify a
few best choices, weeding out lots of really
undesirable ones, the
player is suddenly faced with fewer choices. As I pointed out
in, Choice,
a choice that is obviously bad isn't really a choice, since no one will chose
it.
4.
Therefore, a player who increases his intelligence/knowledge in
order to be able to make choices that result in interesting outcomes ultimately
finds that he has no/little choice.
5.
An experience without (much) choice
turns into a narration. Frank Herbert pointed out this
dilemma in the Dune series, where the prescient Mua 'Dib was left
without choices.
6.
Narration does not work well as a game
because (a) games are supposed to be about choice, and (b) television/movies
are inherently better at narration than games.
NOTE:
A good narrative has
interesting outcomes that are not 100% predictable. A good
author never gives readers exactly what they expect, perhaps an "imaginary
number" version of choice.
7.
Thus, players
that learn enough about the world to accurately predict its outcome (and
reliably produce interesting outcomes) don't enjoy the world because they have
no choices. Players that cannot reliably predict the outcome don't enjoy it
either because most choices will result in uninteresting outcomes.
8.
There is a fine line between a
predictable world with interesting outcomes but little choice, and an
unpredictable one with lots of choice but uninteresting outcomes.
Raph Koster's book, "A Theory of Fun", comes to a similar
conclusion, that gameplay is about understanding the pattern, getting bored,
and moving onto new patterns.
The
introduction of God
All
is not lost though; it
is possible to mix oil and water, and story and game.
Unfortunately, the methods are not always palatable.
Table-top
RPGs successfully mix story and game; they do this by designating one player as
a game master (GM).
The purpose of the GM is to create the world, play the NPCs, and adjudicate the
rules. Because the GM has so much knowledge about the world (which is mostly
made up on the fly anyway) and is friends with the players, the GM can tailor
the experience for the players. Basically, the GM tries to make the outcomes of the player's choices
as interesting as possible.
Most
of the time, the GM's subtle manipulation of reality is gladly accepted by the
players because they realise it's producing a more interesting experience. Once
in awhile, the GM's maneuverings annoy the players to the point where some
storm out of the room. They (usually) come back though because the players are also bonded to the GM
by friendship. They trust that his decisions are in their best interest.
GM's don't work terribly well in virtual worlds
because (a) they cost too much to hire one GM per six players, (b) 10,000 GMs
running around the world with 60,000 players is bound to result in conflicts
between the GMs, and (c) players will (in)correctly blame their or another GM
of favouritism.
Theoretically,
a very intelligent AI
could be written that would solve all of these problems.
Unfortunately:
1.
If the AI were more intelligent than
the players and/or knew more about the world (including
all the world's players), it
could subtly manipulate the world to maximise the players' fun.
2.
To know what was fun for each player, the AI would need a psychological profile
of each player. It would have to know how to produce in-world
events that would be interesting for the profile.
3.
A successful AI would create a world
in which players had full freedom of choice, and which
most choices would result in interesting outcomes (to the player). The AI would
also be able to juggle
the conflicting interests of the 60,000 players in the world.
4.
Unfortunately, many/most people don't like being
manipulated. If they think they are being controlled, they
either rebel or find ways to control their controller.
5.
Additionally, while players don't claim
favouritism from stupid (contemporary) AIs, one intelligent enough to run a world could be attributed
with favouritism.
6.
The solution is to hide the AI so that the player's don't
have proof that it exists; make the AI work "in mysterious ways".
How the AI's development team gets hidden is "an exercise left up to the reader"...
which means that I have no clue how it would be done.
If
it existed in the real world, the
intelligence that I just described would be called God. The
game AI's purpose is to make virtual life fun for the players. According to
modern religions, the purpose of the real-life God(s) is usually anything but
acting as the people's entertainer.
More
unknowns than knowns
While
discussing GMs, I glossed over another trick that can be used to mix story and
game. If the world is
filled with more unknowns than knowns, the GM (or AI) can always pull a
deus-ex-machina and claim that events just happened by chance.
It happens all the the time in the real world;
you're on holidays in the Grand Canyon and just happen to run into an old
friend. A penny happens to by lying on the ground, presumably fallen out of
someone's pocket.
Storytellers
ostensibly despise this device, although they still use it. They do disguise it
though, by describing why a character's friend was also in the Grand Canyon, or
who dropped the penny just 10 minutes before. It amounts to the same thing.
One
technique for introducing unknowns into a world is to fill the world with
armies of NPCs whose job it is to drop pennies or cause
"coincidences".
Fractured
reality
If five people witness a car crash, police will hear five
(or more) different descriptions about what happened.
You could attribute this to the human brain's inability to remember details
when it wasn't paying attention (which is the likely cause), or you could claim
that all five people were accurately describing what they saw; they just saw
different and conflicting events.
With
60,000 players running around a virtual world, at least one thousand of them
will want to be Sir Lancelot (or Napoleon). Unfortunately, reality dictates that
only one can be Sir Lancelot and one can be Napoleon.
The
solution?
Fractured reality; Allow
players to be Sir Lancelot (or Napoleon) in their own eyes, turning them into
Don Quixotes. So long has their delusion is not crushed by the chance meeting
of another Sir Lancelot, they'll be fine.
On
a more subtle scale, players
do not have to see/perceive exactly the same things. A dropped
penny might only be visible to the player that is supposed to pick it up.
Fractured
reality explains where all the pens disappear too, and why you always find your
glasses on the table that you just thoroughly searched two minutes ago.
Ultimately,
what does this mean?
I'm
not sure, but here are your choices...
20 July 2005
by Mike Rozak
Once
again, I've spent my time trying to understand how/why MMORPGs (and MUDs) work.
The thoughts contained herein are a conglomeration of elements from Richard
Bartle's Designing Virtual Worlds, some of my writeups (such as The player
pyramid and Intertwined
relationships), as well as my wanderings through MMORPG
message boards.
What
I noticed while
exploring MMORPG message boards is that (to no-one's surprise)
there are several different "factions" of players, with each faction requesting a different
set of features:
Other
feature factions are also present, either as faint murmerings or theoretical
possibilities:
The
ecology
What's
so important about feature factions?
They
can be used to explain why a MMORPG/MUD exists and is stable...
If
I begin with a CRPG
that's tailored for single-players, add multiplayer ability, and extend the
length of gameplay, I attract the following feature factions:
Some
things to notice about this ecology:
Other
ecologies
Some
feature factions are left out of the traditional MMORPG.
Ecology
== Genre
In
case you haven't noticed, the different ecologies correspond to "genres".
The key (according to this theory) to a successful world design is to develop a
successful player ecology.
20 July 2005
by Mike Rozak
I
am a single person trying to create a virtual world in a real world where
virtual worlds have budgets of $20M and rising. ($20M = about 200 man years). I am a mouse scampering amongst the
titanic feet of elephants.
Consequently,
I spend a large amount of time figuring out where the elephants will be
heading, to help ensure I don't get trodden on.
I
have written some of my thoughts about the subject in Small VW
operators vs. large, and Text vs.
graphics. I have some more thoughts, or at least some
realignment of thoughts, that I thought I'd share...
In
the last six months I have played both World of Warcraft and Everquest
II. Both of them are very impressive, with copious content and stunning eye candy.
There is no way I can compete on that playing field. I could spend three years
developing quests and graphics equivalent to WoW and EQII, and not produce
1/10th of what WoW and EQII contain. The prognosis gets even worse since the
bleeding edge is a moving target. Both these games were conceived and budgeted
over three years ago. The mass-market worlds begun today will have even larger
budgets (probably double, at $40M), and will be even more titanic.
Therefore,
I must admit to myself the following facts:
I
could try to create an army of volunteers to create an open-source WoW or EQII,
but then I wouldn't be a mouse any more. Herding such an army would be a feat
akin to herding cats, something I'm not terribly excited about doing. People
more politically skilled than I can do that.
So
what does this mean about any world I (or other mice) try to create? And what
are the implications?
1.
Content will be limited,
perhaps to 50 hours.
At
50 hours of content, the entire game changes. The experience will be more like
a single player game that you can play with friends or use to meet people. It
will have a beginning, middle, and end, and many of the sub-games that MMORPGs
rely upon, such as guilds and trading, won't exist. Players will regularly jump
from one world to the next. See my Anti-MMORPG
writeup.
My
realisation that I will have less content than a mass-market MMORPG is interesting;
I often think of my role as the author of a novel, while a mass-market MMORPG
is a movie. Since movies are shorter than novels, I would expect niche-market
worlds to take longer to play than mass-market worlds. The opposite appears to
be the case. Coincidentally, graphical worlds become more cost effective as
they get larger because art assets (and actor's voices) can be reused.
2.
Eye candy will be limited.
Graphics will still be needed, or the world won't attract any paying players.
The eye candy won't be stellar though.
I
need to replace the lost graphics with something, and that something is text. However, text
and graphics don't mix so I'll need to use speech. Since recorded speech is too
expensive, I must use synthesised
speech.
3.
Mice eat the food that's too small for the
elephants to see, or which is impossible for elephants to get to. A mouse can
eat the nutritious grass seeds while elephants must consume the entire grass,
which is mostly indigestible fibre. As a mouse, I will have to target niche-market audiences
that elephants can't/won't reach.
4.
It's cheaper to maintain a virtual
world than to build a new one from scratch. Does this
mean that small virtual worlds authors will create one world, and continually
evolve it for the rest of their lives? This
sounds boring to me, although it's precisely what Tolkien did.
I'd rather spend a year creating a world, then move onto another one, and then
another. Unfortunately, such a course would leave me with several small worlds,
all but one being "dead"; a dead world might still have players, but
it wouldn't be updated nor would it be monitored much.
This
is a dilemma for me: A small corporation could stick to one world since as
employees get bored with the world they move to a different corporation. A single
author doesn't have the option to move, and since they'd get bored, I suspect
single authors will abandon the old world to create a new one.
The
implications have implications:
1.
A 50-hour experience means:
1.
The experience will have a beginning,
middle, and end, and probably incorporate story
similar to the way an adventure game or CRPG does.
2.
The player's role within the world
will be specialised; players can be
"forced" into the role of detective or coal miner, and the experience
can revolve around being a detective or a coal miner. The traditional MMORPG
tank, spell caster, and healer will be rare.
3.
Some plots simply cannot be maintained
for a long period of time, but might be possible in shorter
form. You wouldn't want to spend 500 hours experiencing what it's like to live
in a concentration camp, but 5 hours of the horrendous experience might be
palatable and prove to be very educational.
4.
Players will be able to play with
friends, or team up with players online. Many
players will partake in these virtual worlds for the sole purpose of meeting
people (that they can then meet in the real world), using them as a form of
dating service. PvP, guilds, and other MMORPG social institutions are unlikely.
5.
For every elephant that roaming the
savannas, there are thousands of mice. Likewise,
there will be many more 50-hour worlds than large worlds.
By
the way, this has a sub-implication about procedural content... Smaller worlds will
reply on procedural graphics to cut costs, but content will be hand crafted. Procedural
content is most useful in everlasting experiences like Diablo.
6.
Shorter worlds will target gamers who
only play five (or fewer) hours a week.
2.
A verbal world can produce some forms
of content that a mostly-visual world cannot:
1.
Narration works much better verbally
than visually; "Orsin thoughtfully caressed the
rim of his wine glass with his finger before taking a small sip to taste, and
then quaffing the lot." You can try to animate this sentence, but it
will be very expensive (particularly when multiplied by the thousands of NPCs,
each with their own animation) and won't ever convey the same meaning.
2.
Similarly, role players can use narration to enhance their role play.
3.
Non-Euclidean space and non-contiguous
time are much easier to handle verbally.
4.
A verbal description can gloss over unimportant information;
"The princess lived in a white tower with a single window at the
top." What architectural style was the tower built in? What material? How
big was the window? Etc. Someone creating an image for the tower must fill in
all this information even though it isn't important for understanding the
experience.
5.
A narrative world lends itself to text
(or speech recognition) commands from the player.
Verbal commands can convey more complex actions than point-and-click.
3.
Niche markets include:
1.
More intellectual.
2.
People who speak languages other than
the top-6 languages. Similarly, a niche world could
target a Native American myths rather than relying on a European or Asian
mythology.
3.
More experimental.
4.
More extreme
- Large worlds can't be offensive or contain any ideas other than the bland;
Small worlds can have opinions.
5.
Special interest groups
- A virtual world based on Mycenaean trading, for example.
4.
"Dead" worlds
- I'm not certain about the implications of dead worlds. Maybe I'll write
something up later.
19 August 2005
by Mike Rozak
Hunting
and gathering
Chris
Crawford says that the first thing to decide when designing a game is "what players do".
To
my eyes, both The
World of Warcraft
and Everquest II are the same game because players
"do" the same thing. If you tear away the trappings of both games,
and attempt to distil them to their essence, WoW and EQII are about "hunting and gathering".
Players spend their time extracting resources (XP and loot) from the world and
then use the resources to level up their character so they can then extract
further resources. (This line of thought isn't at all new, by the way.)
Players
quickly learn that hunting
and gathering are made safer and more efficient by forming hunting parties
(called parties or groups). They also learn that forming tribes (called guilds) leads
to safety in numbers, a source of people to form hunting
parties with, and a venue for sharing wealth and knowledge. As Raph Koster has
pointed out, these tribes usually consist of 100-150 members, a social limit
that seems to be hard-wired into the human brain.
The monotony of repetitively hunting and gathering the
same objects is mitigated by quests, which provide a
purpose for the hunt. A player's quest list also provides an unending array of
satisfying milestones. If the completion of one quest doesn't produce a
follow-on quest, the player still has a large collection of quests waiting to
be finished. From my own play experience, quests are key; if quests weren't in
the games, both would be incredibly dull.
In
my opinion, The World of Warcraft creates a hunter/gatherer lifestyle
more successfully than Everquest II. In WoW,
players begin in a small village and complete quests that protect and aid their village. They
are then encouraged to visit their racial metropolis, where they continue to
protect and aid their
city... which isn't exactly hunter/gatherer any more, but still has a tribal
feel. Once part of the larger world, players learn their city is part of a
confederation of four aligned nations, which they must protect and aid. Part of
this duty includes raids on the enemy players' villages and cities. EQII wraps
quests in a much more capitalistic and modern guise, with most of the quests
ostensibly benefiting individuals in the city, not the community. At its
initial release, EQII didn't have player-vs.-player raids either. WoW has sold
more than four times as many copies as EQII. Does WoW's admission of its hunter/gatherer foundation
have anything to do with its higher sales?
WoW
and EQII are not alone in the hunter/gatherer foundation; Most MMORPGs (as well as most text
MUDs) follow the same formula. Theoretically, a single-player
hunting-and-gathering game could be written, but it would lose the important
aspect of community that a multiplayer game allows. Having other players to interact with
to the illusion of a hunter/gatherer lifestyle.
Defeating
the boss monster
What
I find fascinating about hunter/gatherer MMORPGs is that they evolved from text
MUDs, which evolved from single-player adventure games, which came from pen and
paper RPGs. Pen and paper RPGs have almost no hunting-and-gathering component.
To
illustrate how different MMORPGs are from RPGs and other computer games, let me
provide an example...
Imagine
that a player of a
MMORPG comes across a boss monster that's too tough to kill.
What does the player do to get around the problem?
1.
The player could keep attacking the monster and rely
upon luck to get through. After all, if the player's character
dies, it will be resurrected. Even if it requires a hundred deaths, the virtual
dice will eventually roll in the player's favour, and he will overcome his foe.
MMORPGs prevent this "try and try again" solution by introducing a
death penalty.
2.
The player could do some planning and come up with a strategy for defeating
the monster. MMORPG strategies plateau at "pulling
monsters"; more complex strategies don't provide much of an advantage.
MMORPG designers intentionally
design out strategic solutions, for a few reasons: (a) Any winning strategy against a boss
monster will be published on the Internet and will be known by
every player within days, eliminating the challenge of the boss monster and
unbalancing other aspects of gameplay. (WoW recently had such a problem
with one dungeon and declared the strategic approach "illegal".)
(b) Most players will forgo strategy and rely on options (3) and (4) anyway.
3.
Any boss monster can be defeated by a
sufficiently large group of players. All the
stumped player has to do is (a) team up with other players who are stuck at
same the boss monster, or (b) call in some friends. MMORPGs are, after all,
multiplayer games. (Of course, the more players involved, the less loot each
one gets. But since monsters re-spawn every minute, the nicer players wait
around so everyone gets a shot at the loot.)
4.
Because there is an infinite supply of
non-boss monsters to produce infinite experience that can be spent to increase
the character's power, a
stymied player merely leaves the unbeatable boss monster and returns after few
days when his character has levelled up. Players won't even
bother to come back unless the boss monster yields up a spectacular treasure,
or is blocking another part of the world that, in turn, yields spectacular
treasure, or some new scenery to break the monotony of hunting and gathering.
Compare
the MMORPG solution to the approach players take in other types of games:
Problem
solving
I
claim that most MMORPGs are about hunting and gathering. Conversely, pen and paper RPGs are about problem
solving. (Some role players might disagree; if so, just pretend
RPGs are about problem solving, for the sake of the argument.) When RPGs are
distilled to their essence, the players enter a room and are faced with a
challenge. They must come up with a proposed solution to the challenge and act
upon it. The challenge often includes combat, but can also take the form of
traps, puzzles, or NPCs that must be placated. Magic items and spells usually
provide new ways to overcome the challenge (such as invisibility or flight)
while magic items and spells in MMORPGs just result in more damage capability.
In all cases, a party
that spends ten minutes producing a strategy or plan is more likely to succeed
than one that just rushes in.
Hunting
and gathering (MMORPG-style) doesn't even vaguely resemble problem solving. If pen and paper RPGs are about
problem solving, how did their MMORPG descendants turn into games about hunting
and gathering?
The
transformation occurred when RPGs were translated from pen and paper (and the
minds of the GM and players) into a computer simulation. Human brains can imagine a much more
complex and varied "world physics" than can presently be programmed
into a computer. When the pen and paper RPG party encounters a
boss monster they can charge in and attack it, as can a party of players in a
MMORPG. The RPG party could instead send someone to climb into the rafters and
cut the rope that holds the 16 ton weight suspended over the boss-monster's
head; a computer can enable this solution, but only with a fair amount of
programming and 3D modelling. Alternatively, the RPG players could invent a
solution the GM had never imagined, like going to the store and buying
superglue, which they then splash all over the boss monster. The enraged and
perplexed monster then chases the party through a narrow crevice where the
monster gets glued to the crevice walls, giving players enough time to take his
loot; computers can't handle such creative solutions.
When a game whose basis is problem solving is translated
to the computer, the problem solving must somehow be simplified:
Back
to hunting and gathering
The "problem" to solve in a MMORPG is how to
deal with other players, how to cooperate with them, how to get them to do what
you need them to do, and how to defeat them. The
world is only a setting for these problems to arise (with some encouragement
from the world design) and then be resolved by the players.
The
hunting-and-gathering foundation works because:
1.
Hunting and gathering both encourage
players to work together. Players must solve the problems of
how to find and join a similarly-minded group. They must resolve personality
issues, resolve real-world schedule constraints, determine one's functional
role within the group, etc.
2.
Hunting and gathering are based on
limited resources, causing conflict amongst the players
over who harvests the resource. Players must solve the problem of limited
resources amongst themselves, through combat, negotiation, or whatever other
means they come up with. These issues must be solved for intra-group and
inter-group conflicts, where the group might be a party or a tribe.
3.
The formation of small groups (aka: parties)
and tribes (aka: guilds) produces social structures that, in turn, present
problems to members of the group/tribe. Players in
a group contents leadership positions and/or convincing the group to follow a
specific agenda.
20 August 2005
by Mike Rozak
Two
abstractions of "physics" are used in games:
Note:
Some designers (like
Chris Crawford) use the term "verbs" and "nouns".
"Attacking" and "digging" are the verbs, and all the
objects that can be attacked or dug are the nouns. In universal physics, few
verbs exist, but they work properly with every noun. In exceptional physics,
lots of verbs exist, but each verb only works with select nouns.
Each
abstraction of physics has advantages and disadvantages...
Of
course, games are a
mixture of universal and exceptional physics. MMORPGs, which
mostly rely on universal physics, use exceptional physics for conversations
with NPCs. Adventure games, which are based on exceptional physics, use universal
physics for movement.
Single-player
CRPGs vs. multi-player MMORPGs
Producing
a matrix of games that emphasise universal vs. exceptional physics, and single
vs. multiple player clarifies
some of the common game categories:
Changing
a single-player universal-physics game (aka: CRPG) into a multi-player game
(aka: MMORPG) has an interesting effect on the game, as I discussed in Problem
solving. Here's a brief synopsis:
1.
For a problem/challenge to be
interesting to the player, it must be carefully balanced so
that it isn't too easy nor too difficult. Because the player's own skill needs
to affect his rate of success, this usually means the player must apply some
thought/strategy to the problem to increase his chance of success. (In the case
of FPS, dexterity is also required.)
2.
In a game with persistent characters,
the designers must ensure that when the player's character reaches a
problem/challenge, the character's abilities and power will be well matched
against the problem, whether it's a monster, trap, or lock to be picked. If the
character is too weak, the player will never be able to solve the problem, no
matter how much strategy is used. If the character is too strong, strategy is
unnecessary, there's no challenge, and the game is no longer fun.
CRPGs
deal with the balance issue by ensuring that experience points (which increase
a character's abilities and skills) are doled out such that the character's
level will be appropriate for the challenge when he reaches it. This demands a finite supply of
experience points.
MMORPGs
must continually respawn monsters so that every player will get his chance at a
kill, which means there is an infinite
supply of monsters, and consequently, and infinite supply of experience points.
Hence, MMORPGs can't guarantee that a character's level will be appropriate to
the challenge.
Work-arounds
exist: MMORPGs can offer rewards appropriate to the character's level. A more
powerful character won't find the lower-level rewards beneficial, so he won't
(theoretically) undertake the challenge. In reality, many players find it much
easier to attack weaker monsters (and get fewer rewards more easily) than to
challenge level-appropriate monsters.
3.
In a game with multiple players,
scenario balance is impossible. If a single
player can't get past a challenge, he just waits until more players reach the
challenge, and they all tackle it together. Alternatively, the player can call
in his friends when needed. Of course, the treasure will have to be divided
amongst all the players. This isn't a problem since MMORPG monsters respawn
every minute, and the players can just re-kill the same monster until everyone
has the desired loot.
Work
arounds exist: MMORPGs with instanced dungeons don't respawn the boss monster,
who happens to have the best rewards. Players that wish to acquire the boss
monster's loot multiple times must work through the entire instance each time.
4.
Even if all these problems are solved, multiplayer games often become
competitive. Competitive players (a significant portion of the
MMORPG population) will read walkthroughs to make their life easier. Any problem that has a fixed
solution/strategy will appear on the walkthroughs, eliminating
the challenge for competitive players and unbalancing the game.
5.
Consequently, a multiplayer universal-physics game cannot produce
challenging scenarios for players. The challenge of a MMORPG is
not the game; The
challenge comes from dealing with the other players in the game.
Single-player
vs. multi-player adventure games
What happens when a single-player adventure game is
turned into a multiplayer adventure game?
The problems that appear when a single-player universal-physics game is turned
into a multiplayer game might also appear; The game's challenge might simply
disappear, and fun along with it.
Two
basic types of challenges present themselves in exceptional-physics worlds:
A
multiplayer exceptional-physics game must do the following:
20 August 2005
by Mike Rozak
Why
people play MMORPGs
Yes,
it's back to that old question... Why do people play MMORPGs? Or, more
precisely, why do they play a massively multiplayer game when they could be
playing the single-player version for (typically) less cost and (typically) a
better game.
Awhile
ago I would have said the reasons for playing MMORPGs vs. their single player
equivalents (usually CRPGs) is the other players, and it all comes down to
socialisation. However, it's not that simple. Let me list the reasons why (I
think) a player might wish to play a MMORPG (instead of a CRPG). The list isn't
exhaustive; Feel free to add/remove items as you wish.
Meeting
new people
MMORPGs
do a good job of catering to most of the above-listed reasons that people play.
For example: MMORPGs go out of their way to encourage players to join guilds by
providing built-in guild features such as: Guild E-mail, guild chat, guild
management, guild web-pages, raid content designed for guilds, and clearly
displaying which guild players belong to.
MMORPGs do not do a good job of helping people make new
friends. Sure, MMORPGs encourage players to form
parties and explore dungeons together. However, those dungeon crawls are 99%
action with very little time for players to sit down and chat. MMORPGs are also
missing some key features for meeting people, many of which were common in text
MUDs.
The
following features allow players to get an impression about other players from
a distance:
Other
features are useful after a conversation has begun.
Niche
communities
MMORPGs
don't provide many features for meeting new people because most MMORPG players don't want to meet
new people. They want to play with people, but not actually
meet them. They play with their friends, and if they group with strangers, all
they care about is what class the stranger's character is. Half the time I've
been invited to join a group, the party leader hasn't even chatted to me first;
they just click "Invite into group" without a word.
In
my mind, MMORPGs are large virtual worlds with tens of thousands to millions of
players. Text MUDs are small social worlds with hundreds to thousands of
players. Wandering up to someone in a MMORPG and striking up a conversation is
like wandering up to someone in an airport and introducing yourself. The
chances are, you won't have anything in common with the other person other than
you're both flying to New York City. MMORPGs attract a wide variety of players,
most of whom will have different interests than you... other than the fact they
like to play MMORPGs. They probably won't like the same music, be the same age,
have the same life philosophies, or have the same goals in life.
Most people don't try to meet new friends through random
encounters in airports, and they don't try to meet new friends in MMORPGs.
To meet friends in real life, people go to parties (organized by their own
friends) or join community groups (like hiking or religious groups). The same
applies to virtual worlds.
People who wish to meet other people will visit virtual
worlds that target niche communities. At the
moment, text-MUDs provide a better way of finding a niche community than
MMORPGs. For example: You can find text-MUDs dedicated to:
A
niche community is one with a very small market, around 1% of the population or less.
I suspect almost everyone fits into at least one niche community. Some people
like hard core science fiction, others are into souped-up cars, while others
like to make pottery. The list of niche communities is enormous; you can get an
idea of how huge by browsing your local book store or magazine retailer.
Hundreds of categories exist, some of which translate into virtual worlds:
Each
of these groups attract certain personality types, and people with very
specific interests. If you're a rev-head and walking around an auto show, the
chances are pretty good that anyone you talk to will also be interested in
cars. Talking to someone
at random an auto show is much likely to result in a friendship, or at the very
least a good conversation, than meeting a stranger at the airport.
The same goes for a virtual world targeted at a niche group.
The
dating game
What
conclusions do I draw from all of these thoughts so far?
1.
There are people who want to meet new
friends... This is a bit of an understatement. Most of
the world's population fits into this category.
2.
Existing MMORPGs do not provide the
features and design needed to meet new people, and the
cannot due to their all-inclusive nature.
3.
Virtual worlds targeted at niche
communities are an excellent way to meet new friends.
Niche
virtual worlds that are intended for people to meet friends must be designed
differently than traditional MMORPGs:
1.
The purpose of the world is for people
to meet one another. Players who want to be the alpha
(fe)male can go to a traditional MMORPG that emphasises levels and PvP. The
same goes for players who wish to be part of a guild, who want unlimited
content, etc. Most players will play in both mass-market and niche virtual
worlds, just like many people watch mass-market television shows and still
subscribe to niche-market magazines.
2.
The world must provide the features
that I listed in "Meeting new people".
3.
The purpose of the world's content
is to attract people of a specific niche all into one place so that they can
meet one another.
1.
It must be considered fun and entertaining by members of the
niche.
2.
It must be considered un-fun and boring by people who don't
fit in the niche.
4.
Once in the world, the content should encourage and help
players to meet up inside and outside the game.
5.
Each niche community will be supported
by a handful of worlds, just like a few magazines exist for
every niche community. Worlds will go in and out of fashion, just like
everything else.
13 September 2005
by Mike Rozak
You
can simplify the structure of a MMORPG, adventure game, CRPG, as well as many
other computer games, into the following recipe:
1.
The player is presented with a menu of quests they
can undertake.
2.
The player choses one and completes whatever
actions are needed to complete
the quest.
3.
As a consequence of completing the quest, the world or player character changes.
4.
Repeat.
First, remove the completed quest from the list and perhaps append a few new
quests.
Of
course, I have stated the obvious here, but sometimes obvious statements
identify a pattern.
Menu
of quests
Notice
how MMORPGs, adventure games, CRPGs, and other first-person reality games offer a menu of quests, not
just one, and not thousands of choices. MMORPGs tend to have
10-20 quests available to a player at any time. Adventure games always have 3-5
puzzles available. CRPGs likewise provide a selection.
The fact that a menu is offered is significant.
Let me illustrate why menus of quests are available for an adventure game:
1.
Adventure games typically have 3-5 puzzles available in every region
of the game. Once those puzzles have been completed, a door is unlocked and the
player is then given access to the next region, again, with 3-5 puzzles. A
typical adventure might have 5-10 regions, producing 25-50 puzzles (aka:
quests) for the entire game.
2.
Adventure game designers have discovered that
if only one or two
puzzles are provided per region, problems arise:
1.
If a player gets stuck on one puzzle
they can't "put it down" and come
back to the puzzle later with a fresh mind. In a game with only one or two
puzzles per region, players inevitably get stuck, put the game down, and never
come back. With more puzzles in the queue, they're more likely to keep playing.
2.
When players solve a puzzle, open a gateway,
solve another puzzle, and then open yet another gateway, they feel like they're been guided
through the experience and have no choice. They rebel, and stop
playing.
3.
One would think that if 3-5 puzzles per
region are good, more are better. Adventure
games don't provide a large menu of puzzles (quests) though:
1.
A well-known rule of user interface design it
to limit the number of choices presented to a user. Too many choices overwhelm the user.
The same goes for games.
2.
An adventure game is composed of 25-50
puzzles. If the game designer places 15 puzzles in each region, then the
adventure game consists of only two or three regions. Since the introduction of new regions
is used reward the adventure-game player with new scenery, and to indicate
progress, fewer regions results in a less-fun game.
3.
If an adventure game were to present
all its puzzles in one region, the resulting experience transforms into the
freely-available experience from game websites like Yahoo
Games. The player is merely presented with a buffet of choices. (See Stop the
buffet.)
The
same rules and reasoning applies to MMORPGs and CRPGs. If too few quests (or
activities) are available the player feels railroaded. Too many quests (and
activities) is overwhelming and less fun.
Undertaking
and completing the quests
I
have written about quest design elsewhere, and won't go into detail here. See The toy room
as well as many other articles on Deeply random
thoughts.
Consequences
Once
the player completes a quest, the world or PC changes. Three types of change
can occur:
Tied
up with the consequences is the ability
for the player to chose what form the consequences will take.
For example:
The types of consequences vary depending upon the game
genre:
Several
types of choices
Notice
that adventure games, MMORPGs, and CRPGs all provide the following choices:
1.
Players have a choice of which quest they wish to
tackle at the moment. The quests to chose from are limited.
2.
Players have a choice in how the quest will be
completed/solved. Each quest can only be solved in only a
limited number of ways though, because programming in a large number of
alternate solutions is expensive. See Problem solving.
3.
Players have a choice (often implicit) in how the
completed quest will affect their characters, the world, or other players in
the world. The number of outcomes is also limited, and related
to the quest; killing the evil overlord won't result in a cure for in-game
cancer.
A game which offers too few or too many choices (in any
category) isn't as fun.
Categorising
some popular genres
Verbiage
I
just described a system where players are provided a menu of quests, chose one
to work on and solve using one of a variety of approaches, and then upon
completion of the quest, change the world.
I
can restate the cycle in much clearer terms:
1.
The player is provided a menu of goals
(or sub-goals) they could undertake. If
completed each goal results in changes to the player's character, other player
characters, or the world.
2.
To complete the goal, the player must determine a solution
(out of several possible) and act on it. Adventure games tend
to require more effort in coming up with the solution than acting on the
solution. CRPGs don't require much time to problem solve, but more time to act.
See problem solving.
3.
Once the goal is achieved, the goal's
effects are applied to the PCs and world. See We don't
always get what we want.
4.
Repeat.
Ideally, completing one goal will lead to other goals so that the player is
never without goals.
Consequences
of stating the obvious
So,
after stating the obvious, have I learned anything?
1.
Both WoW
and EQII are pushing
the upper bounds for how many quests they offer a player at any point in time.
Hard-core players like the freedom of 20+ quests in their journal, but more
casual players will be overwhelmed by the choices.
2.
Both WoW and EQII provide
players with a choice of reward items when they complete a quest. WoW displays
the reward items before players undertake the quest, while EQII only displays
the choice after
the quest has been completed.
If
the ability for players to chose the outcome of their actions or to chose their
actions based on expected outcomes is important (which I claim), then showing the rewards ahead of time is a
better game-mechanic than keeping the outcomes hidden.
3.
A skill-based system allows players to
control the outcome of a quest (good); pure
level-based systems do not (bad). However, providing players too many skills to
choose from overwhelms them. (Most MMORPGs have a hybrid level system combined
with skills.)
4.
A typical MMORPG doesn't provide
enough different ways for players to complete a quest.
Players can either attack the monster with a sword, or attack it with an axe;
there's no way to trick the monster, etc. Nothing new here.
5.
A typical MMORPG doesn't allow players
to change the world! The ability to change the world is
vitally important. If a player cannot change the world, and they cannot engage
in PvP (and change other players), players spend all their time changing the
only thing they can change, their character's level and equipment.
A
way for players to change the world is desperately needed. See Fractured
realities.
13 September 2005
by Mike Rozak
I've
mentioned fractured
reality before, but I thought I'd put all my current thoughts
in one place...
The
problem with a monolithic reality
In the real world, we assume (rightly or wrongly) that
there is one reality that we all live in.
If our individual perceptions of reality are different, it's because we are
misperceiving reality, not that reality is different for each of us.
A monolithic reality doesn't work well for virtual worlds
(in my opinion). The problem was noticed from the very beginning of text MUDs,
since in a monolithic reality, if one player changes the world, it remains
changed for all players. This means that if a player kills the the evil
overlord, then for all players thereafter the evil overlord is dead, which is a
bit of a bummer for all the other players who wanted their chance at defeating
the evil overlord.
Some
workable solutions exist:
Fractures
in reality
Some
solutions exist that "fracture" reality, creating a different reality
for every player.
Why
fractures are important
The
reason why fractures are important is that players want to be able to change the world.
Even if they don't control exactly how the world changes, they still want their
actions to have an effect. (See Choice
and consequences.)
In all of the monolithic realities, except the
content-free one, players have no real effect on the world.
The next time they log on, the world is exactly the same as before. Players can
and do turn a blind eye to this, but ultimately the inability to change the
world diminishes the experience.
I
am not a big of content-free worlds either. Players can change the world in the content-free
monolithic-reality, but only in small ways. After all, if there
are 1000 players in the world, their ability to change the world must be
(approximately) 1/1000th of what can be changed. Furthermore, with so many
"cooks in the kitchen", the world tends to be a chaotic place.
The fracturing techniques I described make it easier for
players to "suspend their disbelief" and pretend their actions impact
the world. Unfortunately, each technique has its
drawbacks, some of which are psychologically disturbing.
13 September 2005
by Mike Rozak
In
Choice
and consequences I claimed that quests and goals were
just about the same thing, and that it might be better to think of handing out
quests to players as handing out goals. Following this idea reveals an
important issue: You
can't give a player a goal; You can tell them about a goal, but they must decide
that a goal is important (to them) before they really accept it.
Thus,
a game needs to package the goal so that the player takes up the goal. How can
a game produce such packaging?
Standard
ways of creating sympathetic goals
When
a player is approached by a NPC and asked to collect six what-cha-ma-call-its
in wherever land, why
should the player actually care?
A
run-of-the-mill adventure game, CRPG, or MMORPG, when it's boiled down,
provides the following motivations for completing the game:
More
ingenious ways of creating sympathetic goals
Now
that I've written down the most common techniques for creating sympathetic
goals, it's easy to see how inane quest design is in most games.
Here
are some better solutions I've seen or heard of. Notice how they involve the
player internalising the goal handed out by the game:
Goals
already existing within the player
Some
goals are already held by the player, and only need to be reinforced by the
game:
Conclusion
You
may have noticed that most of the techniques are also used by storytellers to
keep readers interested. You might say that I'm "adding story to my
game" using these techniques, but I wouldn't. The term "story" (as well as
the term "game") is so overloaded with meaning that I prefer to avoid
the words altogether. What I described herein is a component of
a story, getting the player/reader to internalise the goals of the
game's/story's characters.
Note:
Some of the ideas I've listed in here were inspired by fiction writers who
authored their own game-design books: Lee Sheldon's "Character
development and storytelling for games", and David Freeman's "Creating
emotion in games". I wouldn't add either book to my must-read list,
but they may provide some more ideas.
Having
said that...
...
I had to sneak genre in
somehow.
2 October 2005
by Mike Rozak
Still
on the Of
mice and elephants topic, I've been thinking about
keeping costs low as possible. Here are some cost-cutting measures that I've
seen implemented in various games...
The
world can be designed to reduce costs:
1.
The action that the player wants to
take is often not listed. This happens because (a) the author
can't think of every possibility, and (b) even if the author did, it's too
expensive to write all the branches.
2.
Branching narration inevitably
requires a menu of choices. If players have a menu, they don't
have to problem solve (as much) since they're left with a multiple-choice
question.
Costs
can be reduced using players:
Some
less-appreciated cost reducers (aka: players will whinge):
Thinking
outside the game:
My
suspicion is that games
that spread their cost cutting amongst all the techniques will do better than
games that just rely on one or two cost-cutting techniques.
2 October 2005
by Mike Rozak
In
previous articles, I mentioned that sub-games should vary slightly each time
they're used. I thought I'd explain the idea a bit more using the combat sub-game from
the Dungeons &
Dragons pen-and-paper RPG as an example.
How
does D&D vary combat? Take, for example, an encounter with orcs:
Meanwhile,
the player characters are gaining new powers:
Basically,
D&D starts off 1st level characters with a very simple combat sub-game, and
gradually introduces more choices, strategy, and complications into the
sub-game as characters advance levels. Since new players begin as 1st level
characters and gradually advance, the
combat sub-game's complexity rises in line with the player's mastery of the
rules. This design ensures that new players aren't overwhelmed
with choice and complexity, and that experienced players aren't bored.
2 October 2005
by Mike Rozak
Imagine that you have an idea for a new invention and
that you want to get people's feedback about the new invention.
What will happen?
To
make the abstract case more concrete, imagine that it's the 1940s and you
figured out how to build a microwave
oven. You haven't built one yet, since the R&D is pretty
expensive. However, you get together a focus group of people and ask them if
they think a microwave oven would be a good idea. In order to get the best
responses, you select people that use conventional
ovens for your focus group.
What
kind of feedback will the give?
You
can spend years trying
to convince members of the focus group (or the general population) that a microwave
oven can be built, and that uses will exist for it. What you'll discover is:
You
eventually give up trying to convince people that microwaves will work, and
build a prototype yourself. You present the prototype to your focus group, only
to discover:
Does
this sound too cynical?
2 October 2005
by Mike Rozak
I've
been trying to figure out what adventure games and MMORPGs "are all
about". When I originally broached the question to myself, I thought the
two were fairly different beasts, and that first-person shooters were from a
different planet. I'm not so sure any more...
"Avatar"
games
Adventure
games, computer role-playing games, MMORPGs, first person shooters, and modern
platformers all fit into a category of game that I'm calling an "avatar game".
(Anyone have a better name?) The characteristics of the game category are:
1.
The game's physics are very similar to
reality; Apples fall to the ground, objects break
when they're hit by a large force, and characters look like people or animals.
The advantage of basing gameplay on reality is that players already know what
reality is like, so they automatically the basic rules of the game; all the
game has to teach the player is how to translate their intent into keystrokes
and joystick movements, and how the game world's physics differ from real
physics. Additionally, when a game world is closely linked to reality, it's
easier to escape into.
Conversely,
chess, go, card games, and tic-tac-toe have a physics that's very different to
reality. Racing games, flight simulators, and sports games are in the grey
middle, since they're based on reality, but only on a very limited slice of
reality.
2.
The player controls a single character
that is an extension of the player within the world, although
occasionally games stretch this to a few characters.
Real-time
strategy games are different because the player controls an army. A pet-raising
game isn't an avatar game because the player doesn't control his character
directly.
Sub-games
Within
the world (and associated physics), players uses their characters to
participate in sub-games.
The most common sub-game is combat, but sub-games also include jumping over
obstacles, climbing, solving puzzles, talking to NPCs, etc. See Virtual
world as platform.
Some
important characteristics about sub-games are:
As
I noted in Choice,
sub-games can be strung together to form quests...
Quests
A quest includes a goal, problem solving, and a series of
sub-games that a player must complete to achieve the goal.
The goal is tightly tied into the quest, and usually handed out by the game
world, although sometimes games (like MMORPGs) let the player effectively create their own
quests, deciding their own goals and determining the actions
(sub-games) that must be completed to achieve their goals. A well designed
quest leads to follow-on goals.
An
example of a quest would be:
Quests are used to keep sub-games "fun"
since collecting a bag of cherries in order to help a kind old lady is more
"fun" than collecting cherries for the hell of it. Quests also tie consequences into the sub-games.
See Choice
and consequences and Sympathetic
goals.
Quests can be used as sub-quests
in larger quests.
As
I point out in The
four pillars, the sub-games listed for a quest represent
optimum solutions, and players should (theoretically) be able to approach the
problem however they like, including growing their own cherry trees and waiting
a few years for the first cherries to appear.
Meaningful
choices
The experience must be overflowing with meaningful choices.
(See Choice
and Choice
2.) These include:
Story
I hate to use the term "story"
because it's so loaded with meaning, but some elements of story come into play:
How
games differ
As
I said at the beginning, a large variety of games are avatar games. They
merely differ in their emphasis:
Having
said that, some virtual worlds, like Second Life, can only be fit into
this grand-unified theory with much contortion. Racing games, flight
simulators, and sporting games are in a grey zone too; their slice of reality
is so limited that quests are difficult to invent.
2 October 2005
by Mike Rozak
The
writeup for my Grand
unified theory begins at the smallest components of avatar
games, the sub-games available, and works its way up into quests and goals.
While refining the writeup, I considered whether it was possible to start with
a large view of the world and work down, and whether the same results occurred.
Working
from goals on down
Imagine
that you create a world...
Why
do players visit this world? What do they do? (As Chris Crawford likes to ask.)
I
have played plenty of games with the same "what". How about
"why?"
The
"Why" question leads to goals... Players kill monsters "to
save the princess", "so they feel like they're accomplishing
something", or "just to kill time." You can read
more about goals in Sympathetic
Goals. Following this idea, a world must either allow players to
fulfil goals that the players bring into the game, or provide the players with
goals (that are internalised by the players) once they enter.
It
would be easy for me to write an Eliza-like problem that asks players what goal
they would like to fulfil and then display, "Poof! You fulfilled your
goal." Technically, my "world" lets players fulfil their
goals. However, there are some technicalities:
Thus,
the world needs to include a set
of physics that allows players to complete their goal(s). There
are some caveats about the physics too:
Etcetera...
Basically, the actions
(mostly) correspond to sub-games. I already discussed some of
the requirements of sub-games in My current grand
unified theory of avatar games.
Mix
all the ingredients together, and behold: The same "grand unified
theory" reappears... Except, approaching from this direction reveals that players don't have to be lead through
sub-games by the nose, as my other GUT
paper described.
Q.
E. D.
The
four pillars
Now
that I have clarified that point, keep "goals" and
"sub-games" in mind, while I discuss a marketing topic.
At
the moment, there are two commonly accepted "rules" for making a
successful MMORPG:
You
can create a world with
great eye candy and lots of socialisation; it's called a graphical chat room.
Graphical chat rooms don't do that well financially.
I
think that two other
"rules" for a successful MMORPG exist. I've
read/heard books/people discussing these other rules only in vague terms, while
"eye candy" and "socialisation" are clearly articulated,
perhaps because the other rules are understood on an intuitive level. Despite
being unspoken, successful MMORPGs clearly follow these two rules:
2 October 2005
by Mike Rozak
When
I consider my grand
unified theory, a visual metaphor comes to my mind...
1.
In Choice
I gave each type of
sub-game a single-letter moniker, like "K" for
combat, or "N" for narration. DNA is likewise notated using
single-litter terms for bases, GATC,
which are graphically represented as different
colours on the double helix.
2.
At its simplest, a quest is a series of
sub-games, just as DNA is a series of bases. However, choices exist within the
quest, so (unlike DNA) there can be branches
within the sequence. For example: NcNSKNGN (CX|F) KN
allows players a choice between "CX" and "F". Keep a vision
of a DNA strand with reconnecting branches (loops) in your mind.
3.
A quest can be a sub-quest of a larger
quest. Again, just as like genes (sequences of DNA
that produce a specific protein) are combined in series into chromosomes,
quests can be combined in series. Zoom your imagination out of the small strand
of branching/looping DNA to see a larger picture with a long strand of quest
DNA.
4.
Players also have a choice of quests.
Quest arcs are tied to one another at the beginning and end. Again, zooming out
reveals more even more structure to the quest DNA, producing a tangled mass.
5.
Players' decisions can affect other
quests; if the old woman is fed to the troll, she
can't bake the cherry pie, and the player won't be able to meet the mayor in
quite the same way. You could visualise this by producing more tangles, but
there's an easier way: Every
time the player makes a choice, the quest DNA rearranges itself,
detaching and reattaching portions.
6.
Zooming out completely reveals the big
picture. Strands of branching/looping quest-DNA are
entangled together. Pieces occasionally detach and reattach as players achieve
goals that let them reorder the world. And, most importantly, from this angle,
you realise that the bases
(sub-games) all have subtle variations in their coloration,
since the sub-games vary slightly according to the quest. Fighting a troll is
different than fighting an orc because trolls and orcs use different combat
tactics, fight in different sized groups, and have different special abilities
and weaknesses.
7.
If the exact same colour (sub-game with no variation)
is re-used, players will
notice and not enjoy the experience as much. If too many of the
same colour (sub-game) occur in sequence, or too close together in the strand,
players will notice. If there's a pattern to the colours (blue always following
red), players will notice. If there's a pattern to the branching, players will
notice. If there's a pattern to the way that the quest DNA rearranges itself,
players will notice.
I
suspect that any
identifiable pattern will weaken the experience. However, pure chaos won't work because that
breaks characteristic #1 of avatar games, that the world and
physics resemble reality. Some sort of balance must be reached between pattern
and chaos... which harks back to Raph Koster's Theory of Fun.
16 October 2005
by Mike Rozak
I
ranted about the need for choice in several articles, such as Choice
and consequences. Unfortunately, choices all come to naught when
players reach the end of the game, where they have to kill the
evil overlord whether they like it or not. All the choices they were given,
such as whether to play a good or evil character, what clothes to wear, etc.
are completely ignored by the ending. (If your world has no end, such as a
typical MMORPG, then this issue isn't much of a problem, although you have a
different set of problems to deal with.)
Some
games try to solve this problem by allowing multiple endings; In one ending, the
player slays the evil overlord, while in another, the evil overlord slays the
player's character... I'm being a bit cynical. Some games allow for completely
different endings, such a letting the player partner with the evil overlord.
Unfortunately,
since designers don't want players to miss too much expensively-produced
content, designers inevitably place the branches
to the different endings at the very end of the game, usually
in the last five minutes of play. This enables players to reload previous saves
and see all the endings, thereby making the investment in content worthwhile.
Designers
could place the branches
earlier in the game, but then they'd need to produce more content
to cover the different (and lengthy) branches. Since most players don't replay
their games, they wouldn't experience the extra content, making it a waste of
resources. Additionally, since we're talking branches here, once a player makes
the choice, its effects are final and irreversible. As I pointed out in Choice 2,
players will have entered a new pearl on the string/tree. While this makes for
a very strong choice, it will annoy many players when they realise they're
following the "wrong path" and can't undo it. (The "wrong
path" is in the eye of the beholder.)
The
feather
Here's
an alternative way to view a typical game's choices:
1.
When the game first begins, choices are intentionally limited
so that players aren't overwhelmed.
2.
Players are presented with the most choices during the middle of the
game.
3.
Since the end of the game is fixed (killing
the evil overlord), and usually punctuated by an expensive cut scene, the
player's choices are
paired down at the end.
If
you simultaneously stretch your imagination and squint very hard, you can imagine the game's choices look like a
feather, which is narrow at one end (where it attaches to the
bird), wide in the middle, and narrow at the tip. (For Monty Python fans, you
can also think of it as a brontosaurus.)
When
designers produce multiple
endings, they usually produce one main feather. At its tip are
splayed several other feathers whose roots all meet at the end of the first
feather. This configuration happens to look like a tree, with branches where
the feathers meet. If you really-really squint, the feathers look like the
pearls mentioned in Choice
2.
The
peacock
And
now for something completely different...
Rather
than producing a tree from the feathers, arrange them like a peacock's tail feathers. They all
start from one point and splay out in all directions.
Notice
that the feathers
overlap near the base, so that from a distance, you're not
really sure which feather you're seeing. The overlap is greater at the base
than the tips. Only at the tips is it easy to identify which feather is
creating the pattern. (By the way, peacock feathers really don't overlap that
much; Analogies only go so far.)
If
I explain the analogy in game terms:
In
technical terms:
Notice
that this is different from the string/tree of pearls that I discussed in Choice 2.
There are no 100% final decisions, although some decisions might be very
difficult to counteract.
Advantages
and disadvantages of the peacock
The
peacock works well because:
In a peacock arrangement, players don't really know what other
players' goals are. (The players might not even know their own
goals/ending themselves.) Interaction amongst players becomes more complicated:
Of
course, the peacock arrangement is not without problems:
Beyond
the peacock
20 October 2005
by Mike Rozak
Not
owning an X-Box, I have been waiting months for Fable: The Lost Chapters
to come out for the PC. I just got my hands on the game last week. This writeup
is an analysis
of Fable's design, not a
review. The article is about "why" Fable works,
and why it doesn't.
If
you are looking for review, don't read this, since the article not only gives
away the plot, it intentionally strips the "magic" away from the game
to reveal "the man behind the curtain."
Story-like
devices
The
most notable difference between Fable and other PC-based CRPGs is the
emphasis on "story". I don't like the term story because it's so overloaded
with meaning, so when I do discuss elements of story as they appear in Fable,
I'll try to use more specific terms, such as narration or foreshadowing.
Let
me begin at the beginning...
Act
1a of Fable, first thirty minutes, involves an introductory cut-scene, a short
tutorial, a few short quests that the boy-hero must perform, and a the finale
where the town is pillaged by bandits and the boy-hero is left to wander around
the smouldering ruins.
Act
1a uses a number of interesting techniques that I mentioned in Sympathetic
goals:
1.
The introductory cut-scene foreshadows
that the boy-hero will go on to do great things, thereby informing the player that he, as the
hero's controller, will also do great things.
2.
Act 1a emotionally ties the player
into the game, introducing the player to the boy's father,
travelling mother, sister, the town, and its other inhabitants. Almost all of
these characters (including the town) are presented in a idyllic light, just as
Tolkien portrays The Shire as a rural
utopia. Having grown up in their own family, players readily identify with a family,
particularly an idealised one in a Leave-it-to-Beaver style village.
This identification is important since it's later used to provide the player sympathetic goals that keep him
interested in the game.
3.
Act 1a serves as the first part of the game's tutorial.
4.
Act 1a culminates with a cut-scene of bandits
(their legs only, from a child's perspective) burning the village. The player
is then allowed to wander around his
burning rural utopia, meet his
dying father, and be whisked away by a mysterious mentor.
Most
games would just produce one large cut scene of the pillaging and cut "Feature
#43: Wander around burning village" for cost and schedule reasons.
However, allowing the player
to explore his
burning village increases the player's
sympathy for the hero, and the player's animosity towards the bandits.
Act
1b involves the player's life in the guild hall. It serves the following
purposes:
1.
It is the second part of the game's tutorial.
The
game includes several other small tutorials later, which is a nice touch. For
example: The sneaking tutorial is provided when the player has to sneak into
the bandit camp. I particularly liked the teacher who gives the player a quest
to find books for the teacher to read to his classroom. When the teacher is
given the books, he gives a short reading to the class, as well as the player,
thereby feeding the player backstory in the guise of a quest.
2.
The player is introduced to two mentors, the
mysterious hero that rescued him, and the guild-master. The cut scenes are
designed to imbue the
player with the feeling that the two mentors should be thanked
for rescuing him, looked up to, and trusted. When the two NPCs assign quests
the player, the player is more
likely to internalise the quests' goals from his respected
mentors than other NPCs. The mentor-apprentice relationship is further
emphasised when the guild-master's voice is used to provide occasional tips
throughout gameplay, like "Get your combat multiplier up."
3.
The player is introduced to a female
classmate who grows up along side him. A friendly rivalry exists with the
classmate. It first shows itself during a training duel where the classmate's
achiever-father shows up and scolds her for losing to the the player; this
makes the player feel guilty for winning, and further ties him emotionally to her.
She reappears later both as an opponent and ally. The classmate is used as a device to
emotionally involve the player in quests, and to provide a
non-lethal rival.
Throughout
the game, there is an overriding arc of "story" that is used to keep
the player personally involved with the quests:
1.
In Act 1a, the player gets much of his impetus to
play from "I just paid $50 for this game and I am going to play it,
damn it!" as well as "Look at the pretty eye candy."
2.
The player is sustained through Act 1b by the player's goal to
learn the game so the
player can take revenge on the bandits.
3.
About a third of the way through the game,
the player gets to kill
the bandits along with the notorious bandit leader, only to learn that the bandits weren't
responsible for the raid. In fact, the evil bandit leader had actually saved
his (character's) sister, although the leader still deserves
death.
4.
The player is allowed to wander goal-less for
awhile, but is soon informed that his
(character's) mother is alive and held by the real villain. The
player's goal then is to rescue
his (character's) mother.
5.
In the act of rescuing his mother, the player (not just the player's
character) is captured, imprisoned, humiliated, and tortured.
The capture and jailbreak scenes do an excellent job of making the player dislike the
villain.
Personally,
I discovered that when my escaped character finally got hold of his weapons, I wanted to run
through the entire castle and kill all the guards, even though I only needed to
kill a few to escape. Then, while running through the tunnels with my mother NPC
following, I was much more careful about not letter her get injured than I was
with other NPCs. (30 minutes previously, I had the grave digger NPC following
me and actually wanted him to get killed by the undead, which they managed to
do.)
6.
The final act leads to the death of the real
villain. By this point, the
player (not just the character) is seeking revenge for his (character's)
father, mother, sister, and village, as well as revenge for the humiliation/treatment
that the player was put through in jail.
7.
The PC edition adds a new chapter, where the
the player must kill the villain yet again. While this extends the game, it doesn't work well as an intrinsic
goal because the player has already killed the villain once,
already rescued his sister, and already had his mother killed by the villain.
A
large story-arc, that runs throughout the entire game, helps the player
sympathise with the goals of his character.
Sympathetic goals are also used for the quests.
NPCs asking for help are friendly and appreciative of the player. Their
requests are reasonable and can only be performed by a hero. They thank the
player when the quest is completed. This is in stark contrast to Everquest
II where many of the quest givers came off as being aloof, rude, and
requesting trivial (non-heroic) jobs to be done.
While
a MMORPG can't provide a
player with a family to be captured by a personal enemy, many
of the tricks used by Fable could help MMORPG players internalise their
character's goals.
Other
player goals
Players
also have the following goals that they bring into the game:
Sub-games
Fable
relies primarily on the following sub-games:
1.
Walking
- Players spend most of their time walking. Walking has two variations, running
and sneaking, that are sometimes required to complete the game; Fable included
several race quests where walking becomes the main sub-game, as well as times
where sneaking is required.
I
expected to have jumping and swimming sub-games to accompany walking, but was
annoyed and frustrated by their absence. I would liked to have seen other
variations in the walking sub-game, such as walking on ice (low friction), or
sneaking on noisy/quiet surfaces.
2.
Combat
- Fable's combat is divided into three types:
o Melee
- Close-ranged combat involves clicking the left mouse button to attack. The
right mouse button is a "flourish", which allows a character to do
more damage after several successful attacks. The middle button is used for
parries.
Personally,
I didn't use the parry button often because (a) my character was so much
stronger than his enemies that I didn't need to, and (b) enemies usually
attacked in hoards that came on so quickly that I only had time to furiously
click the left button with occasional swipes at the right. Melee combat, while
fast and furious, didn't give me the sense of being in control.
o Archery
- Bows are fired by pressing and holding the left mouse button. Aiming, which
is required, is aided by a sighting view activated by the right mouse button.
I
liked the archery sub-game best because of the control, but used it the least
because it was only practically useful for one or two shots before the hoard of
enemies would set upon my character, requiring melee or magic.
o Magic
- Of course, Fable includes lightning bolts, fireballs, and healing spells.
Fable also has some more innovative spells like one that slows time for
enemies. As with most CRPGs, spells are mana based, which has design downsides.
See Sub-games with variations.
Magic's
fun-level placed it between melee (boring) and archery (fun). Again, I suspect
control was a factor in "fun", since I had several relevant spell
choices at any one time, which is more choice/control than combat, but less
than archery.
While it's possible for characters to
specialise, combat design seems to encourage an initial shot or two with a bow
(or long-range spell), followed by furious left/right melee clicking and
occasional spells such as time slowdown or healing. (Throw in an occasional
NPC-follower to rescue.) Since every combat experience requires at least some
melee, archery, and magic, combat remains interesting throughout the game,
which is more than I can say for MMORPG combat.
Unlike most CRPGs, combat did not evolve
significantly as the character became more powerful. Fable uses a skill-based
system with no skill tree, allowing characters to acquire all the spells and
feats early on. As a character becomes more powerful, the spells/feats do too.
However, from the player's perspective, they're given a lot of new toys (spells
and feats) near the beginning of the game, and then nothing for the remainder;
it's like having your birthday on the same day as Christmas.
However, Fable used extensive variation
in monsters and settings. Some variations that worked particularly well were:
o Archery
competitions - Combat against a moving dummy.
o Rock-hurling
burrowing troll - As the character leads a NPC out of the
wilderness, a cut scene shows a troll erupt from the ground and hurl a stone at
the PC. When the PC ducks for cover, the troll burries itself again until the
PC exposes himself. This means the PC must aim an arrow at where the troll is
buried, wait for it to come out, shoot the arrow, and duck before the troll
hurls its next boulder.
o Fighting
the bandit boss - Two interesting tricks are used here.
First, the arena is ringed by bandits with swords. They merely stand and cheer
the bandit leader until the PC is pushed too close to them, and then they swing
their swords at the PC doing damage. When the player manages to get away from
the ring's edge, the bandits stop swinging. Second, the bandit boss can't be
hit until he manages to get both swords stuck in the ground, which he does
every 10 seconds or so.
o Arena
combat - Wave upon wave of monsters appear in an
arena as a crowd cheers. The arena also includes damaging machines that are
good to push monsters into.
o Invincible
monsters - Some monsters, such as the fairies/nymphs,
can only be killed when they're in fairy form, not when they're a ball of
light.
o Timed
fights - Some combats must be completed in a fixed
amount of time.
3.
Leading NPCs around
- Players spend a lot of time leading NPCs from one place to another. Fable's
implimention is more fun than other CRPGs and MMORPGs that I've played because:
o NPCs
have a habit of walking into the middle of enemies and need rescuing.
In a few cases, the enemies intentionally ignored my PC and went after the
NPCs.
o NPCs
make comments about the player's ability to lead, and
produce emotes that show how frightened they are. It's amazing how much the comments add to the experience.
o There
are several variations
to the sub-game: Sometimes the NPCs will help fight. Sometimes
there are several NPCs to lead. Sometimes the NPCs do the leading. Some NPCs
must be found first.
4.
Experience point management
- Since Fable is skill-based, players need to decide how many experience points
to put into a skill.
5.
Inventory management
- Simply put, there is no inventory management. Fable allows characters to
carry as much as they want, so players never have to decide what they wish to
keep or throw away.
6.
Buying and selling equipment
- This is a non-event too. Most treasure from monsters and chests is coin or
health/healing potions. Plus, half way through the game, the character has so
much money lying around that the player doesn't need to sell loot.
7.
Searching
- Items that can be searched or interacted with turn blue. As is the norm with
CRPGs, searching wasn't very impressive.
8.
Digging
- Digging with a shovel is a version of searching where hidden items aren't
highlighted in blue. It's used sparingly to good effect.
9.
Stealing
- Stealing uses the same mechanism as searching, except that players don't want
their character to be seen by any NPCs. An icon informs players how many NPCs
are looking though.
10.
Puzzles
- As with most CRPGs, Fable didn't have many puzzles. Those that it does have
include readily-available in-game hints.
Fable
also includes some other
sub-games that don't seem to "fit" because they don't have anything
to do with the goals I had internalised, namely revenge and
finding/rescuing my character's family. These sub-games would work better in a
world-like MMORPG than a directed experience like Fable:
A quick comment about marriage:
From the beginning of the game, I
knew that the way I'd "win" would be by killing the
main villain. My knowledge/expectation of this completely emasculated many of
the sub-games, like marriage. If
the game were posed as one of social/political manoeuvring,
where dressing fancy and having a wife at your side was a viable way to defeat
the villain, then many of the sub-games would stop being mere diversions.
A quick comment about balance:
To be saleable to a non-CRPG player, Fable is designed to be easy, handing out
copious amounts of treasure and loot. However, as I pointed out in The four pillars,
if a player can win the
game using only the walking sub-game (without using the other sub-games) then
they will only every try the walking sub-game, and then later
complain the entire game was boring. The combat sub-game provided my character
with so much money and XP that I
didn't need the other sub-games to make money, like trading,
gambling, and real estate.
Choices
Fable's USP (unique selling point) is the ability for
players to choose their destiny, allowing them to play their characters as good
or evil.
Fable
provides the following choices to players:
Fable
is very weak
on other choices:
User
interface
The
UI is atrocious.
It is difficult to get around, doesn't have enough customizable tool-bar items,
doesn't use standard PC conventions (such as "i" for inventory), and
overloads the mouse buttons. (The right mouse button's meaning changes between
"running" and "target arrow".)
Several
reasons exist for the UI's failure:
15 November 2005
by Mike Rozak
Here
are some more thoughts on the strengths of a text/verbal world, as opposed to a
3D eye-candy centric world:
Some
other strengths keep reoccurring every time I reconsider the issue:
For
previous articles, see Of
mice and elephants and Small VW
operators vs. large, and Text vs. graphics.
15 November 2005
by Mike Rozak
If
you were to invent a world, how could you break out of the stereotypical fantasy mold?
Here's a brief survey of different aspects of worlds that might help.
Physics
When
you create the physics of your world, you have four choices:
"Some
magic" and "High magic" seem to attract the most players.
Real-world physics is probably too boring (for the players attracted to today's
games) and dreamlike worlds are just too weird.
Races
The standard fantasy (and science fiction) game is based
around humans. Sure, some of the humans have pointy
ears (elves), others are ugly and mean (orcs), and some a short (hobbits). In
the end, they're humans, just like Star Trek's Klingons (ugly and
mean) and Romulans (pointy ears) are human.
Living
in a homo-centric urbanised world, most people probably don't understand the
point I'm trying to make. Therefore, I'll explain some non-human alternatives.
(Most people still won't understand what I'm saying, but then again, you can't
explain colour to someone who has been blind their whole life. Unless you've
dealt with a number of different animal species, you can't quite understand how
different species react differently and how their personalities differ from
people's.)
If
I take one step away from humans, I come up with two new types of races:
Ultimately,
humanoid animals and androids feel and play like humans. They have a head, two arms,
two feet, and can talk. They are mass-market fare, and using them in a design
is a safe option.
If
you want to get more radical, you can include:
I
suspect that if you base your world on any of the above races you'll wind up with
a very small market; In the 1980's I bought "Bunnies and Burrows",
a pen-and-paper role-playing game based on Watership Down. One of my
friends got so frustrated with the experience of role playing a rabbit (no
hands, no intelligence) that he made his character go crazy and attack the rest
of the party.
If
you're really brave (and want practically no players), you could experiment
with even more un-human races:
Technology
Technology
is what the inhabitants of the world can create. You basically have the
following choices when it comes to technology:
Setting
The
most common locations in virtual worlds are:
Less
common, but still acceptable are:
Some
really bizarre ones are:
The
more bizarre the location, the less that players will be able to empathise or
even comprehend it, and the fewer players there'll be.
Cultures
The
races of the world can have different political systems:
Likewise,
you could include:
Most
worlds limit themselves to "nothing offensive" and have no defined
political system or culture. Basing a world on a theocracy might show players
what it's like to live in Iran, but they wouldn't play for very long.
World
events
What's
happening in the segment of the world that the players occupy?
What
players do and why they do it
What
do players do in the world?
And
why do they do it?
The
cliche...
A
standard MMORPG or CRPG is based on a world with:
Thus,
The
game with a thousand faces.
15 November 2005
by Mike Rozak
When I was a child, I spoke as child, I understood as a
child, I thought as a child; but when I became a man, I put away my childish
things.
1 Corinthians 13:11
When
I first began playing CRPGs and text adventure games in the early 1980's, the
games basically consisted of:
1.
A virtual space to wander around.
2.
A collection of unrelated puzzles or unrelated
of monsters
to kill.
Hunt the Wumpus, Zork, The
Temple of Apshi, and Ultima I all basically followed this model.
They had not rhyme or reason to their existence, but they were fun (at the
time).
As
I grew older, I wanted more from my games:
1.
Backstory
that explained why the puzzles and monsters were there.
2.
A variety
of sub-games.
4.
A huge
world.
5.
Eye candy.
Wizardry and Ultima II
provided these features, and I was satisfied for a time.
I
didn't play many games while I attended university, nor when I first started
working. When I next explored CRPG and adventure games, I discovered some
additional features had been added to the cannon:
1.
Quests.
2.
Cut scenes.
3.
Every game had a few new twists to
old features.
4.
The ability to play online with one's
friends.
Again,
I was satisfied for a time, but now I have played enough of those games. I want
some new dimensions added to my gaming experience:
2.
Sympathetic
goals; I want to feel that world is
important to me, not just important to my character.
3.
A small and focused world.
I don't want to spend 50 hours playing a game, let alone 500.
Games
with these new features are starting to appear; I am not yet satisfied.
A
curious question keeps creeping into my head... When I was a child I couldn't understand how I would see
the world an adult. As an "adult", I can't understand how I will
perceive the world in the future. Will I want even more
intricate worlds? Or, in other words: All of the above features are
"necessary" for a game, but are they "sufficient"?
Should
CRPGs, MMORPGs, and adventure games also involve:
1.
Real AI?
2.
A director/God?
3.
A theme?
4.
Something else that I can't even
imagine with my child-like mind?
or... What The Sims
Online could have been
15 November 2005
by Mike Rozak
When
you're lying in bed at night, the reading light on, novel in hand, and reach
the end of a chapter, what
makes you say to yourself, "I'll read just one more
chapter, and then I'll go to sleep"?
Likewise,
when playing a CRPG or MMORPG, what
makes you want to play for "just another ten
minutes so I can finish off this quest"?
Summary
of previous articles
As
I stated in Sympathetic
goals, players play CRPGs, adventure games, and
MMORPGs for a variety of reasons, most of which are ultimately exploited by the
game's design so that players keep playing. (Novels take the same approach,
doing whatever is necessary to keep readers from putting down the book.)The
most common techniques that games use are:
Another
reason for playing is less commonly utilised by MMORPGs: Players want to complete quests that
affect NPCs which the player (not just the player's character) likes or
dislikes. I discussed this idea in My current grand
unified theory of avatar games, as well as Sympathetic
goals. A few weeks after writing the articles, I
purchased Fable: The Lost Chapters, and discovered Fable practicing
some of the techniques I was theorising about.
Fable,
more than any game I've played, went out of its way to make the player (not
just the player's character) like a handful of NPCs, and dislike the villain.
The game did this by creating a family for the player's character, and
integrating the player (not just the character) into the family. The villain
then burns the character's village in front of the player's eyes, culminating
with the father's death just as the player finds him lying in the burning
village. This creation of sympathy/empathy
continues throughout the game, and is an important
technique for keeping the player playing. See my Analysis of
fable.
Translating
sympathetic goals to MMORPGs
In
GUT,
I used an example of an old woman asking for help as a way to produce a
sympathetic goal. While
this works to align the player's goals with the NPCs, it is limited
because:
1.
The old woman character can only be used for a few quests.
In a MMORPG, most NPCs hand out only one quest. A few NPCs hand out as many as
five quests before they're "used up". Either the NPC has no more
logical quests left to hand out, or the player's character becomes so powerful
that he no longer frequents the static location where the NPC stands.
2.
No matter what a player does to help the old
woman, she will always
be standing on the street corner soliciting help (from other players)
for her cherry quest. She is too static.
3.
In a problem unique to MMORPGs, other players will also have completed
the woman's quests, which is fine for picking berries, but
problematical for heroic deeds like saving the woman's life. For one, any NPC
whose life needs saving 250 times a day probably isn't worth rescuing. Second,
a player cannot fail
to save the woman's life because that would mean no-one else could undertake
the quest ever again. These limitations weaken the experience, and further
objectify the NPC.
Fable produces its sympathetic goals using
a home town, father, mother, sister, mentor, rival, bandit leader, and
arch-villain. All of
these characters, or their memories, re-occur within the game and produce ties
that keep the player completing "just one more quest".
The
character archetypes that Fable employs cannot be used in a MMORPG because:
1.
All of the above reasons.
2.
Players will find it very improbable
that each of their characters grew up in the same town,
all had fathers that died, and sisters that were kidnapped, etc.
3.
When players group together to help each
other with quests, which is one of the reasons why MMORPGs work, they will end up rescuing the same
sister or killing the same villain over and over.
So
how can Fable's use of sympathetic goals be accomplished in a MMORPG?
Personal
NPCs
Players
need to have personal
NPCs. Personal NPCs are NPCs that exist only when players log
on, and that are somehow tied to the player's character.
Personal
NPCs already exist in many MMORPGs; they're called pets. Some obvious
archetypes for personal NPCs are:
Some
less obvious "NPCs" follow: (Their utility will become obvious later
on.)
Each archetype should provide several different
"flavours" to choose from... Not all pets
should use Lassie's AI and storyline; some are rescuers, while others are
chicken killers. Providing
a number of flavours gives players choice.
In
a MMORPG, multiple flavours are especially important, ensuring that player A's villain is not the same as
player B's villain. Obviously, the two villains will be given different
randomly-generated names and appearances. That's not enough. They must also be
provided different personalities and methods of villainy.
Due
to real-world development costs, I suspect most archetypes will have around five flavours,
so there's a 20% chance that player A's villain will be awfully similar to
player B's villain. (Maybe both villains attended the same school of villainy.
:-) )
Quests
handed out by personal NPCs
In a contemporary MMORPG or CRPG,
if a player purchases a pet, the
pet is used as an extension to the player's combat or travel skills,
nothing more.
In
real life, if you purchase a pet you get:
Think of these extras as "quests"...
Personal NPCs are really quest givers.
They provide goals for players. Grunties (pets in the Hack//Sign anime
series) will get sick and need smiling cherries to heal them. Spouses will want
to go on a holiday. Children will need trips to be dropped off at school.
Henchmen will have personal problems of their own that need solving, with a
player's help, of course. Mentors will need supplies for their magical
experiments. Houses will have gutters that fall off. Etc.
Personal
NPCs are great ways to introduce quests because players have an ongoing relationship with their personal
NPCs. A skilled writer can use the ongoing relationship to either make the player like the NPCs,
or in the case of villains, rivals, and hoodlums, dislike the NPCs. These
relationships create sympathetic
goals:
1.
The player (not just the character)
wants to complete the quest because he likes/dislikes the
personal NPC.
2.
Even if the player doesn't have any emotional
attachment, they at
least want to keep friendly NPC around and eliminate enemy NPCs.
In order to keep the NPC alive/friendly, players need to complete the quest.
For example: If a player spends a lot of time levelling up and outfitting a
henchman, he doesn't want the henchman to leave in search of the henchman's
kidnapped daughter... who may have been kidnapped by the player's villain. The
player will volunteer to help the henchman more readily than some Joe off the
street with the same quest. Likewise, personal villains need to be eliminated
quickly or they'll just return later with new dastardly deeds.
Having
personal NPCs hand out quests also provides other benefits:
Some
implementation details
For
those of you who are technically minded:
Another
way to think about the implementation
You
can think of each NPC as coming loaded with a series of quests with choices.
The choices end up producing a branching
narrative (like a Choose Your Own Adventure book)
that's interspersed with sub-games (like combat or travel).
By
producing a system where several NPCs are "assigned" to a player's
character, you have just created a pre-emptive
multitasking system. Or, in literature terms, a threaded storyline.
Basically, players are following several branching narratives concurrently and
can chose which narrative to interact with at any given point. However, any
particular narrative is sparingly doled out over a long period of time,
ensuring that the player doesn't "overdose" on one personal NPC to
the neglect of others. Ensuring that multiple plots/NPCs are running at once
also allows them to affect one another, such as a childhood friend running off
with the player's spouse.
Conclusion
As
I stated earlier, personal
NPCs aren't entirely new; many aspects of what I describe have
been around for awhile:
22 December 2005
by Mike Rozak
It's
now time for me to wrap a number of ideas together...
Quests
Just
about every virtual world has quests,
and as I've posted many times before,
I feel that quests are critically important for the future development of
virtual worlds and computer games in general.
To
summarise what a quest is:
1.
Players are provided a bit of narration and
given a goal
to complete. Contemporary MMORPGs usually rely on either "Kill N
monsters of type X", "Acquire N items of type Y",
or "Deliver item Z to NPC A". Some even include, "Escort
NPC B to location C."
2.
The player runs off and completes the actions. A
recent (aka: in the last 10 years) feature is to actually show the player their progress,
displaying "Only 7 orcs left!" after an orc has been killed,
or "Only 3 rat tails left to collect!" after a rat tail has
been collected. This
automatic tally capability is important, as I'll discuss in a
bit.
3.
Once the player has completed all of his
tasks, he is either rewarded
immediately, or returns to the NPC to receive his reward.
Of
course, such a simplified description of a quest is like saying that a novel is
merely "about a character that gets into conflict with another
character." While the statement is true, it doesn't explain the
subtleties that actually make novels interesting, nor does this
oversimplification of quests do much to explain their potential.
Since
I've discussed the potential of quests many times elsewhere, I want to talk about the
"automatic tally capability".
Code
associated with quests
When
a player kills an orc for a quest, most
MMORPGs increment a counter in a structure (or database) that's
linked to the character's instance of the quest. The, "You have 4 orcs
left to kill!" message is displayed. When the counter reaches a
preset value, the quest is done, and some code executes that says, "You
finished your quest!"
In most MMORPGs, quests are NOT C++/programming objects.
They are database entries with associated "global" code for counting
quest-related actions. What
quests they were objects?
Imagine
that whenever a player
undertakes a quest, a new "quest" object is created.
That object attaches itself to the player character and "goes wherever the
player's character goes". It sits silently and watches what the player
character does. When an orc is killed, the object registers this fact,
increments a counter, and displays, "You have only 4 orcs left to
kill!"
Ultimately,
this is more work. What does it get you?
1.
All the code for a specific quest is nicely encapsulated.
2.
All "FedEx" quests are merely sub-classes of the
master FedEx class. All KillNMonsters are merely sub-classes of the master
KillNMonsters class.
3.
Something wonderful...
Turning quests into objects unifies many of the concepts I have discussed in
previous articles.
Object
+ Quest = Story arc
Since
I want to distinguish "Object + Quest" from just quests, I'll call
"Object + Quest" story
arcs.
A
story arc
easily enables the following ideas:
Some
random thoughts...
Story
arcs could easily perform the following tasks:
You
should allow players to
turn off their story arcs if they don't wish to be manipulated;
Theoretically, a story arc will make the experience more fun though, so players
will want to have story arcs enabled.
Some
deeply random thoughts...
1.
Players explore/experience a physical world
constructed from three dimensions, landscapes, dungeons, puzzles, traps, etc.
(This is adventure game and CRPG fare.)
2.
When players talk to NPCs, they explore a mental and social world,
manoeuvring their way through conversations, social ties, personality conflicts,
etc. You could imagine, for example, that taking part in a conversation or
trying to climb a social ladder is like wandering through a maze of twisty
passage, all alike. (Chris Crawford's AI is working in this direction. CRPGs
have NPCs that aren't
interacted with on a mental/social level.)
3.
Story arcs let players explore a relational world,
where choices and actions have long-term ramifications. Choosing to slay 10
orcs means that ogres will take over their abandoned stronghold later. Story
arcs can be used to explore consequences.
22 December 2005
by Mike Rozak
In
Intertwined
storylines, I talked about the ability for a virtual
worlds to cater to several different "storylines", the most obvious
being World of Warcraft's Hoard
versus Alliance, or good versus evil, etc. However, more
complex storylines are also possible. I will spend this topic discussing some
more storyline ideas.
But
first, I want to illustrate why storylines are important:
Stories,
single-player games, and virtual worlds
In
a linear story,
you can have: Bob,
a farmer
in 1930's Okalahoma,
experience the dust bowl.
Over the course of the book, Bob does X,
Y, and Z, with the aid of his loyal wife.
Basically,
in a linear story,
the author controls:
A
single-player game
can:
In
a virtual world,
designers can (or cannot):
Part
of the reason why I wanted to mention this transition from story to virtual
world is to point out that virtual world designers cannot control many of
the aspects of an experience that linear authors control, or even that
single-player game designers control. Not
being able to control the exact order of action, X, Y, and Z,
that a story author controls, means that coincidences
can't (easily) happen in a virtual world, a crutch that many
authors rely on.
If
a designer can't rely on
linearity to make the player's experience "fun and
enjoyable", he
needs other tools and devices. One of the devices that
virtual-world designers have that story authors and single-player game
designers don't have, is the ability to enmesh
other players in the player's experience. Of course,
this is an obvious statement, but it's worth pointing out.
Why
virtual worlds need many storylines
One
very important "crutch" that single-player
game designers rely upon is the ability to specify the player character's
storyline, which is basically the character's role, what the
character does, and why the character does it.
A
story or a single-player game can get away with only one storyline. If a
virtual world tries to rely upon one story line (by making all players 1930's
farmers, for example), then they run into the following problems:
1.
As a player, when you encounter another
player, you
automatically know that the other player is a farmer.
2.
If you know he is a farmer, and following the
same storyline, then you
know what the other player has already done and/or will do,
since they're experiencing the same basic set of events as you are.
3.
This makes you feel like you're on a ride at Disneyland, and have no real
control over your destiny.
4.
You have an incentive to always work with other players to accomplish
a task, since they have the same goal.
5.
A clever designer makes some sub-games/quests
zero-sum, so that you win at the expense of another player losing. Thus, some encounters with other players
result in conflict.
6.
Even with this, most encounters will be win-win or
zero-sum. There won't be any ambiguity.
The
most popular way to get around this problem is to:
1.
Define the storyline as the character's growth from a 90 pound
weakling to a super hero... Player characters start out as
pathetic level 1 newbies and gradually work their way to level 100. This is
called the "Hero's Journey".
2.
The world is divided into zones.
Zones have monsters placed in them so that they encourage level N characters to
visit the zone. Low-level characters won't enter a high level zone because
they'll be slaughtered right away. High-level characters won't enter a
low-level zone because they won't earn enough XP and loot for their time
investment.
3.
Of course, as characters kill monsters, they
get XP and loot,
which makes them more powerful, and eventually encourages them to leave their current zone to visit a
higher-level zone.
4.
If a world hands out XP and loot
slowly, players will be forced to visit every zone
and complete every quest in order to earn enough XP to advance to a
higher-levelled zone. Players will feel like (a) they spend all their time grinding, and (b) they
have no choice
in what zones/quests they visit because they have to visit them all.
5.
If a world hands out heaps of XP and
loot, players will never visit most zones. They
will feel like they have plenty of choice, and won't be grinding, but will be
annoyed that they didn't
experience all the content. At the same time, management will
complain that the designer wasted money producing content that players don't use.
6.
World of Warcraft
seems to have a happy
medium, allowing players to skip half the zones as they
advance. This allows players to chose what quests they will undertake, while
not wasting too much content.
The
"Hero's Journey" game storyline has been done to death! It works,
but it has become very cliche.
Down
with levels
Notice
the correlation between "levels", "storyline", and the
"Hero's Journey". Levels are also linked to other techniques; see Experience points.
Imagine,
for a second, that you got
rid of experience points and levels... I know, it's difficult
to imagine, and induces the frightening feeling of a loss of security. You still have a storyline,
but can't use levels
as the way to drive the player forward.
Instead,
a storyline is a series
of quests.
Because
players should have choices, players
can chose to take different branches within the storyline,
which lead to different series of quests. The choices are either explicitly made by the
player ("Do you want to open door A or door B?"), implicitly made (Does
a player's actions make him good or evil?), or are based on success/failure.
(Failing to rescue the princess leads down a different path than success.)
To
add another dimension to
choice, several
quests will be on the player's plate at once. Completing a quest sometimes spawns
two quests. Some quests are initiated based on a timer (or random
event). Others are purely optional
side-quests that player can chose to accept or ignore.
Contemporary
MMORPGs use zones with level-N monsters in it to prevent characters from taking
quests out of order, or bypassing all the quests and ending the game by killing
the evil overlord in the first ten minutes. A level-less storyline system prevents players from
skipping (too many) quests by not allowing players to undertake new quests
until they have completed prior quests; this may require that
certain areas of the world be inaccessible or hidden until the necessary quests
are completed.
A
level-less storyline has the advantage that friends can help each other out. In
MMORPGs, friends might have characters of vastly different levels, preventing
them from playing together.
Another
way to think about this is to view storylines like a Choose Your Own Adventure
book where each page not only involves a choice of A, B, or C, but also a quest
that the player needs to complete. The quest inevitably involves sub-games. See
The
game loop.
If
you already read Personal
NPCs, you'll notice that the scheme for storyline
quests is almost exactly the same as the scheme I proposed for personal NPCs. Bother personal NPCs and storylines
can be handled using the same mechanism,
and both should exist in a world.
An
example storyline
for a farmer during the 1930's might be:
1.
Personal NPCs
introduced for the character's siblings.
2.
Option to buy a pet. (Acquire a personal NPC).
3.
Buy a horse
or a Model T. (Acquire a personal NPC).
4.
Rent or buy a farm.
(Acquire a personal NPC. Buildings require maintenance, etc.)
5.
Find a wife.
(Acquire a personal NPC.)
6.
Have kids.
(Acquire a personal NPC.)
7.
All of the personal NPCs bring their own
quests. Meanwhile, the player deal with the trials of the early dust bowl, trying to
get crops to grow as well as possible. Have the player experience three to five
years, each year getting successively harder and brining new challenges.
8.
Decide to stay or leave. This produces a branch
that lets the character to scratch for a living from the dust, or travel to
California.
9.
The storyline
ends when the player survives the "boss" dust storm
or finds a new home in California.
Choice
of storyline
As
I stated earlier, a single-player game can rely on just one storyline. A virtual world must provide at least
a handful of storylines. Some other storylines might be: Be the
wife of a farmer, be a child of a farmer, be the banker, be a merchant in town,
be a hobo, be a thief, etc.
Some
issues arise:
1.
When a player creates a character, they may be given a choice of which storyline
to play. Contemporary MMORPGs do something similar by allowing players to
select their race and class.
2.
Some storylines may not be available
until the player has played through others. For
example: A player may not be able to play a banker until he as played a farmer.
3.
Some storylines are "earned",
as in The peacock.
Maybe the option of becoming a thief or hobo can only be "earned" by
the player's actions as a farmer.
4.
When a player finishes a storyline, they may wish to
continue playing the same character. At that point, the designer can (a)
forcefully terminate the character, (b) allow the player to continue with the
character but not do anything except side quests, or (c) transfer the character
over to another storyline.
5.
A player might get part way through the
farmer storyline and decide
he doesn't like being a farmer. The design might allow the
player to suddenly (and without any real in-game reason) switch to being a
grocery store owner. Or, the design could require that the player start a new
character.
6.
As with personal NPCs, each storyline might have several
variations. When a player decides to be a farmer, they won't
know if they're getting farmer storyline A, B, or C.
7.
Players may opt not with to be a part of any storyline,
in which case they can spend their time acquiring personal NPCs, playing optional
quests, or chatting.
Typical
MMORPGs allow players to
chose a class and race, which is sometimes used as a storyline
selection technique. In WoW, players that chose to be elves join the
alliance storyline, while undead are part of the horde storyline. (Linking race
to storyline is somewhat self defeating because everyone in WoW knows
that elves are part of the alliance storyline.) Unfortunately, most MMORPGs
don't associate class/race with storyline, probably because it requires more
content, which means more money.
A
designer can obscure
levels by replacing them with skills, which are basically a
vector of levels. A player that focuses on a specific skill also focuses on a
specific storyline; a player that invests in "accounting" will
probably follow the banker storyline, while one with "animal
handling" might follow the farmer storyline. Star Wars Galaxies
does/did this.
The
end
My
intuition and thinking says that virtual worlds should have an end, as
described in the anti-MMORPG.
This means that at some point players will be told, "You're done. You
might want to leave now."
Unfortunately,
storylines are problematical for the following reasons:
The
solution is to let players daisy-chain
storylines.
If players can daisy-chain storylines, then the overall
concept is somewhat weakened. Unfortunately, I
can't think of a way around the daisy-chaining. The designer of a single-player
game can conceivably say, "You're done. Get out of here."
However, this isn't possible/wise with a multiplayer game (as shown above).
In a multiplayer game, daisy-chaining produces a series
of mega-quests (aka: the storylines) that can only
be played one at a time.
The ending of the mega-quest (storyline) is a good time for the player to quit
the world, if that is what they wish. Players that quit immediately after a
storyline finishes will leave
on an up-note; they will have defeated the evil overlord, or
rescued the princess. This
positive ending will encourage players to recommend
the experience to other players.
To
use a food analogy,
storylines in a multiplayer world are like a link of sausages; players break off as
many sausages as they can eat, and leave the rest on the link. Occasional miscreants
take only half a sausage, but most people take a whole one. Theoretically,
players will take their favourite sausage flavours first (curry, chilli, or
rosemary and thyme), and won't bother trying flavours they like less. Most
people will eat one or two links in a meal, while some die-hards will go for
three or four.
Look
ma, no levels!
I haven't gone into detail about all the quests and
choices needed to make a farmer storyline, but they're fairly obvious;
just imagine you're a farmer in the dust bowl and imagine all the tasks you'd
have to do, as well as unfortunate events that might require a response. I can
think of oodles. For one: milking the cow. You could make a sub-game out of
milking the cow, just don't make the player milk the cow more than a few times
or they'll get annoyed with the grind. Milking-the-cow even has variations
in the sub-game; the cow may become more difficult to milk as the drought
worsens, or a player's only bucket may get damaged and be more easily tipped by
the cow.
You
can do it all without
levels, and without
monsters.
Some
additional points:
22 December 2005
by Mike Rozak
The
basic game loop
1.
Goal - Give the
player a goal,
preferably an internalised goal,
either using story-like
techniques, or based on a goal that players bring with them
to the game. Ideally, provide the player a choice of the goal, and/or the order to
achieve the goals.
2.
Problem solving
- Provide a problem
that the player needs to solve
to accomplish the goal. Preferably, provide a choice of solutions. Problem solving also
includes investigation.
3.
Action
- Provide a mechanism
for the player to act
on the world, to achieve the goal using the solution he devised. The mechanism
involves time commitment,
the player's
skill,
and the risk
of failure. Preferably, there are a many subtle choices allowed with every action.
4.
Reward
- When the player succeeds (or fails) in the action, provide a reward (or an occasional punishment)
based on the goal and the solution. Rewards/punishments usually involve the
fulfilment of the goal, although not exactly
as the player expects. Rewards/punishments sometimes affect future choices,
future goals, the range of solutions available for future problems, and/or the
actions used in future. They might simply be parts of a story or eye candy.
5.
Repeat,
not necessarily in this order.
The
game loop in multiplayer games
1.
Goal that somehow involves other
players
o Friends
§ Want
to hang out with friends.
§ Help
friends.
o Guild
members
§ Rising
in guild rank.
§ Help
guild.
o Neutrals
§ Meet
new friends (or enemies).
§ Recruit
new guild members.
§ Help
other players.
§ Get
information or a service from a neutral.
o Rivals
§ Beat
a rival at a contest.
o Enemies
§ Defeat
an enemy player (or guild).
§ Must
interact with other players to defeat an enemy player, or help a friend.
2.
Problem solving that somehow involves
other players
o Friends
§ Getting
friends in one place and/or convince to work towards a game/multiplayer goal.
§ Dividing
loot.
o Guild
members
§ Getting
guild members in one place and/or convince to work towards a game/multiplayer
goal.
§ Dividing
loot.
§ Organization.
o Neutrals
§ Find
a neutral willing to help with a game/multiplayer goal.
§ Determine
what the other player wants to improve bartering positions.
o Rivals
§ Determine
how to overcome rival in a contest.
o Enemies
§ Determine
how to overcome an enemy.
3.
Action that involves other players
o Standard
single-player mechanics applied to multiplayer games
§ Combat
§ Influencing
NPCs
§ Pickpocket
§ Etc.
o Multiplayer
mechanics
§ Chat.
§ Forums.
§ Bartering.
§ Trading.
§ Guild
management.
§ Contests.
4.
Rewards/punishments involving other
players
o Standard
single-player rewards/punishments, but gotten/given by other players
§ Loot,
experience points
§ Damage,
character death, loss of equipment
§ Knowledge
o Multiplayer-specific
rewards/punishments
§ Friendship
§ New
guild members
§ Join
a guild
§ Networking
§ Rivals
§ Enemies
§ Social
status
§ Political
power
Note: Mix and match between multiplayer
game-loop components and single-player game-loop components. This, a goal might
be single-player based, but require a multi-player solution, using
single-player actions, producing a multi-player reward. Mixing and matching is important, since it
provides more variety
in game play (exponentially so) than single-player alone or multiplayer alone.
22 December 2005
by Mike Rozak
In
The
attraction of impossibility I wrote about people being attracted
to virtual worlds (and games) for the ability to do something they can't in
real life. The
player pyramid discussed how virtual worlds tend to
emphasise social
goals/desires that people can't get from real life, while
single-player games can fulfil non-social
goals/desires (or whatever their AI is capable of.)
Richard Bartle's player model
in http://www.mud.co.uk/richard/hcds.htm
discusses similar ideas, being more minimalist in his categorisation, and
claiming that people play virtual worlds because they want to socialise
(socialiser), be socially dominant (killers), compete (achievers), and explore
the world (explorers). Explorers don't fit my "virtual worlds are socially
motivated" theory as well as the other three player types.
Nick Yee has a similar set
of "facets" that cover socialisers, killers, and achievers, but leave
out explorers.
Basically,
Richard Bartle's and Nick Yee's models both come down to the fundamental goals of the player:
why the player is playing, often at a subconscious level. My musings came to
the same conclusion.
However,
games and virtual world can provide other goals, sympathetic
goals. These are goals that the game gives to the
player, and which the player internalises into his own goals. For example:
"Kill the evil overlord" is a goal given by the game world,
and which (through various devices) Fable
manages to internalise into the player. (Random comment at the end of the
article.)
The various player models and sympathetic goals are flip
sides of the same coin. Player models describe what goals a
player brings into the game/world, while sympathetic goals describe the goals
that the players acquire once there.
I
suspect that certain players are more strongly attracted to worlds that allow
them to achieve their own goals, preferring to join world-like worlds.
Other players find it more fun to take one the world's goals, joining game-like worlds.
Revisiting
goals/dreams that players bring into the game/world
Since
I have recently spent a considerable amount of time looking at sympathetic
goals, I thought I'd revisit player models a bit, and fine-tune some of my
thoughts from The
attraction of impossibility.
To
attract players based on their personal goals, a game design needs to resonate with their goals.
Obviously, a world accurately based on the Roman Empire would attract players
who are interested in the accurate depiction of the Roman Empire... all 10,000
of them. A world that allows players to be a cowboy might attract wild-west
fans. Etc.
A niche-market world can choose a specific interest group
and tailor its experience to the group,
particularly if the group can't achieve its goals in real life. The specific
features are, of course, dependent upon the group. People who are interested in
realistic space travel might want a realistic simulator of travel to and then
explore mars (that would bore the pants off most people).
What does a game/world do if it wants to attract a larger
(mass-market) audience? Richard Bartle's
and Nick Yee's models provide some insight, although I find them too general.
Teenagers
seem to be more attracted to MMORPGs and CRPGs than adults. (Yes, most player
are not teenagers, but (a) most of the real-life population is over 19, and (b)
teenagers find MMORPGs and CRPGs more expensive than do adults. Despite these
hurdles, almost 50% of the MMORPG-playing population is under 20.)
What
"personal goals" are common to teenagers? Remembering back to those
awful years (definitely not the best years of my life), I came up with the
following list: (Feel free to make up your own.)
In
case you haven't read between the lines by now, MMORPGs and CRPGs fit snugly into a stereotypical
teenager's personal goals.
If
a mass-market game were designed for adults, the question arises: What "personal goals" are
common amongst adults? (Feel free to make up or modify the list
to suit your opinions.)
Of
course, these goals are drawn with very
broad strokes. Everyone is different. For example: I like
watching the international news, and I like science fiction and fantasy. From
this, I've concluded that my fundamental "dreams" are as follows:
Mass
market vs. niche
Notice
that my goals don't particularly match what I've written for the standard
"adult" goals. After all, everyone is different.
This difference affects a game's design: Teenagers
are attracted to a game with virtual housing because it allows them to be an adult.
Most/many adults
would like to own their own virtual
house so long as it's bigger/better than their current one. I'm not particularly attracted to
virtual property, because in real life, I don't want anything
larger/better than I already have.
Due to their production price tags, large worlds must
target "typical" teenager or adult "dreams",
like owning a big house or holidaying on a tropical island. Large Hollywood
productions do the same.
Small worlds (and small movies) fulfil more niche dreams.
I could take my personal goals and produce a world that fulfils them, but I
wouldn't get many players. However, if my costs were low enough, a few thousand
dedicated players might be all I needed.
Random
comment at the end of the article
One
way to create a sympathetic
goal that I didn't think of when writing sympathetic
goals is:
Storylines III
10 January 2006
by Mike Rozak
This
article includes continuing thoughts from Storylines II.
Of course, it's just one more thought experiment to add to the list; who knows
if the idea will actually work.
Linear
narrative, linear avatar games, and sandboxes
I
am continually refining my thoughts about the difference between a linear
narrative and game. For this article, I need to define them a bit more clearly
than I have in the past, in order to make a point:
Of
course, these three categories define a continuum,
from linear to free-form.
Each
category (narrative, linear, and sandbox) has strengths and weaknesses, which
I've written below. Green items are strengths, red are weaknesses, and
yellow are in between.
|
Linear
narrative |
Linear
avatar game |
Sandbox |
|
|
Players/readers
can read NPCs minds. |
Novels and movies
allow readers to "read" the characters' minds (especially in
books), as well as see what the enemy is doing off-stage. Mystery novels
(somewhat game-like) do not
tell the player what the other characters are thinking. |
Not only are
game-NPC's minds fairly uninteresting to read, but the ability ruins the game
aspects of the experience. |
Not only are
game-NPC's minds fairly uninteresting to read, but the ability ruins the game
aspects of the experience. |
|
NPCs
behave in a realistic and believable manner. |
Because authors can
pre-script NPC dialogue and behaviours, NPCs can be very realistic. |
AI is limited, so
NPCs aren't very believable. Linearity allows some pre-scripting, so NPCs can
be made to appear more intelligent than they really are. |
Since there's no
linearity, NPCs only have AI to rely upon, leaving them fairly dim. As a
result, sandbox worlds often avoid NPCs, or associate NPCs with specific
quests, which are more linear elements of the sandbox. |
|
Both
PCs and NPCs have a large variety of actions to choose from. (In Chris
Crawford's terminology, this is the number of "verbs" available.) |
Because everything
is pre-scripted, any action that can be described verbally or visually is
possible. |
Only those actions
which the developer programs in can happen. However, the linearity leads
players down a certain path, and implies what actions players might
"want" to take. Authors can (usually) predict the action and code
for it, making the verb list appear larger than it actually is. Failure to
predict a player's actions results in a "guess the verb" problem
common to text adventure games. |
Since players can
and will want to do anything and everything, their choice of actions are
ultimately limited. A typical sandbox game lets players walk/run, fight,
craft, etc. If a player wanted to arbitrarily glue three matches together to
form a triangle, they couldn't because the standard verbs wouldn't support
it. |
|
Eye
candy quality. |
Since everything in
known in advance, eye candy is maximised. The overall quality of text in a
novel is always superior to that of a text MUD, and the overall quality of
graphics/sound in an animated movie is superior to that in in games. |
Because of the
linearity, special animations (or text passages) can be pre-written. If these
special animations/text are long enough, they're called cut scenes. |
All animations and
text are stock, so they're (as a whole) not very good. |
|
Foreshadowing
and prescience. |
Linear narratives
frequently foreshadow what is to come in the story, using foreshadowing as a
hook to keep the reader interested. |
Linear narratives
can use some foreshadowing, but only about events that they know the player
will be forced to experience. |
A free-form
experience cannot foreshadow. |
|
Serendipity
(confluence of events). |
Most linear
narratives are designed so two characters just happen to meet at the right
time, happen to have a hairpin to pick the lock, etc. Serendipity allows
protagonists to get out of tight situations, as well as giving readers the
desired sense of "things happen for a reason." |
Serendipity can
happen at certain points in the experience; a player can open a door to find
an important scene just beginning. Of course, the scene's start was triggered
by the door being opened. |
The more a world is
like a sand-box, the less likely that a player will be at the right place at
the right time. Designers can "hack" in some serendipity by
triggering scenes when players approach, but then the experience becomes
slightly more linear. |
|
Jump
around time, as well as accelerating time through the boring bits. |
Stories frequently
jump forwards and backwards in time, as well as skipping over the boring bits
with a few words. |
Linear games can
jump around time, but the player will probably be very confused. Furthermore,
the time-jumps only serve to emphasise to the player the fact that they can't
really alter the game's outcome. Accelerating
time through the boring bits is a common practice. |
Jumping around time
isn't possible. Accelerating
time is possible in a sandbox, but I haven't seen it used. |
|
The
experience can have a plot. That is, events that don't ultimately affect the path
of the story (aka: the drive towards the ending) are ignored. |
In a story, every
narrated event serves to flesh out a character or to drive towards the
ending. If an event doesn't, then it is removed from the book/script. |
Because the overall
flow of the experience is linear, it can have a plot. However, smaller events
that are entirely controlled by the player may ultimately prove to be
meaningless to the ending. |
Plot? We don't need
no stinking plot. |
|
The
player can be a specific character, as opposed to a general archetype.
Providing a specific character makes it easier to
produce sympathetic
goals and personal
NPCs. |
Stories can be
about a specific protagonist. |
Linear games can
either specify the player's character ("You are Frodo Baggins".) or
provide a more open-ended character ("You are a hobbit from the
shire.") |
While a sandbox
could specify the player's character, doing so would push the experience
towards a linear game. |
|
Rewards
for completing goals. |
When a protagonist
completes a goal (since the player cannot), the author has an enormous
variety of rewards to offer. |
Authors can offer a
large variety of rewards, although not so varied as a story. Rewards not only
include gold, loot, etc., but also changes to the world as a consequence of
moving to a new pearl. |
Rewards are usually
limited to gold, loot, or character power. However,
in a multiplayer game, rewards for interaction are "handed out" by
other players, and can be more varied, including social rewards. |
|
The
player's character can be placed in a specific scenario that is designed to
be interesting. (Related to a "serendipity (confluence of events)",
except in broader strokes.) |
Frodo just happened
to be the nephew of Bilbo Baggins, who happened to find the one ring a few
years before Sauron was to attempt his return to power. |
Same as with
stories. |
Specific scenarios
are sometimes used, but don't work as well in a sandbox because there's no
guarantee that the specially-manufactured scenario will come to fruition. |
|
Problems are presented as interesting puzzles. |
Not possible,
except in mystery novels, which stretch linearity to the limit. |
Puzzles are fairly common,
especially in adventure games. |
Puzzles are
possible, but multiple-solution problems seem to work better. |
|
Players
can "do stuff", immersing the player.
One of the weaknesses of Choose
Your Own Adventure books is that
while they allow players to choose, they don't allow players to
"do". |
Stories do not
allow the player to do anything (other than flip pages). They try to make the
player feel as though they've done something by describing the protagonist's
actions. |
Players can do
stuff. |
Players can do
stuff. |
|
Players
can make small choices. |
Stories do not
allow choices. |
Players can choose
where to walk, what to buy, exactly how to attack a monster, etc. |
Players can choose
where to walk, what to buy, exactly how to attack a monster, etc. |
|
Players
can make major choices, such as whether to be good or evil. Allowing players
to make major choices both increases immersion, and customises the experience
to the player. |
Stories do not
allow choices. |
While players can
make major choices, allowing them to do so do requires that the designer
produce more branches in the larger linear story (turning it into a
computerised Choose Your Own Adventure book). |
Major choices are
very easy to implement, and flow naturally from the sandbox's logic. |
|
Customise
the experience to suit the user. |
Theoretically, a
novel/movie presented on a computer could be customised to the viewer. For
example: If the viewer were uninterested in romance, the romance scenes could
be skipped or replaced with abridged editions. |
Linear avatar games
often customise the experience to the player. CRPGs allow players to chose
their character's class and race, for example. Adventure games usually don't
provide any customisation. |
Customisation is
common and expected in sandbox games. Users control the character's race,
gender, class, equipment, etc. |
|
Problems have multiple solutions. |
Not possible. |
Players are given
problems to solve, but the problems typically only allow one or two solutions
that the author has planned ahead of time. Consequently, many of the problems
turn into puzzles. |
Players can solve
problems any way they chose, so long as the limited selection of verbs allows
for the solution. |
|
Players
encounter "guess the verb" frustrations when they think they can do
something but can't figure out how to tell the computer to do it, and/or the
players could use verb X with object A, but not object B. |
Not an issue
because the player can't actually do anything. |
"Guess the
verb" frustrations frequently occur in linear games because linear games
often rely on exceptional physics. |
Since sandboxes can
only use universal physics, guess-the-verb problems are not an issue. |
Note:
Sandboxes often include
quests, which are small linear games, allowing them to take
advantage of some features of linear games. Linear games often include cut-scenes,
which are short linear narratives, allowing them to take advantage of some
features of linear narratives.
Of
course, you can modify this list however you see fit. The point of the table is
that it shows that linear narration, linear avatar games, and sandboxes have
their own strengths and weaknesses. Some
players will prefer linear narration, others linear games, and others
sandboxes. Individual preferences will also change over time. There is no "right" answer.
Ultimately,
when designing a game, the
designer needs to decide how linear the experience will be. A
very linear experience will have certain strengths and attract one sort of
player, while an open-ended sandbox will have other strengths and attract
different players. (Note: People don't seem to like to watch totally linear
experiences on their computers; they prefer TVs for that.)
Visualisation
In
A tangle
I tried to visualise what was happening in an avatar game. Here's another way
to visualise what is happening...
In
a linear narrative,
the protagonist moves about and "does stuff", all the while time
slowly advances. If you graph the protagonist's movement through space in X and
Y, and time in Z, then what you get is a thin
squiggly line that traces the character's movement (and
actions) through time.
Now,
forget what I said about
X and Y being the protagonist's location in space, and Z being time.
X, Y, and Z (and any other dimensions) merely represent the
"location" of the character in arbitrary dimensions. Some of the
dimensions might be locations, but many could be based on choices, such as how
good or evil the character is. Even though the XYZ dimensions no longer have a
specific meaning, the
character's actions and "story" are still represented by a thin
squiggly line in arbitrarily dimensional space.
Then,
imagine the story
becoming less linear until it turns into a linear avatar game.
In the visualisation, the thin squiggly line thickens up, but still remains
squiggly. The thickness
of the line represents the number/frequency of "small" choices a
player has. When the player is allowed to make major choices, the squiggling line
branches, forming a tree; if the branches recombine then a
rhizome is created. If you squint hard enough you'll even see the string or pearls
formed by points where the player's small choices are narrowed into a thin
line.
As
the experience turns into a sandbox, players
are presented with many
more minor and major choices; The the squiggle thickens and produces abundant
branches. It becomes so thick and so laden with branches that
the squiggle becomes a complete tangle. This tangle is the visualisation of the
sandbox,
where players can do anything they wish.
Visualising
storylines
If
you have ever read a novel (which you probably have), then you've noticed that novels don't just provide the story of
the protagonist. They also include other characters; Often, half the chapters of the novel
will follow the story of other secondary characters. Some
novels even include several protagonists.
If
a thin squiggle represents one character's story, then a novel is a collection of squiggles.
Because books and movies are inherently linear, an author will first narrate a
bit of the protagonist's squiggle, then jump over to another character's
squiggle and narrate a portion of that, then back to the protagonist's
squiggle, etc.
By
convention, novels only narrate characters' stories where the stories (a) are
interesting to the reader, and (b) significantly and repeatedly affect the
protagonist. For purposes of visualisation, if a character meets or somehow affects another
character's story, then their squiggles touch. This means that a novel is a collection of thin
squiggles (each representing a single character's story), all of which touch
one another at one or more points.
Therefore,
a linear avatar game is
a collection of thick squiggles that occasionally touch. Most
avatar games only follow the protagonist, so the game only includes one
squiggle. However, some allow players to control different characters in
different parts of the same instance of the world, resulting in the same basic
structure as a novel (containing multiple squiggles, albeit thicker.)
Extending
the idea to sandboxes
produces several highly tangled squiggles that intersect one another. This
tangle of tangles is almost impossible to visualise in detail. I don't think
I've ever played a single-player sandbox game that had the player controlling
several different characters in the same
instance of the world. Single-player sandbox games do allow
players to create several characters, but they each have their own world instance.
Providing a sandbox with several characters (in the same instance) doesn't make
much sense, since a sandbox provides so much flexibility that a single
character is all the player needs to experience the game's instance.
However,
I have played a
multiplayer sandbox, where each player has their own character
in the same world. A multiplayer sandbox is known as a world-like MMORPG.
A multiplayer linear avatar game is like a game-like
MMORPG, but not quite. I haven't actually
seen any multiplayer linear avatar games as I've described. I have seen
game-like MMORPGs, such as World of Warcraft, that are part way
between a linear avatar game and a sandbox. WoW isn't a pure linear
avatar game because the storylines aren't well defined. Guild Wars
comes closer, but it only has one storyline.
There
are a few reasons why
I suspect pure multiplayer linear avatar games don't exist at the moment:
1.
No one has seriously tried the idea.
Most games are clones of other games with small evolutionary modifications.
2.
Existing game-like MMORPGs evolved
from world-like MMORPGs, and haven't made a complete
transition. As a rule, existing
MMORPG players do not like linear games, since MMORPG players
are attracted to world-like MMORPGs because of the other players, by the open
ended gameplay (sandbox), and/or by the extended gameplay. A multiplayer linear
game won't have the open-ended gameplay, nor will it take 500 hours to
complete.
3.
It's a lot of work to make a game-like
MMORPG with multiple storylines. Since
storylines are inherently linear (with occasional branching), if there's only
one storyline in a world then you and your closest 100,000 friends will
experience the same storyline in the same world, making you feel like you're on
an amusement park ride, not in a world. Therefore, a world must have many (4 to
20) storylines. This many storylines is not only a lot of work, but it is
"wasted" content that most players won't ever see; Bean counters will strenuously object.
4.
"Multiplayer stories" don't
make any sense since the experience is akin to sitting in a
darkened movie theatre with thousands of other well-behaved people; they have
no effect on the experience. As any die-hard sandboxer will tell you, multiplayer sandboxes make the most
sense. A multiplayer linear game, often derided by sandboxers
as a "massively single-player
game", is some place in-between multiplayer stories, which are
pointless, and multiplayer sandboxes, an obviously-successful application.
Thus, it's unclear whether
a multiplayer linear avatar game makes sense.
My thought experiments have revealed some reasons why
they might work, even though players
can't affect each other's larger "storylines". Player
interaction can have smaller effects:
1.
Friends can help each other out
on their storylines.
2.
Players can use the game world (and
storylines) as a way to meet
other people. See The dating game.
3.
Storylines can be designed to
(occasionally?) require interaction with other players.
Such interaction might include the buying and selling of items, teaming up with
specialist player-characters from another storyline, or even some PvP. See The game
loop.
Creating
a multiplayer linear avatar game
Here
are the challenges
with multiplayer linear avatar games, as I see them:
1.
The world and its backstory must be
rich enough to handle 4-20 storylines. The
World of Warcraft's backstory explains why players want to go out and kill
things, but not why a player would want to partake in a private eye or a town
mayor storyline.
2.
The team must create enough content
for 4-20 storylines. While this isn't nearly as much work
as creating an equivalent number of single-player linear avatar games, it's
probably 3x-10x as much content work. Don't forget that each storyline must
include a few variations as well as a few major choices that require branching,
requiring yet more content.
To
reduce costs and to make the world feel populated, some/most content should be shared
between at least two of the storylines. For example: The same
crypt that attracts a vampire-slayer (storyline) might also attract an
archaeologist (storyline).
3.
The 4-20 storylines must all be
individually interesting, at least to a portion of the
game-playing population. If
only 1% of the players elect to play a storyline then it probably isn't worth
including. The most popular storylines in MMORPGs are (1) kill
things, (2) kill other player characters, (3) trade, (4) explore, and (5)
forget about playing the game and just socialise. That leaves 15 storylines,
some of which might include being a detective, Don Juan, military commander,
ship's captain, or mayor of the town. Only ten storylines to go! (I suspect
there is a connection between successful storylines and The dream
factory.)
4.
As with a novel, players with different storylines will cross paths...
o All
the storylines must somehow interact with other storylines.
An isolated storyline that includes no interaction with other players might as
well be a single-player game. Likewise, if the storylines cluster into two
disconnected subsets, then split the game into two or redesign the storylines.
For
example: If explorers only ever interact with archaeologists, and
archaeologists only ever interact with explorers, then either re-design
explorers and archaeologists to interact with other storylines (like
merchants), or get rid of both of them.
o A
specific interaction
must make the game more fun for at least one of the players involved.
Ideally, interactions will be fun for both players, but this won't always be
the case.
o Corollary:
Interactions that are
neutral fun-wise should be gotten rid of. There is no point
forcing player interaction unless it will make the experience more fun.
o Corollary:
Players who like to play
alone must be able to avoid interactions with other players.
There might be a cost though, such as NPC merchants charging more than player
merchants.
o While
players may not enjoy some of their player-with-player interactions, on the whole, player interactions
should make the player's experience more fun.
Any storyline where player interactions end up being a net
minus for the player should be removed, since
players of that storyline would find a single-person version to be more fun.
The Player pyramid
may have effect though; Theoretically, some storylines could have a net
negative player-with-player interaction if
the player were allowed to play for free.
o Furthermore,
any storyline which is a
net negative to all other storylines should also be removed.
However, if a player pay enough money and ends up subsidising other players
then many sins can be forgiven.
Example:
One player might choose the storyline of thief, while another might be a
merchant.
At some point, the storyline of the thief might allow the
thief to rob a player merchant (resulting in controlled PvP); obviously, the
thief player will find the experience fun, while the merchant will dislike the
experience. To counteract the merchant's negative experience with the thief
player, the merchant will need a positive experience with another player, such
as making a spectacular profit from another player, or perhaps having the
pleasure of fingering the thief.
The thief storyline cannot be designed to cause grief
every time the thief interacts with other players, since that would make the
thief storyline a net negative. Consequently, some players will need to find
encounters with the thief to be beneficial and pleasant, such as an
archaeologist player that hires the thief to guide the archaeologist through a
series of trapped tombs.
The thief is only allowed to rob other players when his
storyline permits (which means only a couple of times). If the thief were given
carte blanche, the storyline would not only be a net negative, but it would be
an unknown negative, enabling a few particularly-successful thieves to ruin the
experience for hundreds of players.
5.
At best, marketing will only be able to
attract half of the current crop of MMORPG players. 50%-75% of them will
intensely dislike the linearity and/or the relatively short gameplay
of multiplayer linear avatar games. Consequently, marketing must convince players who
like single-player linear avatar games that they'll like the
multiplayer equivalent. This will prove difficult since most of these players
will either dislike other players impinging upon their world and/or having to
connect to the Internet while playing. They will also erroneously assume that
all multiplayer games are time syncs and cost $15/month. See The law of
new inventions.
26 January 2006
by Mike Rozak
I
decided that I need to understand puzzles and problems
solving more, since they significantly affect
sub-games, which in turn, affect quest design.
This article is the result...
To
put it simply, every puzzle (or problem to solve) has some aspect(s) that makes it difficult
or challenging to "solve". I was originally going to produce
a list of puzzle categories, but realised that the categories can themselves be
categorised into "what's the difficult part is", so I expanded my
mission.
This
article doesn't deal
with the appearance/dressing of the puzzle, just what makes it
challenging. Whether the puzzle is presented by a machine, NPC, riddle, or
natural occurrence is irrelevant to this discussion.
My
list of puzzle categories and the categories' categorisations follow:
I'm
not entirely happy with these categories. They don't seem orthogonal enough to
me. However, they're a start. I'll put this on the Internet and perhaps come
back to it in a few months.
PS - While I wrote
down many of these puzzle categories from my own gaming experience, some of
them came from conversations, archived and contemporary, on rec.arts.int-fiction,
listed in ifwiki.com.
26 January 2006
by Mike Rozak
Here
are some thoughts about quest design, based on my own observations, and various
books and articles that I've read.
The
difficulty curve
Swords & Circuitry: A Designer's Guide to Computer
Role-Playing Games, by Neal and Jana Hallford, points
out that the "difficulty" of a CRPG should increase over time, but
not in a nice smooth line. It should look more like the serrations of a saw-blade turned
on an incline. The game starts out very easy, gets slightly more
difficult, falls back to easy for awhile, gradually becomes more difficult,
takes a step back, etc. By the end of the game, the final boss monster is the
most challenging encounter.
There
are some reasons for this difficulty curve:
I'd
like to point out one more thing about the difficulty curve; there are actually two difficulty
curves. One is the curve faced by players that are
inexperienced at CRPGs (or otherwise unskilled/inept), and the other is a lower
but parallel curve that skilled (hard core) players experience. The lower curve (for skilled play)
needs to exist because it occurs naturally when the game
rewards intelligent actions. Basically, players who think, learn from the game,
and use their own skills/intelligence do better at the game. If both curves
were the same, then a player's personal skills and insights wouldn't impact the
player's progress. Players would realise this and feel cheated, like older
children feel when they realise there's no skill involved in Chutes/Snakes
and Ladders.
Unfortunately,
this means that hard
core gamers, who are looking for a challenge, find any given CRPG to be easier
than casual players do, and less challenging. The difficultly
level cannot be universally increased, since that would make the game
unplayable to casual gamers. It is possible to create a "difficulty level
setting" that players can select, and most CRPGs provide for this. An
increasing difficulty curve also mitigates this problem, but it means that
casual players won't have the skill to finish the game.
Adventure games have a larger separation in the
difficulty levels experienced by casual and hard-core players
because all the skill comes from the players, not the player characters. Due to
their nature, adventure games can't effectively implement a difficulty-level
setting. This means that a given adventure game is almost always targeted at
either hard core or casual gamers, not both. Many casual gamers will
"cheat" by downloading a game walkthrough for the puzzles they can't
solve.
Sub-games
throughout the game
Here
are some rules about sub-games:
Sub-games
in quests
As
I've discussed elsewhere,
a quest is a sequence of
sub-games wrapped within a "story". CRPGs tend to
rely heavily on the combat sub-game, but many more sub-games are possible.
Sub-games
should be carefully placed in quests:
Stories
Some
additional thoughts about "stories":
Choices
Players must be given choices.
As I previously
mentioned:
Consequences
All quests result in consequences to the player, player
character, and/or the game world.
Some
sample consequences
are:
If
possible, players should
be given a choice of consequences. There are several ways to do
this:
String
of pearls and threaded storylines
Due
to technical limitations, games
are usually designed to be a string of pearls.
Basically, a player enters a section of the world where they have a large
number of choices (and quests to choose from), but at some point their choice in quests narrows down to
the point where the player has only one quest left. Once they
complete the quest, they move on to the next pearl in the string.
In
a CRPG, the transitional quest usually involves killing a boss monster for the
pearl (such as the bandit king working for the evil overlord). In adventure
games, the transitional quest unlocks a door that lets the player into a new
section of the world. In both cases, the
consequence of the last quest in the pearl is to allow the player into the next
pearl.
Unfortunately,
by it's very nature, the
"pearl" narrows down into a string, at which point the player is
choice-less. You must compensate for this lack of choice:
Memes
Finally,
try to include something
memorable
in each quest. Without memorable events, players
won't recommend the game to other players.
1 March 2006
by Mike Rozak
This
article is a continuation/summary of the "laws"
of choices that I've been writing down in Choices,
Choices II,
and Choice
and consequences.
Available
options within a choice:
Strong
and weak choices:
Consequences:
Miscellaneous:
1 March 2006
by Mike Rozak
According
to Richard Bartle in Designing Virtual Worlds, MUDs with the longest lifespan seem to
cater to all player types: achievers, explorers, killers, and socialisers. MUDs
that attract only specific player types (such as only killers) quickly die out.
Recently,
I've been trying to design a setting for my virtual world. One setting that
interests me, both from a development POV, and as a hypothetical player, is a
"realistic" alien environment, like the TV shows, "The
Future is Wild" or "Alien Planet". For those of
you that haven't seen these shows, they use computer graphics to imagine what Earth's animal life might
evolve into 25 million years from now, or what animal life
might be like on an alien planet.
Although
neither show includes sentient life, if they did, the aliens certainly wouldn't
look like humans with pointy ears. Intelligent alien life might look more
bizarre, like land-dwelling octopi, for example.
The
problem with a realistic alien setting is:
1.
Most people can barely relate to pointy-eared
humans in Star Trek. Very
few can relate to intelligent octopi.
2.
A completely alien planet with
intelligent octopi characters screams out "explorer",
drowning out other possible storylines.
Some storylines, such as a romance, are impossible; it's hard to be romantic
with an intelligent octopi.
The
question arises: If I
were to create a realistic alien world that is only attractive to explorers,
how many players would I end up with? According to Richard
Bartle's observations, the answer is, "Not many."
A
broader model
Richard Bartle explains why a world needs to contain a
mix of achievers, explorers, killers and socialisers.
Basically, socialisers benefit from achievers because achievers give
socialisers something to talk about. Achievers benefit from explorers, who
explain the finer points of the world. Killers like having socialisers around
as prey. Explorers like having achievers around that they can slowly release
pearls of wisdom to them.
Previously,
I tried to come up with a broader models in Ecology of a
MMORPG and Intertwined
relationships.
However,
I think I can go a bit further now:
1.
As I discussed in Storylines II
and Storylines III,
you can think of a
virtual world as a collection of single player (linear) games
where (a) the games take place in the same world, and (b) the games and players
interact with one another. (I'm not saying that virtual worlds are limited to a
combination of linear storylines, however.)
Current
MMORPGs rely upon the following games/storylines: Kill monsters for a living,
kill player characters for a living, craft and trade for a living.
2.
Although individuals jump from one storyline
to another during gameplay (such as crafting to combat), players tend to stick with their
favourite storyline/game. This means that a player personality type (not
necessarily Bartle's four/eight types) is associated with each storyline.
The same general rule holds for movies, books, and
television: Individuals reliably like a few genres
(types of storylines) and avoid the rest. Personally, I like science fiction
and fantasy, but dislike romance. I have friends who are the opposite.
3.
In a virtual world, storylines interact with one another,
sometimes positively, often negatively.
In
MMORPG terms, PvP doesn't work well with scripted world-building (like Second
Life) because PvP players use the infinitely-powerful building-scripts as
a weapon; it's like giving every single PvP player their own arsenal of nuclear
weapons to use... and results in complete and utter devastation.
PvP
combat and crafting/trade seem to work well when combined into one game,
however.
To
switch media to movies, "romantic comedies" are a combination of two
genres/storylines, romance and comedy. Romantic comedies do well at the box
office. "Romantic horror films", to combine two different genres, do
not do well. (Has there ever been a romantic horror film?)
According
to this generalisation (which I don't think is quite correct), a virtual world should include
multiple storylines (which end up attracting different player types) only so
long as the interactions between the storylines are a net positive.
This implies that my alien world idea might work. An alien world would
obviously attract explorers, players that like to kill alien monsters, and
players that like to trade with aliens for bizarre goods. Players interested in
romance would go elsewhere, but that's no great loss.
Word
of mouth
Despite
my model's reassurances, I don't think my alien world would work, and here's
why...
Players of virtual worlds actively recruit their friends
to play because the game is more fun when one's
friends are involved.
I suspect that friend-networking
is the single largest marketing force that virtual worlds have.
Advertising and fansites fall a distant second and third.
This
relationship means that marketing (and design) of virtual worlds is less like a
TV, books, or single-player games, which are all solitary experiences, and more like the marketing (and design)
of a movies and restaurants. People go to movies and
restaurants with friends; I may really like a Chinese restaurant's Mongolian
beef, but if my friends don't like Chinese food, we won't visit my favourite
restaurant. Instead, we'll
choose a restaurant that everyone can enjoy (or at least not
dislike).
As
far as my alien world goes, I may like it, but my friends almost certainly
won't; It's way too weird for them. They'll want to play something different.
I'll be in the minority and be forced to follow them wherever they go, such as World
of Warcraft.
Categorise
players based upon what form of entertainment they consider "fun",
using whatever metrics you like. (This might be Richard Bartle's player types,
or something different). It seems to me that friendship is only weakly correlated to player type.
In restaurant terms (and to state the obvious), friendship is only weakly correlated to restaurant
preference; I don't choose my friends based upon what sorts of
food they eat. The inevitable result is that most restaurants are "family
style" restaurants that offer a varied menu. Likewise, most virtual worlds must cater to
most player types.
The
"cater to all player types" rule is only strengthened by the
realisation that friends will bring their friends into the virtual world too. The friends of my friends have play styles
that are completely unrelated to my own.
Consequently,
if my realistic alien VW
doesn't cater to players interested in romance, I'll not only lose
romance-liking players, but I'll lose any player who has friends that like
romance. The effect on world popularity is almost exponential! If
1% of all players would ardently like to play in an alien word, and if 10% of
all players would only play if they were cajoled into playing by the rabid 1%,
then the probability of getting four friends to play, one of whom strongly
wants to play, and three of whom are in the 10% group, is 0.001%. That's an
awfully small market.
Revisiting
the restaurant analogy:
I guesstimate that 5%-10% of Australians are vegetarians. The percentage of
vegetarian restaurants, however, is certainly less than 1%.
Exceptions
I
can enumerate some exceptions to my rule of "Thou must cater to all
player types and build the equivalent of a family restaurant":
1.
Age grouping
- The friends of most teenagers are teenagers. The friends of most adults are
adults. The friends of most retirees are retirees. Therefore, you can produce a
virtual world targeted solely at teenagers, adults, or retirees.
2.
Age-specific storylines/genres
- Teen sex flicks only appeal to teenagers. Undoubtedly, some storylines/games
will have similar age resonances.
3.
Education
- In the US, most friends of college-educated adults are also college educated.
Most friends of high-school educated adults are also high-school educated.
4.
Wealth
- Education and wealth are correlated. Furthermore, wealthy people tend to
spend most of their time with other wealthy people, because of the exclusive
neighbourhoods where they live, their job, and the awkwardness of showing their
less wealthy friends their latest $100K sports car.
5.
Language (and culture) -
Most people's friends are from the same country (aka: language and culture).
6.
Segregated cultures
- Many cultures are segregated based on gender, race, and/or religion. Such
segregation could be mirrored in virtual worlds targeted at the culture.
7.
Short virtual world experience
- The shorter the virtual world experience, the less strongly this rule holds
because friends are more willing to put up with an experience they really don't
like.
For
example: If a friend wants to see a romance movie, I will go (with protests).
However, if that romance movie were ten hours long, I would suddenly find an
illness/excuse to avoid the movie; Ten hours of fantasy, such as The Lord
of the Rings, and I'm feeling fine with an open appointment book.
8.
Niche markets
- If a player wants to experience an alien world so badly that he's willing to
forgo his real-life friends (or if a player doesn't have any real-life
friends), then the player will play alone and (hopefully) meet new friends in
the virtual world. See The dating game.
This observation implies that my alien world would attract somewhere between
0.001% (if players insist on hanging out with real-life friends) and 1% (if
players abandon real-life friends) of all players.
Another
issue I need consider is this: Do
I want to be a small
fish in an ocean, or a big fish in a small
pond? So many family restaurants serve the
(admittedly large) market, that individual family restaurants aren't very profitable.
Being the only one in town, a niche-market vegetarian cafe may do well despite
its small customer base.
Effects
on world design
The
requirement to cater to all player types affects world design:
1.
A family-restaurant world must be compartmentalised
by danger level, weirdness/explorer appeal, player-vs.-player, etc. While a
single player game, such as a horror survival game, can be universally
dangerous and weird, a virtual world must contain a bit of everything. Players
interested in romance will probably want a safe (non-combat) environment
without anything too weird (no octopi aliens). Explorers will want the aliens,
and a bit of danger. Crafters and traders won't mind the aliens, but they won't
want any danger.
2.
Whatever the storyline, a family-restaurant world can't change
too much throughout the storyline.
This
one is a bit tricky to explain... To be satisfying, storylines require that
world changes based on the player's actions. Change in multiplayer games
requires fractured
reality. Unfortunately, what is a "satisfying change" in storyline A,
is not the same as the change required for storyline B. Different changes
require different fractures, potentially producing a world with so many
fractures that players from different storylines will never meet.
Example:
If all players were on the same storyline of killing the evil overlord then at
every milestone (such as killing an important minion of the overlord) the
player could be moved a new section/fracture
of the world. GuildWars does this with pre/post apocalypse fractures.
The evil overlord storyline might have three fractures: one before the evil
overlord gained power, one during the evil overlord's tyrannical reign, and the
last taking place during the revolution. This fracturing wouldn't work with
multiple storylines (a consequence of catering to multiple player types)
because players interested in trade (and not interested in slaying the
overlord) wouldn't care which fracture they're in. Nor would they appreciate
being randomly moved from one evil-overlord fracture to another. So, which one
do they get placed in? Whichever one is used, two thirds of the time, players
from the evil-overlord storyline won't be able to interact with players from
the trade storyline.
3.
A niche virtual world must provide an
easy and effective way to meet other players. See The dating
game.
Why
I was vague about player types
I've
been purposely vague about what the different "player types" are because
(a) it's not necessary for the argument, and (b) locking into specific player
types limits one's perception of possibilities.
Richard
Bartle posits four basic player types (extended to eight in his book):
Some
"player types" of novel readers (aka: genres) are:
Some
player types (genres) from movies and TV:
Player
types (genres) from games:
An
evolutionary explanation for entertainment has some
other player types to add.
I
listed all these player types (genres) because they're all valid depending upon
how you look at the issue. The lengthy list also illustrates that people's ideas of "what's
entertaining" varies greatly.
The
same can be said for food: Family restaurants usually offer 50+ meals, not only
including traditional American dishes, but also genericized fare from
"niche" restaurants, "Mexican" taco salads, Indian curries,
Chinese spring rolls, and a few vegetarian dishes. Likewise, a virtual world that doesn't
explicitly target a niche market must offer an equally large menu of
experiences, catering to the enormous variety of entertainments that players
find entertaining.
More
ramifications
At
the moment, most MMORPGs
only target a tiny fraction of the player types listed here.
Even with a scope limited to Bartle's player types, most MMORPGs only cater to achievers
and killers. According to my thought experiment, expanding a
world's design so that it caters to even one or two more player types (whatever
your player-type model) should significantly improve the MMORPG's appeal...
assuming it's trying to be mass market.
9 March 2006
by Mike Rozak
A
month ago, Raph
Koster's blog discussed how levels make it difficult for friends
to play together. That got me thinking, and when combined with
ideas from The
parlour, the lobby, and the sandbox, I came up with
some interesting thoughts.
The
problem
Levels are a useful gameplay device.
They provide the following "features":
Unfortunately,
levels have some major
downsides with respect to friends:
Of
course, some work-arounds
to the level/friends conflict have been invented:
All
of these work arounds help, but they don't entirely solve the problem.
Levels
have additional problems, namely that players
feel railroaded into one advancement path, and don't feel like
they can individualise their characters. To solve the railroading problem, some virtual worlds use skills.
Skills
make the "friend" problem worse!
To
allow players to better customise their characters, many virtual worlds let
players advance individual skills rather than levels. If all of the skills are from the same
combat oriented skill-family (swordsmanship, archery, dodge,
etc.), then the sum of a character's skill levels is a good measure of how
"combat worthy" the character is. This combat worthiness can be used
to determine what content the character can handle, and what player characters
it can group with. Basically, "combat worthiness" is another name for
"level". The only difference between individual combat skills and
levels is that the individual skills allow some customization, giving players
more freedom.
However,
the logical next step with a skill system is to introduce skills like "basket
weaving".
This presents a problem!
Imagine that the game has two skills (or skill families),
"combat worthiness"
and "basket weaving".
1.
A character with combat worthiness of 20 can play in dungeons and quests
targeted at combat worthiness levels 15 to 25. This
behaves just like levels, where a player can partake in 10% of the content at
any point in time.
2.
Likewise, a character with basket
weaving skill
30 can partake in dungeons and quests targeted at basket
weaving levels
25 to 35. Again, it's just like levels.
3.
What happens if a quest is targeted at
players with combat worthiness
of 20 AND basket weaving of
30? What percentage of players will be able to
participate? 10% times 10% = 1%!
Consequently,
if a world has two
orthogonal skills (or skill families) as opposed to a single
orthogonal skill (or skill family) then:
As
with levels, some work
arounds exist:
No
skills or levels
A
third alternative is to not
have skills and levels (or to have very weak ones), making all characters
equal. This trivial
solution always lets friends play together, but it destroys some of the
fundamental tenants of CRPGs, levels (as above) and classes.
Classes are important because they allow players to tackle problems using
character-skills that suit the players, customising the gameplay experience to
the player.
One
way to handle not having skills or levels is described in Storylines
III.
My
intuition tells me that if there are no levels/skills for players to improve
(goals), and there are no classes (customization), and there are no storylines
(goals), then the world won't be very fun for most players. This might be part
of the reason why Uru
Live failed.
Lobbies
and friends
If
a game designer wants to ensure that friends can play together, and/or that
players can meet and play with strangers, another solution exists, the lobby.
Basically,
a virtual world designed
around a lobby has the following features:
1.
There is a main world which acts as a
lobby. Players can't do much in the lobby besides
meet up with other players. Players have characters in the lobby, but the
characters are basically holograms without substance.
2.
The lobby includes a "dating"
services that helps players meet. It
maintains a record of what content (instances) the player hasn't yet
experienced so that players won't be grouped unless they have
content that they might wish to experience together. The lobby might also link up players based on age, how much
time they have to play in the session, or other search criteria.
3.
An instance is a private world that
only a group of friendly players can enter. It is
created when a group of players meets in the lobby and decides to enter the
instance. The instance is destroyed when the players leave.
4.
Instances are short enough (1-3 hours)
to be completed in one session. An instance
could potentially be saved mid-way, but this would require that the same group
of players get together at the same time on another night, something that is
logistically difficult, even among friends. It is even more difficult for casual players
who only play five hours a week.
5.
In games like GuildWars and Dungeons
and Dragons Online, players bring persistent characters into the
instances, which doesn't solve the "playing with friends"
problem. In the lobby that I'm describing, players are given instance-appropriate characters when
they enter the instance. Thus, if they enter a standard
dungeon, players are given fighters, magic users, clerics, and thieves. If they
enter a murder mystery, they're given detective characters. These player characters' backgrounds might even be
specifically designed to fit in with the instance's story.
6.
Some instances are sequels to previous
instances, just as television series are discrete
episodes, yet form a longer narrative. Players can skip around
"episodes" of an instance series in order to play with friends.
First,
I'll list some of the
downsides of a lobby world:
However,
lobbies have many
benefits:
2 April 2006
Revised 4 April
2006
by Mike Rozak
Several
months back I played Fable: The Lost Chapters, and wrote up
the aspects of Fable that I thought were well designed and unique. I
just finished playing through part
of The Elder Scrolls IV: Oblivion and thought I'd do the same. This is not a review, but an analysis
of what works from a design perspective.
Hand-generated
vs. procedural content
Ever
since Will Wright demoed Spore, developers
have been all hyped up about procedural content, claiming that
it will save the world (along with Java, multimedia, and pet rocks). While I
use procedural content in my own game,
I do so with the understanding that procedural
content has limitations.
Most importantly, procedural
content can quickly become repetitive and boring.
Oblivion uses procedural
content, but doesn't rely solely upon it. This is where Oblivion
excels: Oblivion
has a great balance of procedural content, hand-generated content, and
in-between procedurally-aided content.
Some
definitions follow:
Realistically,
there's a whole spectrum
of hand-generation vs. procedural content: (This spectrum
metaphor is important for later on.)
|
Hand-generated |
Procedurally |
assisted |
Procedural |
How
Oblivion handles hand-generated and procedural content
Oblivion has a mix of hand-generated, procedurally
assisted, and procedural content. Importantly, it has a well-balanced mix of
content.
Here
is a brief description about how Oblivion's world is generated (given
my understanding and perception of it):
|
Procedurally
assisted |
The world's topography is
roughly painted by an author. Procedural algorithms, such as rainfall
erosion, are applied to the author's topography, producing gullies and
streams. |
|
Hand-generated |
The forest type
(evergreen, deciduous, grasslands, etc.) is painted on the topography by the
author. |
|
Procedural |
Individual trees, shrubs,
and grasses are procedurally placed
at run-time. |
|
Hand-generated |
Monster encounter sites are placed by hand, but in a somewhat random pattern. Monsters tend to be
near dungeon entrances and roads, but there is no compelling reason for the
monster to be in that exact location. For
example: Many bandit camps are placed in valleys near the road.
Realistically, a bandit camp would be on a sheltered hill near the road so
the bandits could climb the trees and overlook the road. Or, the camp would
be in the valley near a hill, but the bandits would spend all their time on
the hill. |
|
Procedural |
The specific monster
that appears at a site is determined by the player's level, although the
author does choose the type of monster (goblinoid vs. demonic vs. animal). |
|
Hand-generated |
Dungeon entrances are hand
placed, but their appearance is
based on a stock set of entrances. Dungeons are evenly scattered around the
world, but little attention seems to be paid to the reasons why dungeon X is
in location Y. |
|
Procedural |
Some dungeons, such as the
Oblivion gates, are randomly placed throughout the world. |
|
Procedurally
assisted |
Dungeons are created by
hand using a set of "tiles".
The dungeon layouts appear haphazard to me, and not as carefully laid out as
they could be. I suspect the haphazard layouts come from a combination of
tile limitations and because the designers don't have enough time to perfect
the layouts. |
|
Hand-generated |
Dungeon monsters and loot
are placed by hand, but somewhat
haphazardly. |
|
Procedural |
The specific monster and loot
that appears at a site is determined by the player's level, although the
author does choose the type of monster (goblinoid vs. demonic vs. undead). |
|
Hand-generated |
Cities are placed by hand, and their external appearance (always walled to
reduce polygon counts) is custom to the city. |
|
Hand-generated |
City streets and buildings
are placed by hand, using predefined
building models that are repeated though-out the city. |
|
Procedurally
assisted |
Most NPCs are placed by
hand, although guards are more
procedural. NPC appearance is hand-selected using sliders that modify a basic
model. |
|
Hand-generated |
NPC actions are controlled
by AI. The NPC's AI
goals are chosen by the author. |
|
Procedural |
The minute-to-minute details of what the
NPC does is left up to the AI. |
|
Hand-generated |
Quests are hand-generated,
and reasonably detailed and creative.
(Quests in WoW and EQII, are hand-generated and not as creative, with more of
yellow/green colour because they could almost be procedurally generated.) |
|
Hand-generated |
The main quest, returning
an emperor's son to power, is tightly
controlled and very detailed. |
The
colour spectrum applied to NPCs
Here's
example of the spectrum with regards to NPCs:
|
Hand-generated |
These are high-quality
NPCs that the player
will meet again and again throughout the game. The player is
supposed to develop strong
emotions about the NPC. In
Oblivion, the major NPCs appear in the main quest, such as the
Emperor's illegitimate son, Martin. Martin is a priest with a chequered past
(which I haven't discovered yet) that doesn't know he is royalty. He's
uncertain how to handle his new role as emperor... basically, someone the
player is going to like. In
the Myst series, almost all the characters are hand-generated
"red". Of course, the number of characters in each Myst
game can be counted on one hand. |
|
Hand-generated |
Orange NPCs are supposed
to leave an emotional
mark, but will only be encountered once or twice.
In
one mage's guild chapter in Oblivion, the head of the chapter asks
the player to find a guild member who has "disappeared". Meanwhile,
the chapter's head continues to practice low-level magic like summoning imps.
Talking to the other members of the guild reveals that they don't respect
their current chapter leader because she's not skilled enough. It turns out
that the wizard who has gone missing turned himself invisible as a joke,
knowing that the leader was incapable of casting the appropriate spells to
find him and then turn him visible. |
|
Procedurally
assisted |
This NPC is the standard
MMORPG NPC. He has a
name and an uninteresting quest to hand out, like "Bring
me some butter beer and I'll give you 10 gold." While the player
knows that the NPC likes butter beer, there's not much personality there.
It
wouldn't be difficult to randomly assign every NPC a favourite food/beverage
and automatically hand out similar quests. There's very little work for an
author to do. |
|
Procedurally
assisted |
This NPC only has a name
and a job. He is nothing
more than a human-looking vending machine. |
|
Procedural |
Blue NPCs have names, wander around, and might deliver a
rumour. When killed, they are automatically respawned. |
|
Procedural |
Violet NPCs don't even
have a name. They have
no purpose except as scenery (much like procedural trees) or monsters to be
killed. When killed, they are automatically respawned. |
A world with only red and orange NPCs sounds enticing
until you realise that due to production costs, the world would only have a
handful of NPCs. Ultimately, this unpopulated world
feels very empty. Myst is a perfect example.
A world with only yellow and green NPCs has many more
NPCs because they're cheaper to implement. It
feels populated, although still a bit sparse. The NPCs are fairly uninteresting though.
Most MMORPG cities resemble this.
If a world were only populated by blue and purple NPCs
then there would be a hoards of them, but they'd
be universally pointless. MMORPG wilderness and dungeons are typically
populated by blue/violet monsters.
Oblivion has NPCs from all colours of the spectrum,
although it would be nice to see more blue and violet NPCs wandering around the
cities so the cities looked busy.
Mixing
the colours of the spectrum produces white
If
you look at the colours I've use to label Oblivion's content, you'll
notice that they come from every
colour of the spectrum. This makes Oblivion's combined colour, white-ish.
If
I were to create the same table for any of the Myst games, the colours would be almost
all red. Diablo and Rogue would be almost entirely blue or
violet. Most MMORPGs
rely heavily on yellow and green.
The advantage of being white is that it's a bit of
everything. My perception of Oblivion
(so far) is that it's a huge world (a result of procedurally generated content)
with splashes of meaning/plot interspersed (from the hand-generated content). Myst
feels less like a world because it's so small (which is one reason Myst
takes place on an island). MMORPGs feel large, but they are devoid of meaning
(because there isn't much red hand-generated content).
Notice
that MMORPG NPCs are chromatically between finely hand-crafted and procedural
NPCs. However, being in
the middle (yellow or green) is not the same is being white.
MMORPG cities are places to go and shop ,or to get quests from the NPC vending
machines. They don't contain NPCs that players will care about, nor are there
random NPCs wandering around to make the city look busy.
White is important.
In my opinion, Oblivion
is a better game than any of the Myst series or any MMORPG I've tried,
mainly because it is "well
rounded"... aka: It has all the colours of the spectrum. I suspect that games which manage to
include a range of content, from hand-generated to procedural, will ultimately
be better games.
To
use a medieval analogy appropriate to Oblivion, a quality sword must be forged with
both hard and soft steel. A blade entirely of hard steel will
be very sharp but will easily break. A blade entirely of soft steel won't
break, but it will not cut well. A compromise of a sword that's uniformly
medium-hardness isn't a terribly good weapon either. The proper combination of hard and
soft steel is critical.
Another
way of looking at this
When
developing an avatar
game, you
have the option of spending money on content creators
(hand-generated content), programmers (procedural content), and/or
procedurally-assisted content (content creation tools that make content
creation a breeze, involving both programmers and content creators.)
As
per my earlier discussion, the
"content" produced by content creators feels and behaves differently
than the "content" produced by procedural content or
procedurally-assisted content.
Individual players will prefer the different types of
content based on their individual personalities and their current mood.
For example:
Contrast this to a game like Myst,
where if I don't want to partake in the main plot, my only option is to stop
playing. Or a MMORPG, where there is no larger story worth mentioning, leaving
the experience of killing hoards of orcs somewhat meaningless.
p.s.
- One major gripe about Oblivion
Morrowind, Oblivion's
predecessor, had one major problem: Given that your
character was level N, many of the dungeons you'd wander into would either be
too tough (designed for level N+5 or higher), or too easy (level N-5 or lower).
Consequently, most dungeons would require a speedy exit because the dungeon was
way too difficult, or the dungeon was a piece of cake. Consequently, there was rarely any challenge; most
dungeons were either too hard to even contemplate, or way too easy.
Oblivion fixed this problem, but with an equally bad
solution. (I admit that there might not be a good
solution.) Almost all
dungeons automatically adjust their difficulty level to suit
the player's character level. This is good in that all dungeons are now challenging,
but...
29 April 2006
by Mike Rozak
Discuss on www.mXac.net/forums
A
few years ago, I read Creating Emotion in Games, by David Freeman. One
of the points that David Freeman made (or at least implied) is that games should be designed to evoke a
variety emotions from players, like an emotional
roller-coaster.
I've
been a bit sceptical about this idea. However, lately I've been toying with the
idea that if you broaden
the commonly accepted understanding of emotion, emotions (in their new sense)
could explain why people play games. Interestingly, the list of
broader emotions also includes some concepts from ludology, where researchers
have defined a number of "types of fun", such as "hard
fun". Many of the ideas also tie into my thoughts about evolutionary
fun.
Warning:
These ideas are half
baked. I'm just wandering down a path and seeing where it takes
me.
Some
emotions
How
have I expanded my definition of emotions?
To
begin with, I'll enumerate some "animalistic
emotions" that are common to most mammals:
Some
emotions that humans experience are more primate
specific, although they do occasionally appear in other
animals:
Finally,
I'll broaden the
commonly accepted list emotions to include some
"emotions" that are specific to humans:
Notice how my human-only emotions aren't usually called
emotions. They're often prefixed by "sense
of" or "feeling of" though. They act like emotions since they're
rewards (or punishments) handed out by the brain, and people go out of their
way to cause these "emotions".
Table
of entertainments and emotions they can evoke
With
the expanded definition of emotions, I noticed that every form of entertainment
evokes some of these emotions. Not surpassingly, some forms of entertainment
are better at evoking some emotions than others. Below is a list of emotions
along with capital X's to indicate if the entertainment is very good at
producing the emotion, and small x's if the entrainment is less adept at
evoking the emotion.
|
Emotion |
Stories |
Sports (watching) |
Single-player computer games |
Sports (playing) |
Multiplayer computer games |
|
Anger |
x |
X |
X |
||
|
Dominance |
x |
x |
X |
X |
|
|
Excitement
(adrenalin) |
x (action movies) |
x |
X |
X |
X |
|
Fear |
X |
x |
x |
X |
|
|
Frustration |
x |
X |
X |
X |
|
|
Hate |
x |
X |
X |
||
|
Lonely |
x |
x |
|||
|
Lust |
X |
x |
|||
|
Surprise |
X |
X |
X |
X |
X |
|
Anticipation |
x |
X |
X |
||
|
Boredom |
X |
X |
X |
X |
X |
|
Disgust |
X |
x |
(?) |
||
|
Feeling of
comradery |
x |
X |
X |
||
|
Jealousy |
x |
X |
|||
|
Love |
x |
(?) |
|||
|
Sadness |
X |
(?) |
|||
|
Shame/guilt |
x |
X |
X |
||
|
Epiphany |
x (mysteries) |
X (adventure) |
(?) |
||
|
Laugher |
X |
||||
|
Sense of being
needed |
x (CRPG) |
X |
X |
||
|
Sense of
achievement |
X |
X |
X |
||
|
Wonder |
x (fantasy, sci-fi) |
X (adventure, CRPG) |
(?) |
The
(?) in multiplayer computer games indicates that it's possible, but
not catered to in current MMORPGs.
Some
interesting revelations...
This
table produces some interesting revelations:
29 April 2006
by Mike Rozak
Discuss on www.mXac.net/forums
Avatar games
segment their worlds so that players can't enter portions of the world without
completing specific tasks. Designers do this to ensure that new geography
content is gradually opened the user throughout the life of the game, preventing the user from experiencing
all the geography content at the game's outset, thereby
weakening the experience. (Character abilities, such as spells, are also gradually
unlocked throughout the game.)
I
just thought I'd spend some time listing different styles of gates and keys.
First, the most common mechanisms:
Some
solutions that restrict world access that aren't exactly gate-and-key:
29 August 2006
by Mike Rozak
Discuss on www.mXac.net/forums
I
am a mouse running betwixt the feet of elephants.
Consequently, I spend inordinate amounts of time figuring out where the elephants will roam.
The
elephants (major MMORPGs)
Here
is my best guess as to what the major ($20M+) MMORPGs will "innovate"
over the next five years:
In
case you haven't noticed, I'll point out some larger trends:
Elephant
wannabes (minor MMORPGs)
Since
I am merely a mouse, I also need to watch out for the minor MMORPGs (budgets
less than $20M). Here's what I think they'll be up to:
The
NPC-conversation wall
You
may have noticed that I
failed to include "more intelligent NPCs" in either
the major or minor MMORPG list.
There
are two kinds of
"intelligence", neither of which will be used in
MMORPGs (or so I think):
NPCs with personality, ones that you could actually care
about, present a number of technical and design problems that form a sizeable
barrier that won't be quickly overcome, the
NPC-conversation wall.
The
"NPC-conversation
wall" consists of the following elephant-blocking stones:
1.
For the most part, players aren't requesting
conversational NPCs - They ask for more weapons, more monsters,
better graphics, ship-to-ship combat, etc. They either don't think that
conversational NPCs are possible, or don't want them (perhaps because the
game-player demographic is self-selected to not care about such things).
2.
Intelligent NPCs require much more
server-side CPU - A factor of 10x the server-side CPU
currently devoted by MMORPGs is easy to imagine. 100x is also possible.
3.
Intelligent NPCs require enormously
more server-side memory. After all, a NPC won't be realistic
if it forgets that it talked to you just yesterday. Just consider of the
numbers: An average MMORPG server has 3000 concurrent peak users, which implies
15,000 players, each with 4 characters each, or 60,000 characters. 60,000
characters times 1000 NPCs is 60,000,000 relationships to keep track of! Even
if most relationships are only tens-of-bytes of data, many will be several
kilobytes.
4.
Player density must be low
- A typical MMORPG NPC can "talk to" a dozen players at once since
the NPCs don't really talk; they just display private dialog boxes on each
player's screen. A realistic and believable NPC wouldn't be able to do this,
and would only be able to talk to one, maybe two, players at a time. This
either requires a much lower player density, or many more NPCs, such as several
weapons merchants in the same area.
5.
Text-to-speech
- MMORPGs are transitioning from text-based NPC dialogues to recorded speech;
recorded speech makes for better eye candy than text, as well as working
synergistically with 3D visuals. Unfortunately, recorded speech severely constrains the ability for NPCs
to carry on a dialogue because its impossible to record every
single utterance that might come out of a NPC's mouth, especially when
multiplied by the number of NPCs.
The
only solution to this problem is text-to-speech. Unfortunately, text-to-speech sounds lousy and will
scare away most players. It sounds even more lousy when
intermixed with recorded speech. Any MMORPG that already uses recorded speech
(which will soon be almost all of the major MMORPGs) will find it (nearly)
impossible to transition to text-to-speech.
6.
Typed responses or speech recognition
- No matter how socially intelligent a NPC is, if a player's responses are
limited to a menu of four phrases, the NPC will only have four possible
responses, nullifying any advances in intelligence.
There
are two solutions: Either allow the player to type in a response and use natural-language
understanding to interpret what the player said, or use speech recognition to
transcribe the player's speech before passing it onto the natural-language
understanding system.
Unfortunately,
current natural-language
understanding requires that players forgive it's limitations;
players can't just say anything they like, much as text-adventure players
continually run into the "guess the verb"
problem.
Nor
do players like to type (or even know how). Sadly, typing is the only option
since speech recognition
doesn't work well enough yet.
7.
Large number of NPC animations
- In games such as Oblivion, where NPCs are smarter than your average
MMORPG NPC, you see another problem: Characters have a limited palette of
animations that they can perform, mostly involving movement and combat. This
limits how much personality can be expressed by the NPCs; Theoretically, one
NPC might swagger, while another might daintily sip her tea. Standard MMORPG
animations don't include "tea sipping" or "swaggering", let
alone "dainty tea sipping". Needless to say, NPC animations are very expensive to
produce.
8.
Faces - Faces are
critically important for realistic NPCs. Unfortunately, the following problems
arise:
o MMORPG
UI needs to be redesigned to that NPC faces are more than just a few pixels
high. They actually need to occupy most of the
screen.
o As
soon as faces are enlarged, players will realise that the faces all look the
same. To solve this, MMORPGs
will need more complicated and varied facial geometry, textures,
hair, jewellery, clothing, etc. This means higher development costs and more
memory on graphics cards.
o Attempts
at procedural face
animation, as in Oblivion, result in characters whose
facial animations are so "wrong" that the characters look mentally
ill.
9.
Cut scenes
- One important way to express a NPC's personality is to show how they interact
with other NPCs, either through scripted actions, or in cut-scene anecdotes of their
past. MMORPGs don't have any facility for low-bandwidth cheaply-produced
cut-scenes.
6 September 2006
by Mike Rozak
Discuss on www.mXac.net/forums
Awhile
back, I wrote "My
current grand unified theory of avatar games" and
"Virtual
world equation". It is time to update and combine
them. (Note: I've presented most of the ideas here
before, but this document cleans them up a bit.)
So,
without further ado, here is my current
theory of life, the universe, and everything... with respect to virtual worlds.
Single-player
avatar games (aka: CRPGs, FPSs, adventure games)
A
single-player avatar game needs to accomplish the following:
1.
Players enter the game with one or
more "goals",
many subconscious. These goals might include obvious items, such as: to
waste time, to be entertained, or to provide a challenge. Or, they might
be more intricately tied to the real world, such as goals to: own a house,
make friends, be famous, explore new planets, save the world, etc.
The
"deeper" and often subconscious goals, such as the desire to be
important to society or to understand oneself, vaguely segue into Richard
Bartle's "hero's journey" theory, as described in Designing
Virtual Worlds.
2.
One of the game's tasks is to figure out
what the player's goals are so that the game can fulfil the
goals. CRPGs accomplish this, in part, by letting players choose their race,
class, and how they wish to experience the world (such as Oblivion's
choice of fighting vs. thieving).
Corollary:
The game's genre,
advertising, and early user experience should indicate to the player what types
of goals the game is capable of fulfilling. A player who cannot
properly fulfil his goals in a game will either (a) stop playing and give a
negative review to his friends, or in the case of a multiplayer game, (b) try
to achieve the goal despite the game, leading to whingeing on the game BBS, or
in-game griefing.
3.
The game may provide players with
game-specific goals, such as rescuing the princess. Such
goals should (ideally) align with the player's goals. To get players to take up
game-given goals and internalise
them, the game may use "story"
elements to add an emotional context to the goal. (Occasionally, the story's
resolution may be a goal in itself.)
4.
At any given time, the game should provide players with a
menu/choice
of goals they work towards. As with a GUI menu, the (figurative)
game-menu should be around five choices.
5.
Players should be given in-game
abilities that allow them to achieve their goal in a manner that the player
expects. This may require the author to allow for several
different solutions to the
goal.
6.
The time and effort
required to accomplish the goal should be
roughly what the player expects. Players
expect that walking to the grocery store will be easy, but rescuing the
princess will be difficult. If a goal is easier to achieve than players expect,
they'll complain that game is too easy and/or too short. If the goal is too
difficult to achieve, players will give up and complain to their friends that
the game was too difficult or boring.
7.
The time and effort to accomplish a
goal should be enough to produce a desired effect.
For example: If the game is about being an explorer like Magellan or Columbus,
then travelling between places might take a relatively long (and boring) time
so that players feel like they have travelled a large distance. Another
example: Defeating the evil overlord is made pointless if (a) the evil overlord
never posed a real threat to the player or (b) the evil overlord never did
anything evil to the player.
8.
Don't give players exactly what they
expect, either in the process of attaining the
goal, or the enjoyment of the goal. If a game does provide players exactly what
they expect, they will find the game boring. Instead, give players something better than
they expected. (Easier said than done.)
9.
Fun comes into play here; Goals should be "fun" to
achieve, and to "fun" to enjoy once achieved. Go talk
to Raph Koster about this, or read his book, A Theory of Fun.
10.
By the time a player has completed and
enjoyed his goal, a new
goal should be found
to fill its place. A goal-less player will leave the
game.
11.
No matter what, all players will eventually decide
that it's time to leave the game... (Except Peter Pan, who
never grows up.) Even if the game always has another goal waiting to be
achieved, the player will either achieve his personal goal(s), or
(subconsciously) decide that the game either won't fulfil/satiate his personal
goals any more, or is making the goals too difficult to achieve. (Again, this
hints at Richard Bartle's "hero's journey"). In an ideal world, players are able to
achieve and enjoy their personal goals.
Also
in an ideal world, the player's exit should coincide with an end to the
content, so that (a) development money isn't wasted on unused content, (b) the
player doesn't feel like he is abandoning content that he payed for, and (c)
all the content wraps itself up into a memorable
ending.
Multi-player
avatar games (aka: MMORPGs, MUDs, virtual worlds)
Multiplayer
avatar games are more complex since they not only have all the single-player
issues (above), but several new challenges:
1.
Every player has their own set of
goals that they bring with them, which isn't any
different than a single-player game except...
2.
Players tend to bring their
friends into the game,
or to leave the game when their friends leave. This means that a multiplayer
game must support a much
larger variety of goals.
3.
Multiplayer games have a high barrier to
entry (because of Internet connection costs/problems and the sometimes negative
affects of other players). Therefore, players
who play multiplayer games usually have strong social reasons why they want to
play, otherwise they would take the easier/cheaper route and
play single-player games. These reasons/goals could be as simple as wanting to meet other
people, or could be more complex, such as: the
desire to run a business with real customers, to be a leader, or to dominate
other people.
4.
Players' goals often conflict:
There can be only one Napoleon. If two or more players want to be Napoleon,
then a mechanism must be in place to determine who gets to be Napoleon.
Contemporary MMORPGs rely upon "the grind" as a mechanism; he who
whacks the most orcs for the longest period of time is crowned emperor. Real
money payments, player skill, or a lottery could also be used.
5.
A player's goals often impinge upon
other players' enjoyment of the game. If a player
wishes to be an innkeeper, then other players must visit his inn, even though
they would prefer to whack orcs and avoid inns altogether. Consequently, some mechanism
must exist to ensure that a player's goal doesn't harm another player's
experience, and/or if a player's goal does inconvenience another player, the
other player should be compensated.
One
obvious solution is to make
the player work to achieve his goal: For a player to achieve
their own goal, they
must play a part
in helping other players achieve their own goals.
Ideally, players won't even know (or care) that they're playing a supporting
role. One solution is to create an in-game
system that connects players together: People that want to meet
other people "coincidentally" meet up in the street. Leaders are
"coincidentally" connected with followers. Innkeepers with clientele.
Griefers with people that like being griefed (but who won't admit it to the
griefers). Etc.
In
some cases, the "support" is in the form of higher fees, which end up
paying the employees,
who end up "altruistically"
supporting the player by providing them new game mechanics, better eye candy,
or game masters whose role is to make the game more fun.
6.
All of these issues combine to create
a complex
social ecology. The world must attract a certain
percentage of players that want to be followers, a few leaders, and only one
Napoleon. If everyone wants to be Napoleon, or everyone wants to be a follower,
then the social ecology disintegrates and the game dies. Richard Bartle
discusses this in his book, Designing Virtual Worlds.
20 September 2006
by Mike Rozak
Discuss on www.mXac.net/forums
Once
in awhile, I sit down and write down all the reasons
why I think people play (multiplayer) games.
I purposely start with a blank page to maximise my creativity. This time
around, I scribbled down the following items:
While
I wrote my list, several other issues floated through my mind:
"I
think therefore I am".... not quite...
All
these ideas were intermingling in my brain when a subtle (or illusory?) pattern
suddenly emerged...
Several
of the reasons why people play games come down to a simple statement, "I exist!"
These are:
Descart's
one-liner, "I think therefore I am," while profound, isn't
appropriate in this case: "My decisions and actions affect the world, therefore I
exist (in the world)," seems to be more
precise.
I
rapidly had another epiphany: "The
terrible twos" are all about "I exist!"
Two year olds are continually defying parental authority (aka: making
decisions), looking for excitement, creating (with crayon scribbles on the
walls), and impacting other players (seeking attention by being cute, as well
as the less-desirable temper tantrums).
I
am a success!
Another
collection of reasons for play can be seen as ways that players prove that
they're a success:
The
pattern begins to emerge...
Consider
this:
1.
Children (up to their teens, and
sometimes beyond) are often concerned with "proving their existence".
They may not be as obnoxious about it as two-year olds, but much of what they
do is similar.
2.
Once a child has proven to himself and the
world that he exists, sometime before age ten, he notices that everyone else
has known this all along, and they all exist too. Furthermore, other people "exist" better
than the child; Adults and older children are more skilled at
just about everything: being better at basketball, able to ride their bicycle
faster, standing several inches taller, etc.
3.
The next logical step is for children to
improve how well they exist. Children
do this by being competitive... by being a success! (For those
of you than don't remember being ten, just about everyone in the world is
better at everything you try to do. If this fact weren't obvious enough, school
reinforces the impression by continually handing you grades informing you how
incompetent you are!)
Children
quickly realise that they can't be a success at everything, so they target a
few niches and work to be the best; they join the baseball team and are almost
immediately better than 90% of their classmates... only because their
classmates haven't joined the baseball team. They then work their way up the
team, trying to become MVP; if that fails, they try their lot at hockey,
volleyball, or swimming. Children eventually find something they can be best
at, even if it is something obscure like discus, the chess club, or WoW
battlefields.
A
second pattern came to my attention: Both
"I exist!" and "I am
a success!"
can be sub-categorised into "introverted" and
"extroverted". Making choices, for example, is more
introverted because people don't need others to witness the choices for the
choices to be relevant. However, a temper tantrum in isolation proves nothing;
it must have an audience to be appreciated.
Behold!
A matrix:
|
I exist! |
I am a success! |
|
|
Introverted |
|
|
|
Extroverted |
|
|
And
now for "the meaning of life"
What happens (in real life) when someone finally is a
success (at discus, or whatever)?:
1.
Many people don't ever become a
success, but keep striving for their entire lives. If
your definition of being success is being the richest person in the world, you
are unlikely to succeed.
2.
Many people succeed and immediately
choose another goal to succeed at, and then another, and another.
How many sports stars become top of their game and then "retire" only
to take up another sport or to become a coach?
3.
Most people eventually succeed at
something, coming to the following conclusion: I succeeded
at being a discus player; I am the best in the world, or at least in my home
town. That's pretty good. I could try to succeed at something else, but, in a
sense, "I've been there and done that." I know how much work
it will take, I know what it will feel like to succeed, and I know I'll have
the same "What's next?" dilemma when I'm a success at my new
endeavour.
4.
If I am a success and I don't care to
repeat the experience, what do I do with my life?
Of course, I need to put food on the table and provide shelter, but that still
leaves a lot of spare time. What now?
5.
What do famous sports figures do after
they retire? Some coach, but not necessarily to prove
they're a success. Some start up tennis schools. Some donate their time to
charity. Some become politicians. Some just drink themselves to death...
Ignoring the alcoholism alternative, the answer is that, "People who have been successful become
mentors," or take on similar sorts of altruistic roles.
Introverted people, whom you usually don't hear about,
often go back to university, or read every book in the library,
or spend all their time watching daytime soaps.
The
matrix expands:
|
I exist! |
I am a success! |
Meaning of life? |
|
|
Introverted |
|
|
|
|
Extroverted |
|
|
|
I
have (for the most part) said nothing new
First
of all, the transition from "I exist!" to "I am a
success!" to "Meaning of life?" sounds
suspiciously like Maslow's
pyramid of human needs, with "physiological" needs at
the base, followed by "security and safety", "love, and feelings
of belonging", "competence, prestige, and esteem",
"self-fulfilment", and finishing up with "curiosity and the need
to understand."
The
path also mimic's Joseph
Campbell's Hero's
Journey to an extent. "I exist!" is the
"departure" portion of the journey, "I am a success!"
the "initiation", and "Meaning of life?" is the
"return".
It
likewise vaguely resembles Richard
Bartle's "development tracks" from Designing
Virtual Worlds, of which there are two common paths:
The
new stuff!
I
do have something new to say; Let me first discuss a few more observations:
1.
All people exhibit "I exist!",
"I am a success!", and "Meaning of life?"
behaviours at various
times, and often simultaneously. I am painting is very broad strokes.
2.
In real life, children (ages 2 - 16) tend to have "I
exist!"
behaviours. Adults do too, but the tendency is more prevalent
in children.
3.
Teenagers and 20/30-something adults
(10 - 40) tend to have "I am a
success!" behaviours,
beginning with school grades and sports, and finishing with corporate ladder
climbing and expensive houses.
4.
On a tangential note:
Between ages 20 and 25, most people's brains develop a genuine sense of empathy, which
barely exists for children under 15.
To
use an everyday example, teenagers (in particular) play incredibly loud music
and selfishly impose it on the neighbourhood. Teenagers know that other people
don't like them playing loud music, but they don't care. By age 25, loud music
playing tends to stop because (IMHO) the culprits can now empathise with their
neighbours, who have complaining about the music's volume for years.
The
introduction of empathy has an effect on games. As a teenager, I'd happily spend
hours slaying orcs in fantasy games. As an adult, I wonder why all orcs are an
enemies, and if the virtual orcs have virtual mothers who cry over their
childrens' virtual graves. This effect really struck home when I was playing World
of Warcraft and a quest requested "10 bear gall-bladders"; in
the real world, real bear species are nearly extinct because of the real bear
gall-bladder trade! The same goes for shark fin, rhino horns, etc. I found
myself empathising with virtual bears.
5.
30-something adults and older (30 +)
tend to have "Meaning of life?"
behaviours. By the time people hit 40-50, you often hear
them asking, "Why do I have such a large house?" and
"Why am I working 60 hours a week?"
6.
People tend NOT to change from
introvert to extrovert over their lifetime, and vice versa.
What
this means:
Loose
threads...
I'm
not sure I entirely believe these conclusions. They seem a bit too simplistic
to be real, and I can readily poke a number of holes in the theory. However,
the theory provides some conclusions that need to be explored.
Furthermore,
the model doesn't "explain away" all the reasons why people want to
play games, such as:
30 September 2006
by Mike Rozak
Discuss on www.mXac.net/forums
It's
time once again to talk about The Elder Scrolls IV: Oblivion. You
might wish to read my previous article about Oblivion here.
This
article discusses the
"process" used to design Oblivion, or
at least the process as near as I can guess. It relies heavily on Oblivion's
"Official Game Guide", which I'd strongly recommend to anyone
interested in designing avatar
games. The book is intended as a walkthrough, but
since it describes just about everything in Oblivion, it also doubles
as a design document.
Sub-games
Oblivion contains a number
of major sub-games:
As
well as quite a few minor
sub-games:
Oblivion's designers could
have used different sub-games, or could have emphasised some sub-games above
others. For example, Oblivion does NOT include the following sub-games:
Setting
Oblivion's setting has the
following elements:
www.MudConnect.com
lists nearly two thousand text MUDs and their web pages. If you browse through
them you'll see that almost every MUD has a page devoted to each of the setting
items that I listed. However, what most of those MUDs don't do is have the
setting items significantly (or creatively) affect gameplay. Being an Elf is no
different than being a Dwarf, except for the cliche attribute differences. The
few games that eschew Elves, Dwarves, and Orcs still have Elf look-a-likes, and
don't seem to realise that an
Elf by any other name is still an Elf.
The
procedural world
I
discussed Oblivion's use of procedural content in my previous
article.
To
create Oblivion, the designers created the following
"procedural" elements:
The
reason this part of the world is "procedural"
is because:
1.
While a person enters the information by
hand, they usually
aren't too particular about it. It doesn't really matter if a
character is named "Fred" or "George", or where a specific
monster is, or if a particular home contains a book of cooking recipes or a
book of history.
2.
Once the data has been entered, the author just presses "go"
and the world activates. From then on, it requires no intervention from the
author or the scripts he writes. (Sounds like a certain
religious debate from a few hundred years ago? Does God still play an active
role in the world, or did he create it and leave it to its own devices?)
"The procedural world" is a standard component
of FPSs, CRPGs, MUDs, and MMORPGs. Most players (and
apparently most B-grade game designers) think that all a world needs is
procedural content and then it's done. This is far from the case.
Adventure games do NOT have much of a procedural world.
In most adventure-game worlds, every detail is carefully placed by the
designer. Any details that do not
need to be manually placed probably aren't critical to the game and simply
aren't added.
"Grind"
tasks
One important design element of procedural worlds is the
ability to "grind" in them.
Whenever
a player is allowed to make choices, they will make mistakes. Some of these
mistakes will cause the player to lose/squander resources, like money,
equipment, and experience points. Unfortunately, if a player makes too many
mistakes, they find themselves without any money, equipment, and/or spare
experience points. This lack of resources prevents them from completing the
game.
To
get around this problem, most
worlds have a "backup" so that players can earn back resources that
they have squandered. Oblivion, for example, provides
an infinite supply of monsters to kill and herbs to collect. However, to ensure
that players don't spend all their time playing the "backup" game
instead of the real game, the "backup" game is designed so that it
doesn't pay (in resources) very well, and is fairly dull. (Plus, there's no
reason to make a "backup" fun, or it would be the primary game
instead.)
A dull backup game is also known as "the
grind".
MMORPGs, for reasons of their own, rely almost
exclusively on "the grind". They need
the grind because (a) they want to keep players around for as long as possible
and can't afford a continual supply of quality (fun) content, and (b) they need
a way to "rank" players since many players attracted to MMORPGs like
being ranked (above other players, that is). Without liberal amounts of grind,
a MMORPG's content would quickly be exhausted by players who routinely spend
20-40 hours per week playing, all the characters would quickly reach the
maximum level, and ranking would be impossible.
Adventure games don't include a grind.
They avoid the need for a "backup" by making it impossible for
players to make decisions that prevent them from advancing in the game.
However, adventure games do reduce cost by making many of their puzzles so
obtuse that players spend hours wandering around looking for a solution.
Quests
Built
on top of the procedural world are hundreds of quests.
These are detailed and hand crafted. Each
quest has:
Many
CRPGs and MMORPGs include quests, although they tend not to be as deep as Oblivion's
quests. MMORPGs are
especially guilty of having shallow quests, relying on quests
that provide a flimsy purpose (aka: FedEx deliveries), no plot, no characterisation,
no setting exposure, no cut scenes, no special quest-only regions, no changes
to the game world (during or after the quest), no choices in approach, and no
choices in outcome.
Conversely,
adventure games are
almost one hundred percent quests (or technically, quest-arcs).
Adventure games don't usually allow for choices in approach or outcome though.
Quest
arcs
Oblivion combines a series
of quests to create a quest
arc. Quest arcs allow the designers to do the following:
Personal
NPCs
Oblivion does not have personal
NPCs. Adding them would have improved Oblivion. For more information,
read this.
Basically, you can think of a personal NPC as an extra-long quest-arc that
travels with the PC, or at least frequently visits the PC.
Elements
of "delight"
A
few months back, Raph
Koster mentioned adding elements to
"delight" players, but not necessarily perform any actual game
purpose. (By the way, the same idea was also brought up a few thousand years
ago by Aristotle. He called it "spectacle".)
Oblivion contains a few
elements of delight:
Interestingly,
adventure games tend to
emphasise delight more than CRPGs or MMORPGs. The Myst
series is a perfect example of this. I'm not sure if it's an accident of
history, or if it's because adventure games are targeted at a different player
personality, one who appreciates delight more than FPS and CRPG players.
Using
the formula to create unique worlds
One
major problem that I have with most CRPGs, MUDs, and MMORPGs is that they're
basically the
same game. They all rely on hit-point based combat.
They all have Elves, Dwarves, and Orcs, or races that are just like Elves,
Dwarves, and Orcs. They are all based in quasi-medieval worlds with an evil
overlord trying to defeat the forces of good. They are all populated by
uninteresting quests that involve killing 10 orcs, collecting 10 orc heads, or
delivering 10 orc sabres. And the few that have epic quests don't use them
properly.
This
problem is being further exacerbated by the plethora of MMORPG development
toolkits coming onto the market. I'm not opposed to
MMORPG (or CRPG) development toolkits; I'm writing my own. I am opposed to
toolkits that only allow players to create more of the same. In order for an
author to not
create more of the same using a toolkit, the
toolkit needs to encourage authors to rewrite large segments of the toolkit's
code. (See below.) Coding is time consuming and requires skill.
Since toolkits proudly boast the ability to create a world in under an hour
without any programming skill, they can't require coding, which means they
usually don't provide authors the tools and incentives to actually create
something unique. What results are thousands
of user-created worlds that are essentially all the same.
What is required to create a unique world?
1.
Eye candy is only one of the ways to
differentiate a world. Contemporary game developers like
emphasising eye candy as a differentiator (to older generations of the genre)
because it's a low-risk solution. Throwing out the combat sub-game and
replacing it with ballroom dancing is just not done; it is a much riskier
proposal that could result in the loss of one's job.
2.
Look at all the bullet points in this article
and figure out a unique
solution to each bullet item. Not all solutions need to be
completely original, but at least a handful of elements should be.
Creating
a world full of Elf look-alikes that are just renamed/retextured versions of
the classics isn't good enough. Likewise, avoid cliche medieval villages and
the same old evil-overlord story.
The
lack of creative solutions to sub-games, settings, quests, quest arcs, and
personal NPCs is exacerbated by B-grade FPSs, CRPGs, and MMORPGs that fail to
include decent quests, quest-arcs, and personal NPCs. (See below.) This only
leaves the designers half the amount of "stuff" to play with, and
thus, less opportunity for differentiation.
3.
Creating unique sub-games or unique
variations on existing sub-games is especially important
because the world's setting, quests, quest arcs, and personal NPCs are constrained
by the game's sub-games. Unique sub-games require heaps of programming,
especially in an eye-candy rich 3D-world.
The
least amount of work required is to creatively modify an existing sub-game,
such as combat. Making a fighting sub-game that renames "swords" to
"katanas" is not going to cut it though. Combat actually needs to be
different. How about hit locations instead of hit points? Or intricate
strategies like those that are used by real fencers? Or melee combat in zero-G?
A
better solution is to demote fighting to a minor sub-game since it's so
overused. Instead, place players in a quasi-medieval city (if you like) where the major sub-game is renting
apartments to NPCs, or painting NPC portraits... Whatever! Just
avoid the cliche of killing hoards of 20 hit-point orcs.
For every major sub-game you add (such as renting
apartments), you'll need to either demote or entirely remove another sub-game.
Every sub-game you include must be learned by the player. If there are too many
sub-games, players will find the learning processes to be too much work,
especially casual players who only expect to play the game for ten to twenty
hours. Games targeted at hard-core players can include more sub-games. A more
casual 15-hour game, such as Beyond Good and Evil, has slightly fewer
sub-games than Oblivion, and all of the sub-games are much easier to
learn.
4.
You may need new technology. Part of the reason why
the same classic sub-games are continually reused is because that's all that
currently technology allows. A game controller only has so many buttons and
knobs, limiting what kind of inputs are possible. The same problem exists for
outputs. The only reason Guitar Hero and Dance! Dance! Revolution
have unique sub-games is because they each include new I/O devices shipped in
the box!
Text-to-speech
and speech recognition are two technologies that enable new sub-games. I
discussed these in The
NPC-conversation wall.
5.
Properly designed quest, quest arcs,
and personal NPCs are required to create unique worlds.
Most novels and television shows are set in the real world, but they still
manage to differentiate themselves from one another. They do this by focusing
on different combinations of plots and characters (aka: personalities); in
avatar games, plots and characters are part of the quests and quest arcs, not
the procedural world.
20 September 2006
by Mike Rozak
Discuss on www.mXac.net/forums
This
article contains some thought experiments involving games, puzzles, toys,
stories, socialisation, Choose-your-own adventures, and stickybeaking; You
never thought they were related, did you?
Games
World of Warcraft and Everquest
II are clearly games.
In both of them, there exists a landscape populated by monsters, non-player
characters, and other players. The
core activity for players is the combat sub-game; its needs
drive the AI and social interaction designs.
Let
me put forth a non-binding definition of a game:
A
game
includes a reality
bound by a set of rules.
Players make choices
and the rules determine the outcome, ultimately resulting in the player winning or losing. In
a game, the degree to how well a player's choices worked result scalar positive or negative feedback.
Players use this feedback to improve
their understanding of the system as well as hone their abilities
(such as manual dexterity). With players' new feedback and skills at hand, players make another choice.
Repeat ad infinitum until
players understand the rules and get bored. (See Raph Koster's
"A Theory of Fun" for more info.)
In
WoW and EQII, players try to kill monsters. The game returns
positive/negative feedback in the form of hit points and other metrics. Players
gradually perfect their combat techniques. To keep the game interesting, variations
are added into the game over time, such as new monsters or new PC abilities.
In
artificial intelligence terms, the
described gameplay sounds an awful lot like neural networks.
Neural networks work by making a choice, seeing how right/wrong the choice was,
and adjusting their model of reality based upon the amount of
positive/negative feedback. Over many thousands of iterations, a neural network
learns to accurately predict the input data.
A
world without games?
What
would happen if gameplay
were removed from WoW and EQII? What would players do?
Both
WoW and EQII are missing
some activities present in other virtual worlds, such as
puzzles and toys:
Puzzles
Neither
WoW nor EQII have any puzzles to solve.
In many ways, puzzles are the opposite to games. In
a puzzle,
players make one choice. The feedback is binary,
either "You got it wrong, try again!" or "You got
it right!". Failure just returns players back to square one, while
success sends players on to their next puzzle. The same puzzle is never reused because it's trivial to
solve the second time around.
Consequently,
the neural-network
approach of learning from the positive/negative feedback doesn't work.
The binary feedback from puzzles, while accurate, is only TRUE or FALSE, and
isn't good enough for such learning. To solve a puzzle, players must understand what the
puzzle is about, and then the solution is easy. Neural networks cannot solve puzzles
at all well. At the moment, the only way that computers can
solve puzzles is to brute-force them and try all possible combinations.
As
I've said before, puzzles
utilise deductive reasoning.
CRPG/MMORPG gameplay is
inductive.
Puzzles don't make terribly great social games
since any players that know the solution have to bite their tongue while the
other players in the group struggle to find the solution. In WoW's
game-based combat, killing the same orc a second time isn't much easier than it
was to kill it the first time.
Toys
(aka: play)
A
toy is a
reality
bound by a set of rules,
some of which are fixed,
and some of which are liberally
added or removed by the player. The player makes decisions and sees
what happens, getting vector
feedback that isn't just success or failure. Based on the
feedback and their own whims, players can make new choices, or they can change the rules
of the toy. Repeat ad
infinitum until the player runs out of choices and/or new rules to invent
and gets bored.
For
example: A frisbee
is a toy. It is bound by
the rules of physics, albeit interesting ones that allow it to fly in ways that it shouldn't.
To the laws of physics, players often add
their own rules of throwing it up in the air and catching it,
or throwing it from person to person, or only being able to catch it in one
hand. (Some of these rules temporarily convert the frisbee from a toy to game.)
During play, players are continually making choices (throwing the frisbee to
different people and in different ways) and changing the rules ("Left
handed catches only!").
Toys are about creativity and experimentation.
Games are about winning.
A
matrix?
I
feel a three-dimensional
matrix coming on:
Goal oriented
|
Inductive / intuitive |
Deductive / logical |
|
|
Learning
(input) |
Game (Tennis, CRPG) |
Puzzle game
(Adventure games, Chess?) |
|
Creation
(output) |
Work (Artist) |
Work (Engineer) |
Experimental
|
Inductive / intuitive |
Deductive / logical |
|
|
Learning
(input) |
Toy (Frisbee) |
Toy (Puzzle) |
|
Creation
(output) |
Toy (Musical
instrument) |
Toy (Legos) |
I'm not entirely happy with the matrix,
particularly the part about goal-oriented creation always being
"work". Also, the matrix implies a hard separation between inductive
and deductive reasoning, learning and creation, and goal oriented vs.
experimental; Many activities cross over. Chess, for example, is a combination of
looking at the board and instantly understanding what's going on (inductive), as well as
trying to understand your opponent's strategy and predict their future moves (deductive).
I
can even go a step further with the matrix: "Stickybeaking" is
about learning fun/interesting
information either by seeing/experiencing
something directly (such as the neighbour's new car) or being told about it (a
salacious rumour). Stickybeaking is neither
inductive nor deductive since it's about acquiring knowledge
rather than understanding it.
As
a side note: The urge to
stickybeak is enhanced by in-game "stories." The
whole point of a story is to make
a narrative as fun and interesting as possible in order to keep
readers/viewers interested in the activities of fictional characters. Stories
accomplish this by using several techniques, such as suspense, likeable
characters, and conflict. The same techniques can encourage stickybeaking.
If
you really squint, "socialisation"
is, in small part, the inverse
of stickybeaking. It includes the creation of rumors (for stickybeaks)
as well as many of the elements
(likeable people and conflict) that stories try to mimic... but
this is a stretch.
Goal oriented
|
Understanding
(Inductive
/ intuitive) |
Knowledge |
Understanding
(Deductive
/ logical) |
|
|
Learning
(input) |
Game (Tennis, CRPG) |
Research |
Puzzle game
(Adventure games, Chess?) |
|
Creation
(output) |
Work (Artist) |
Work (Reporter) |
Work (Engineer) |
Experimental
|
Understanding
(Inductive
/ intuitive) |
Knowledge |
Understanding
(Deductive
/ logical) |
|
|
Learning
(input) |
Toy (Frisbee) |
Stickybeaking |
Toy (Puzzle) |
|
Creation
(output) |
Toy (Musical
instrument) |
Socialisation,
rumors |
Toy (Legos) |
Since
I'm not entirely happy with the matrix, I
don't use the matrix in the rest of this document. I decided to
bring it up, though, because it's an interesting concept to consider.
Beyond
DikuMUDs (WoW and EQII)...
As
I stated above, WoW and EQII are about:
At
the moment, 15+ million
people play game-like MMORPGs.
What other types of worlds are possible?
Second
Life
Second Life is:
Second Life is the dominant
social/creation MMORPG. While nearly a million people have tried Second
Life, only around
100K are active players.
Uru
Live
Uru Live is:
Of
course, Uru Live was cancelled before it was released. It's currently
being revived again, but my suspicion is that it'll have fewer players than Second
Life, perhaps 10K
players. The reason is that puzzles don't encourage socialisation as much as games or
toys.
Hypothesis: Games don't mix well with puzzles
because games attract "achievers",
players that like to "win" and be "ranked" above other
players. Puzzles can be easily "solved" by downloading a game
walkthrough over the Internet. Therefore, puzzles are wasted effort in the eyes of achievers,
the primary audience for multiplayer games... This doesn't mean that puzzles and games
can't be combined, just that they can't be combined with achievers.
World-like
MMORPGs
World-like
MMORPGs (Ultima Online, the old Star Wars Galaxies, and Runescape(?) )
are:
If
Runescape is included as a world-like world, then there are around one million world-like players.
Without Runescape, the number is closer to 400K.
Hypothesis: While world-like
worlds are more popular than Second Life, they are less popular than game-like
worlds. Could this be because (a) games are more popular than toys,
and/or (b) trying to fill a world with too
many different types of activities results in lower quality activities due to
design conflicts? For example: Ultima Online cannot
allow players as much building freedom as Second Life because that
much freedom would break Ultima Online's gameplay.
Hypothesis: World-like worlds
traditionally simulate a world and encourage players to "live" there,
spending 20+ hours a week in the game. Could
world-like worlds actually just be a sub-set of "toy + game" based
worlds, some of which don't
require such a large time commitment?
Chat
room
A
chat room is a world that handles only:
While
chat rooms are popular (way
more than 15 million users), they're "a dime a
dozen".
There's
no particular reason for a player to use one chat room above another, other
than the other people in the chat room. Consequently, some chat rooms include built-in games, toys, puzzles, and
stickybeaking to attract users... which turns them back to a DikuMUD,
Second Life, or Uru Live.
Stickybeak
world
Imagine
a world where all players do is:
Notice
that there aren't any games, puzzles, or toys.
Would this work? I'm not
sure; there aren't any worlds that follow this model. Stickybeaking synergizes
with socialisation, but not
as well as games or toys. Furthermore, producing content for stickybeaking is
fairly expensive since it is quickly consumed; that's why Second
Life has players build their own stickybeak content as part of the
building "toy".
Choose
your own adventure... A stickybeak world?
When
I was a teenager playing the Tunnels & Trolls solitaire adventure, City
of Terrors, I thought about turning it into a computer game. City of
Terrors was basically a Fighting Fantasy book, or Choose-your-own-adventure
book with combat thrown in.
From
time to time, I still consider a computerised CYOA game, but I don't think it'll work.
There are a few reasons:
1.
A CYOA "page" is a cut-scene
followed by a few choices.
Each choice leads to another page. In a T&T solitaire adventure, Fighting
Fantasy book, or CYOA book, about a third of the pages are about movement.
Movement is better
handled by modern game movement, either room-to-room or WASD
free-form movement.
2.
About a third of the pages involve combat or traps. These
are games and puzzles
(as per my definition above). However, combat
and traps are best handled by placing them in rooms/spaces that
player characters move in, not in CYOA pages.
3.
Items (1) and (2) create an experience that's
similar to a CRPG, MMORPG,
or adventure game, except that one-third of gameplay is spent in CYOA "pages",
which amounts to cut scenes followed by choices. The cut-scenes are an incredibly useful
design tool because they enable more interesting NPCs (story) as well as
describing events that the world's physics and animations engine can't
simulate... "Mary sits down daintily and carefully sips at her tea,
saucer held gently in her left hand."
4.
However, most players hate cut scenes because cut
scenes temporarily take
the control away from the players. Technically, games take
control away from players all of the time, but only for a fraction of a second,
a few seconds at worst. Players don't mind that, which (I suspect) means that short cut scenes are acceptable.
However, a CYOA with short cut scenes implies more decisions.
As
devil's advocate, most
game players don't care about the "story" that cut scenes can create,
so they're biased. Many people are enthusiastic about stories... but those people are almost all
watching TV or reading books, not playing games.
5.
More decisions result in more
branching, which costs exponentially more money to develop.
The only saving grace is that each
individual CYOA (text) cut scene and decision list is cheap to develop.
An animated CYOA "book" with frequent branches would be incredibly
expensive.
6.
Having so
many choices
becomes problematical because there are important design rules to follow,
as I list in my choices writeup.
For
example: Many of the choices
in CYOA books lead to dead ends... literally. In a CYOA book,
if players select a dead-end choice, they just flip back to their previous page
and choose something else. In a computer game, players can't flip back; that
would be cheating and isn't allowed by the game's UI. If dead ends are common, players will
just download the walkthrough in order to avoid choosing a dead end.
Once they have the walkthrough in hand, they'll
forgo choosing altogether and just follow its instructions.
7.
Consequently, (almost) all choices must lead to equally
interesting/positive consequences. This results in a lot of branches that never reconnect with the main
branch, which is even more development work.
8.
With so many branches, players will want to replay each
scenario several different times to see what happens in each. What
happens if I decide to go on a date with Marry instead of Jane? This turns
the experience into a stickybeak
game where players
explore possible futures instead of exploring three-dimensional space.
However,
if players do replay scenarios then they're
at least re-using content, reducing per-hour play costs.
9.
Exploring alternate realities is a
strange and obtuse concept for most people. After all,
the last time a player tried the scenario, Mary tragically died in a car
accident after the prom, but she's alive an kicking once again in this new
reality. I suspect that most players will be turned off by the oddity.
An
easier-to-stomach design is to create
template scenarios with replaceable names, so that Mary is
renamed to Alice, and Jane to Betty. However, because all the other
text/animation is the same, players will quickly realise that the same scenario
is being rehashed.
Having
said all that, CYOA pages still have merit. They are, for example, the standard mechanism for NPC
conversations. They may not be enough to base a stickybeak game off of
though.
A
parting thought...
This
has almost nothing to do with puzzles, games, toys, socialisation, and
stickybeaking, but I thought I'd bring it up anyway.
1.
As stated above, CRPG and MMORPG players like inductive
games. Adventure-game players like deductive games.
2.
The preference is probably a result of
the way that players (like to) think. Thus,
CRPG/MMORPG players prefer to use inductive reasoning, while adventure-game
players prefer deductive reasoning.
3.
Game developers/designers that like to
play CRPGs/MMORPGs will try to find jobs in CRPG/MMORPG game companies.
Likewise, adventure-game enthusiasts are more likely to work for adventure-game
companies.
4.
When it comes time to designing a new
CRPG/MMORPG, do
CRPG/MMORPG designers prefer an inductive design? (Model the
game after a previously successful game, but with a few changes.) Do adventure-game designers use
deductive design? (Try to understand, at a fundamental level,
what the player expects and what the technology can deliver, and design from
that.)
While
I've seen plenty of evidence of inductive (evolutionary) designs for
CRPGs/MMORPGs. I don't
see an overwhelming amount of deductive design from adventure games;
they seem to be as evolutionary (not revolutionary) as CRPGs/MMORPGs.
8 December 2006
by Mike Rozak
Discuss on www.mXac.net/forums
This
is not a
review of Neverwinter Nights 2, and isn't intended for people deciding
whether or not they wish to purchase the game. Instead, it is an analysis of some of NWN2's
design. For my other analysis, see:
Warning:
There are some game
spoilers below. Don't read any further if you haven't already
player NWN2 and still plan to play it.
Important
design elements
NWN2 has two design elements that really
stand out:
1.
Cheesy cut-scenes
- While cut scenes are standard for all games, NWN2 uses a low-cost
cut-scene engine that produces cheesy-looking cut-scenes. However, the low
cost-of-production enabled the content designers to include a large number of
cut scenes and associated branching narratives (aka: Choose Your Own
Adventure books).
2.
Personal NPCs
- NWN2 does an excellent job implementing personal NPCs.
Cheesy
cut-scenes
Cut
scenes are standard fare in all games except Tetris. They are
incredibly useful for fleshing out characters and producing sympathetic
goals in players.
Most games have a dedicated team that produces
high-quality eye-candy-laden cut scenes that cost
an awful lot of money. Consequently, they
don't include many cut scenes, and they certainly don't include the cut scenes in
branching narratives because half of the (very expensive) cut
scenes would never be seen by players depending upon which branch they choose.
Neverwinter Nights 2
took a different approach: They include a
toolkit for cut scenes as part of their engine. This toolkit isn't very
powerful, and it doesn't appear that there was a specialised NWN2
cut-scene team, so the cut scenes are very cheesy looking. However, what it
lost in eye candy is gained in power.
Because
NWN2 cut scenes are so cheap and easy to produce, NWN2 includes heaps of cut scenes,
using them to not only create
sympathetic goals, like all cut scenes do, but to create new gameplay.
The
new gameplay is possible because the cut scenes are so cheap they're disposable, and
ultimately mixed in with
branching narratives (in the form of dialogue trees).
Traditional (expensive) cut scenes aren't used with branching narratives
because that would mean that players wouldn't see half of the
expensively-produced animations. (I'll provide some examples of NWN2's
cut scenes later.)
To
top it off, NWN2's
cheesy cut-scenes are dynamic! That means they're customised
depending upon which party members (personal NPCs) are tagging along with the
player. A traditional cut scene can't compete against that.
Personal
NPCs
Awhile
back, I wrote up an article about personal NPCs.
One of my inspirations was Baldur's Gate, a direct ancestor of Neverwinter
Nights 2. Baldur's Gate used personal NPCs as party members.
NWN2 takes the idea of personal NPCs a few
steps further than Baldur's Gate:
Unfortunately, NWN2
party members:
Both of these problems emphasise that there are two
halves to NWN2,
the cut scenes and their branching narrative, and the "kill lots of
monsters" game.
The
tightrope game
Whenever
the player gets into a conversation with a NPC, players are provided a handful of responses, many of
which are different ways of saying the same thing. One response
might be polite, another bold, and a third intimidating.
Many games provide a variety of choices so that players think
they have a choice, but in reality, all the choices end up in exactly the same
place with exactly the same consequences. This is a no-no,
as I pointed out in Choices
3. It's equivalent to asking a player to chose between two
doors, each of which leads to the same room.
NWN2's conversation-tree choices actually
have different effects:
When
combined together, all the conversation-tree dialogue choices create a "tightrope game".
Every time a player is asked to choose, they must weigh up all the outcomes of
the choice.
The
trial
Half
way through the game, the
player is put on trial using a lengthy series of cutscenes. The
trial is a nice invention because:
Unfortunately,
NWN2
designers messed up the fundamental design of the trial in two important ways:
1.
The player doesn't know that the trial
is going to happen and that his actions are going to be judged.
Without such knowledge, the designers are effectively asking the player to
chose between two identical doors, which as I wrote in Choices 3,
is bad design. This problem is somewhat mitigated by that fact that dialogue
choices also have more immediate effects on NPCs and personal NPCs.
2.
If the player wins the trial, the enemy
lawyer asks for trial by combat. And although I haven't verified this, if the
player loses the trial, the player's lawyer asks for trial by combat... Which means that the trial doesn't
affect the outcome of the game and is equivalent to two doors
leading to the same room, another design flaw.
The
castle
Later
in the game, the player is given control over an old castle, another refreshingly-different
sub-game. It is the player's responsibility to:
I
haven't played the castle sub-game to completion, but I can already see a flaw:
The
importance of cheesy cut-scenes and personal NPCs
In
my opinion, if NWN2 didn't have the cheesy cut-scenes and
personal NPCs, it would be a much weaker game, so weak that I
would have stopped playing after only a few hours. Oddly enough, many reviewers didn't seem to notice
either of these design decisions; perhaps they didn't
consciously recognize them, or perhaps they found the traditional "kill
lots of monsters" game to be adequate.
Also
in my opinion, if NWN2 had cut out 75% of the "kill
lots of monsters" content, it would have been a better game.
Not only would the length have been shorter (NWN2 is too long), but
I've killed way too many orcs in previous games for it to be fun any more,
including Neverwinter Nights 1, and Baldur's Gate, and World
of Warcraft, and Everquest II, and Oblivion, and Fable,
and Might and Magic, and Dungeon Siege, and.... and going
back 25 years to Wizardry I and Ultima I.
13 December 2006
by Mike Rozak
Discuss on www.mXac.net/forums
This
article is a follow-up to my Quest design
101 writeup, and attempts to describe how to
design an interactive-fiction title. The same guidelines apply to CRPGs,
MMORPGs, FPSs, and other avatar games.
The
player can change the world in meaningful ways...
By
the time the player "completes" the IF title, they should have
changed the world in a "meaningful" way:
The
world changes the player in meaningful ways...
The
world should change the player, not just the player's character:
Players
can change and be changed by other players...
If
the interactive fiction title is multiplayer, then:
Change,
in general
Not
all players are interested in the same amount and type of change. Using a distortion
of Richard Bartle's player models:
Immersion
The
world should be immersive:
2 January 2007
by Mike Rozak
Discuss on www.mXac.net/forums
Recently,
I've been pondering methodologies
used to design virtual worlds (and avatar games in general).
I've seen the following methodologies used in games:
I
happen to like stories, but I
don't like the way they're handled by any of the existing methodologies.
Myst-like and linear-story games are very limiting as far as choice. The
spaghetti (Oblivion)
methodology creates a lot of freedom, but ends up being a homogonous
entanglement of quests. Diku-MMORPGs only have token stories. Pre-Diku text
MUDs and world-like worlds don't need any story whatsoever.
Here's
an alternative that I'm currently thinking about:
Stories/quests
with meaningful and reconnecting choices
For
the moment, I'll just discuss quests (sub-stories), but they'll tie into the
quest-arc(s) that control(s) the game's overall story.
To
produce a quest:
1.
The author writes a story that the player will participate
in, much
like a game based on a linear story.
2.
Unlike a linear game, each quest includes meaningful
choices that allow for branches within the story
that ultimately change the story's outcome, just like Choose Your Own
Adventure books. Unfortunately, meaningful
choices are a lot of work, particularly if players are offered
several meaningful choices in a row. If each meaningful choice has two
branches, and there are three meaningful choices throughout the quest/story,
then there are 2 ^ 3 = 8 outcomes. With four choices, there are 2 ^ 4 = 16
outcomes! Thus, don't
expect stories/quests to have any more than two or three meaningful choices.
3.
Choices that reconnect don't produce
as many branches, such as the choice of travelling to
Inviroth by land or
by sea. Each choice produces a different experience, but ends up back at the
same node. Consequently, quests/stories may include several reconnecting choices early on,
before there are too many branches (a consequence of meaningful choices).
If
a quest/story only includes two meaningful choices, then each quest would have a beginning,
middle, and end. The beginning only has one alternative (or
branch). A choice is made at the transition between the beginning and middle.
There are two alternatives/branches for the middle part of the quest. Another
choice is made at the transition between the middle and end, creating four
alternatives/branches for the end-quest.
Add
in a few reconnecting
choices at the beginning and middle of the quest/story, but not
the end, and the author then has to write two beginning branches, four middle
branches, and four end branches (with no reconnecting choices). That's a total
of ten branches.
A
quest with no middle would only have four branches, two reconnecting choices at
the start, and two meaningful choices at the end.
Procedural
choices
For
some reason, Choose Your Own Adventure books aren't very satisfying
(to me). I've spent a long time trying to figure out why, and (I think) I
finally have the answer:
Players like to make frequent choices, as often as one
choice every few seconds. It's part of what makes the world immersive.
CYOA books
"fail" because players only make infrequent choices;
they spend a minute reading a page, make a choice, spend another minute
reading, make a second choice, etc.
Theoretically,
if a CYOA book could offer me a choice every sentence or two (minus
the hassle of reading the choices and flipping to the next page), the
experience would be quite compelling. It would also result in several
quintillion branches for the author to write!
Procedural
choices (managed by software algorithms) can
easily offer players frequent choices.
Unfortunately, procedural choices tend to be shallow and less interesting than
the hand-created choices in CYOA books.
The
solution (or rather, a
solution) is to fill in the time between meaningful choices (CYOA-book
choices) with procedural choices. Procedural choices might take the following
forms:
Some
elegant uses for
procedural choices are:
The
three-act game
The
three-act story is well known. There's a beginning,
middle, and end, each with distinct boundaries and each act
having a different theme. In the Hero's Journey, for example, the first act is
about the character's "call to duty", followed by trials in a distant
land, and completed by the journey home with the boon.
Most games with linear stories have three acts.
Even some MMORPGs have three acts; for example: World of Warcraft's
first act is about the player doing quests for their village and race/clan (at
the request of NPCs), the second act is hoarde vs. alliance (often at the
request of NPCs), and the third is guild vs. guild.
Basically:
Not
coincidentally, quests
(as I described them) have three acts, with a meaningful choice
between each act. Thus, the
entire game can be seen as one large three-act quest. Or, it
can be seen as a fractal-like construct with three-act quests within three-act
quests.
Tailoring
quests to each act's theme
As
I've written before, players
should have (as much as possible) a choice about which quests to undertake, and
a choice about what order to undertake them.
Oblivion does an
excellent job providing such choices, but at a
cost. While players can undertake (almost) any Oblivion quest at any
time, Oblivion
only has one act. It can't have more than one act because it
doesn't place limits on what order players complete the quests.
At
the other extreme, completely
linear games like Syberia have three (or more) acts, but no
choice about which quest to undertake.
In
the middle is World of Warcraft. Quests are level-limited, so a player character's level (aka:
progress through Act I, II, and III) affects what quests can be taken.
To get to the next level, a player must complete (to pick a random number) 20
quests. Since WoW provides 40 quests per level, players can choose
which quests they wish to complete, and in which order (to an extent).
Personally,
I'm interested in
producing a game without levels. It will have skills, but the
difference in power between a low-skilled and high-skilled character is
minimal. (Skills are actually used as a time-based resource, but that's for
another article.) Thus, I
can't use levels to limit access to quests, much the same
limitation as Oblivion, which automatically adjusts all quests to suit
the PC's level.
However,
I don't want Oblivion's
homogonous mass of intertwined quests (aka: spaghetti). I
actually want to have acts.
My
current solution is:
1.
Some quests will be act-specific,
and won't be given to players unless they're in the proper act. (Again, similar
to WoW.)
2.
Some quests will change depending upon
the player's act number. Ultimately, this means that some
quests will require three thematically different versions, one for each act.
The version handed out depends upon the what act the player is in when they
accept the quest.
3.
Players won't have to complete all of
the available quests for an act. As in WoW,
they'll have a choice of quests.
4.
The game's story allows a transition
from Act N to Act N+1 only when enough quests from Act N have been completed
(much like WoW). In order to complete the transition, the player will need to finish a
transition-specific quest, such as a travelling quest, from the
hub-town for Act I to the hub-town for Act II.
5.
Taking a page from Oblivion, there will be one main quest-arc that
progresses through all of the acts, and it (potentially)
includes sub-quests that
cause the transition between acts. These sub-quests won't be
available unless enough act-specific quests are completed (as above.)
6.
There may be a few secondary quest-arcs, also like Oblivion.
7.
There may be many one-off quests, like Oblivion.
Storylines
(large games)
Although
I don't expect to have the resources for this, a large game might include
several different storylines.
Each storyline is the
equivalent of a three act game, but they share the same world.
For example: One storyline could be "default the evil overlord" while
another might be "become the mayor of the city".
Filling
the world with multiple storylines has the advantage that players are never
entirely sure what the motivations of other players are because they might be
on a different storyline.
To
reduce development expenses, most quest-arcs and quests would be shared amongst
all storylines.
10 January 2007
by Mike Rozak
Discuss on www.mXac.net/forums
In
this article I will revisit a way to visualise choice
and use the discussion to show how "intuition"
can be a useful device.
Choice
space, part 1
Imagine,
if you will, a multidimensional
volume of "choice space" that covers all the possible
choices that players can make. Since visualising a highly-dimensional volume is
too complex, try to imagine
a piece of paper. X (left and right) represents choices, and Y
(up and down) represents time.
1.
A story is a line,
since players have no choices. It starts at the bottom of the page (the past)
and works its way up. It may make jogs to the left or right, but it's still a
line.
2.
A "Choose
Your Own Adventure" book, with
a set of choices, is a tree. It starts at the bottom of the page
with the first page of the CYOA book. At each choice is a node with
branches to the left or right. Technically, CYOA books aren't trees
because branches sometimes recombine, but a tree is an easier concept to
envision.
3.
If there are sub-games between the
choices (aka: procedural
choices), then the line segment between each
choice becomes fuzzy, representing all the possible ways
that individual players could complete each sub-game. If players always start
and finish at a well-defined CYOA-choice then the fuzzy lines refocus
into a point at the choices.
4.
If the procedural-choices are used to
determine the CYOA choices, then the fuzzy lines DON'T refocus to a choice-dot,
and remain fuzzy even at branches. For
example: If the player's choice of whether to be good or evil is actually
determined by their procedural choices, then the choice node isn't a dot, but a
fuzzy ball.
Choice
space, part 2
Instead
of viewing choices as a series of fuzzy lines, imagine that the two-dimensional sheet has a third
dimension of topography added. Lines turn into steep gorges, while fuzzy areas
are valleys.
However,
in my current description, even
the valleys are walled by steep cliffs, preventing players from climbing out.
What
I describe is the typical configuration for contemporary adventure games and
(most) CRPGs. Basically, players
can wander around in the valleys, completing quests, but are prevented from
ever climbing out of the valleys. For example: Players cannot
decide to join the evil overlord, much less become his hairdresser.
Some choices are just not allowed:
1.
Some choices aren't possible due to limitations
in the programming of the world's physics, such as
becoming the evil overlord's hairdresser. There is no hairdressing skill, and
only a few unstylish 3D hairstyles like Mohawks and mullets.
Adventure
games, which tend to have exceptions-based
physics, commonly prevent any activities except
those that advance the plot because such activities haven't been programmed in.
2.
Some choices will be be prevented by
other design assumptions; even if the world allowed hair
styling, the author may have pre-programmed the evil overlord to always be
antagonistic towards the player character... meaning that he'd never show up
for his hair appointment.
3.
Some choices haven't been developed
because the author doesn't think players would be interested in them.
The evil overlord might show up for his hair styling, sit patiently in the
chair, and even pay the character, but nothing more would come of it; he'd be
just another customer.
4.
Some choices are developed, but not as
well as they could be. The overlord may become a regular
customer, but the overall hair-stylist quest/plot is fairly boring. You can
think of these as short
valleys. The overlord might get a few haircuts throughout the
game, but his hair wouldn't undergo a character arc or anything story-like.
5.
Some choices are disallowed because
they would ruin the game's story and/or some pre-programmed quests.
See below...
Problems
with total freedom
Some choices are disallowed because they would ruin the
game's story and/or some pre-programmed quests.
For example, the right haircut might make the evil overlord so happy that he
stops his evil ways. Or, to get away from the hairdressing motif, what would
happen if a player in a Lord of the Rings CRPG is given the one-ring
for safe keeping but decides to flush it down the toilet? Game over.
In
other words, if a game
lets a player climb out of the valley then the player might not be allowed back
in. Once the one ring is out of the player's hands, there's
nothing the player can do except slay orcs. All the work that the authors spent
digging out the valley to Mordor is lost on the player.
Given a choice, players will always (accidentally) climb
out of the valley. Don't forget, they're wandering around
in a thick fog and can't see where they're going. Even if they don't want to
break the story by leaving the valley, they will do so.
Adventure
games and CRPGs solve this problem in the following ways:
Intuition
There
is another possibility, but it breaks the fourth wall!
If a player does something that would ruin the plot (or
even a minor quest), warn them. Display a message,
"Flushing the one ring down the toilet is a bad idea. Do you still
want to pull the lever?"
Providing
players with "intuition" warning has the following benefits:
1.
Players can do whatever they want
(so long as the world's physics allow for it). This means that they aren't
restricted (barring limits to the world's physics). In other words, they can
climb out of the valley.
2.
A player that is told, "You can't
flush the one-ring down the toilet," gets frustrated. If they get an
intuition warning instead, they may be annoyed for having the fourth wall being
violated, but they now
know: "I can't flush the ring down the toilet for
my own good."
3.
Despite the warning, players can still
take quest-damaging actions. Oddly enough, this provides them with
another choice; they can choose to rebel.
"Intuition"
is commonly used in MMORPGs, but in a different form. Monster names are coloured based on
their difficulty, essentially telling the player, "You
have a bad feeling about attacking that monster."
The
plot / valley thickens
The
topology analogy has some ramifications for a game's design:
10 January 2007
by Mike Rozak
Discuss on www.mXac.net/forums
For
awhile now, I've been trying to identify a fundamental "equation"
that can represent many (or most?) of the design problems found when creating a
virtual world. This article is my latest attempt. It focuses on the tradeoff between story and choice.
The
prescient-author thought-experiment
Like
Maxwell's demon, I'll begin my discussion with an incredibly
improbable / impossible thought-experiment.
Imagine
that an author is creating an interactive fiction title. The author can see the future, happens
to know exactly what choices a specific player will make long before the IF
title has even been started. This prescience saves the author a
lot of time and work because the
author only has to write a piece of linear fiction.
Because
the author has (potentially) years to design and implement the game (which ends
up being just a story), the game can even include movie-quality eye candy.
Once
the title is completed, the chosen player is placed in front of the computer,
given a mouse and keyboard, and chooses away. However, all the player's choices are
effectively ignored during "gameplay" because the
author knew exactly what they would be before hand, and programmed them in.
Regardless, the player
walks away from the "game" vowing that it's the best one that he's
ever played (assuming that the author is any good).
Let
me start "the equation" here.
E(c) = f(author's
skill, eye candy)
In
English, the player's
enjoyment of the game's choice, E(c), resulting from a given choice, c,
(and its subsequent story/experience) is a function of:
The
"game-master behind the curtain" thought-experiment
Prescient
authors are hard to come by. However, face-to-face RPG game masters (or
"dungeon masters") are more common.
Instead
of a prescient author creating a movie ahead of time, imagine a game master sitting at the
other end of a network and acting as the IF title's brains.
This game master is, of course, incredibly skilled, and can parse the player's
inputs so blindingly fast that the player thinks they're interacting directly
with a computer. Furthermore, the game master can create new content in the
blink of an eye.
Even
if the game-master's authoring skill and eye-candy ability are the same as the
prescient author's, the player's experience won't be as good:
Thus,
E(c) is added to:
E(c) = f(author's
skill, eye candy, knowing what the player will do ahead of time, enjoyment
synergy, real-time restraints, published tie-ins, other players)
Outsourcing
the game-master with AI
Game
masters are expensive, being paid at least minimum wage. Therefore, the bean
counters will get rid of them, replacing
game masters with immigrant AI labour. AI gets paid very little
and doesn't require benefits or holidays.
Unfortunately,
contemporary AI has plenty of flaws compared to a game-master:
E(c) is expanded once again:
E(c) = f(author's
skill, eye candy, ..., a compelling story, etcetera)
E(c) rules of
thumb
To
look at E(c) another way, the following factors affect E(c):
You
can't do that!
Unfortunately,
not every action that a
player chooses to undertake can actually be acted upon. Character limitations aside,
players encounter the following problems:
This
is an important part of "the equation":
P(y|d) = f(no
content, world's physics, guess the verb, adversely affect other players, break
major or minor quests, not really a choice)
In
English, the probability
of encountering a "You can't do that!" (or similar) situation,
y, given a decision, d, is a function of how much content there is, how robust
the world's physics are, etc.
Just
to clarify, players will also be told, "You can't do that!"
when they try have their human character (or pig) fly, try to walk through
walls, or leap tall buildings in a single bound. This sort of failure isn't an
issue for P(y|d) as long as players accept it as part of the reality
of the world.
The
annoyance factor
When players choose an action that isn't allowed, they
get frustrated and annoyed, and they don't
enjoy the game as much.
How
annoyed a player gets depends on a few factors:
The
annoyance factor produces another term:
A(c') = f(expect
game to handle choice, why the choice didn't work)
In
English, the annoyance factor, A(), given a failed choice, c', is a
function of the player's expectation that the game should handle the choice,
etc.
P(y|d)A(c') rules
of thumb
To
look at P(Y|d)A(c') another way, the following factors affect
P(Y|d)A(c'):
First
pass at the equation
My
first pass at the "interactive
fiction equation" is:
E = Sd
(E(c)
- P(y|d)
A(c'))
I(d)
P(d)
In
English, the player's overall enjoyment of the interactive-fiction title is the
sum, over all decisions, d, of the enjoyment from the individual choice that's
finally accepted, E(c), minus the probability of the player's choice being
invalid times the annoyance that results.
Both
the choice-specific enjoyment and
annoyance are scaled by I(d), which is a
function that indicates how important the player views a decision. (See the
"leaf picking" example above.) The are also scaled by P(d), which is the
probability that the player will encounter the decision. Thus, the more
branches in the game, the lower the probability that the player will encounter
the specific decision.
The
equation is busted
From
the start, let me emphasise that the equation is busted. It's only an
approximation. Here are some of the more obvious flaws:
The
less-busted equation is:
E = Sd
( E(c)
- P(y|d)
A(c')
- A(c, t) + ki
) I(d)
P(d)
Interpreting
the equation
The
obvious use for the equation is to maximise
E for all (paying) players, limited by the funds and technology
that the interactive fiction title has available. This means:
Some
specific conclusions about E(c), how enjoyable the consequences of a
choice are:
About
P(y|d), players getting "You can't do that!"
messages:
About
A(c'), players being annoyed by "You can't do that!"
messages:
About
A(c, t), players being annoyed by long cut scenes:
About
ki, immersion from
choice:
About
I(d), decisions that the player perceives as important:
About
P(d), common decisions:
12 January 2007
by Mike Rozak
Discuss on www.mXac.net/forums
I
thought I'd spend some time discussing how I think interactive fiction, games, world-like
worlds (like Ultima Online), creation-worlds (like Second
Life), and
role-playing intensive worlds (RPI) relate to one another.
Games
as a base, adding interactive fiction
Imagine
a game, like a fighting game where players stay in one room and kill hoards of
aliens that mindlessly attack. This game is the basis for CRPGs and MMORPGs.
Similarly, puzzles are the basis for adventure games.
However,
to make the game more fun and meaningful, authors try to create sympathetic
goals, which
explain to the player why they're killing all those monsters or solving all
those puzzles. These goals inevitably take the form of a short
story explaining that hoards of aliens are trying to invade earth, or something
equally cheesy.
When
the game is varied to keep the player from getting bored (see sub-game
variation) the story is also expanded; Perhaps the
player defeated the first wave of aliens, but new (and more powerful) aliens
have arrived. They look different and use more powerful weapons and tactics.
At
some point, the story becomes prominent enough that players try to change the
story through their gameplay. Perhaps they can kidnap the alien commander and
stop the fighting now, or perhaps the war has all been a huge misunderstanding.
As
soon as players want to change the story, which was originally added to simply
answer "why", they are entering the realm of interactive
fiction.
The
transition to IF isn't binary. Some "games" can be 100% interactive
fiction, while others are still games, but with a heavy emphasis on story.
Starting
with interactive fiction, and adding games
Approaching
the problem from the other direction, imagine beginning with a Choose Your
Own Adventure, which is pure interactive fiction. (CYOA books
have no games or puzzles.)
However,
games and puzzles are easily added. They serve a few purposes:
Games
and world-like worlds
Multiplayer
games have another direction they can take:
1.
Instead of adding stories (interactive
fiction) to games, multiplayer
games can encourage players to compete.
2.
Competing players will team up.
3.
Teamed up players form social bonds.
4.
Players with social bonds tend to stay
in the same game longer, 500+ hours.
5.
Players that stick with the same game tend to
ask for new and varied features. Many
(or most) of these features have social ramifications,
emphasising social status and meeting other players.
These
social features are commonly known to any Ultima Online player:
The more world-like a game gets, the more difficult it is
to add interactive fiction components. While a
world-like world may include quests, they're typically not central because (a)
they're not needed by players who enjoy the world-like component, and (b) the
players' ability to modify the world (with housing and politics) often
conflicts with the author's pre-programmed IF content, and (c) creating an IF
title that lasts 500 hours is simply too expensive.
Creation
worlds
Clothing, player housing, in-game crafting and trade, and
in-game government can take over the virtual world, forming a "creation
world", like Second Life.
The
fun of a creation world comes from creating new objects and seeing what other
players have created, not from playing the game. Additionally, as developers
expand the players' ability to create, they find that it unbalances the game to
the point where the game is no longer playable, and it's discarded.
Creation
worlds don't blend well with interactive fiction either; players simply have
too much power.
A
pattern? Genres?
These
four types of worlds seem to form a pattern:
|
Interactive fiction |
Game-like worlds |
World-like worlds |
Creation worlds |
A "game" world can be heavily weighted towards
one of the four types of worlds, or it can straddle two of them.
Or, it can be centred on
one genre, and steal a small bit from genres to the left and
right; World of Warcraft is a game-like world that includes quests
(tiny pieces of interactive fiction) and guilds (part of a world-like world's
features).
It
doesn't seem like a game can get much broader though; a world-like world cannot
have interactive fiction in it (except in very limited form), and a creation
world cannot be a game (although it can contain games in their own magic circle
within the world).
Notice
how the genres forms a linear sequence, going from "The author has all
the power" to "Players have all the power."
Role-playing
intensive worlds (RPI)
Some
virtual worlds place a heavy emphasis on role playing. I'm not sure where they fit in
relation to the other four genres of worlds. They can be based
on game-like, world-like, and creation worlds, but they're often modified with
extra features to encourage role playing. For example: If combat is supported
in the game's "physics", it often has features that allow players to
supersede the combat rules in order to maximise role-play.
21 January 2007
by Mike Rozak
Discuss on www.mXac.net/forums
This
is a second instalment to my Interactive
fiction equation article. If you haven't read it yet,
then do so now, or you'll be completely lost.
Note:
Throughout this document I make a
lot of simplifying assumptions about the equation and its
terms. In the long run, many (or most) of these assumptions may prove to be
wrong.
Summary
The
final equation I came up with last time was:
E = Sd
( E(c)
- P(y|d)
A(c')
- A(c, t) + ki
) I(d)
P(d)
Writing
a more-specific E(c)
In
my last article, I glossed over the equation for E(c), and just
provided a verbal explanation about how it described the enjoyment that a
player got from the consequences of a choice, c.
Here's
an actual equation for E(c):
E(c) = (Ec
+ kx
(Ec
- Exc))
· kw
The
terms are:
In
English, this equation says:
A player's enjoyment of a choice is affected by the
consequences of the choice, Ec,
what the player thought the consequences would be, Exc,
and the player's personality, kx
and kw.
The
new E(c) presents some interesting (and obvious) conclusions:
Writing
a more-specific A(c')
A(c') is a function that indicates how
annoyed a player gets when their first choice isn't handled by the game. As
with E(c), I didn't write an equation for A(c') in my
previous article.
However,
this time around, I have an equation:
A(c') = (kx
(Exc'
- Exc)
· kw
+ ka)
Xc'
The
new terms are:
In
English:
A player's annoyance at getting a "You
can't do that" message for their first choice,
c', is equal to the difference in enjoyment between the expected experience for
the first (failed) choice, c', and the expected experience for the second
(accepted) choice, c. This difference is kx
(Exc'
- Exc)
· kw.
Add an additional constant, ka,
that players experience whenever they're told "You
can't do that", and scale by the player's
expectations of actually being able to act, Xc'.
Some
interesting observations:
A
new A(c, t)
A(c, t) was added to approximate how
annoyed players got while watching a cut scene. Theoretically, it is based on a
sum of A(c')'s caused when the player is watching the cutscene, gets
annoyed with the direction it takes, and wants to make a choice, but can't.
Every time a player wants to do something that the cutscene won't allow,
another A(c') penalty is accumulated.
This
relationship between A(c, t) and A(c') allows me to produce a slightly better
cutscene fudge-factor:
A(c, t) = (kx
(Cd
· kw)
+ ka
t) Xcs
The
new terms are:
In
English:
A player's annoyance with a cutscene is a function of how
much the cutscene's expected enjoyment diverges from the cutscene's expected
enjoyment if the player could make choices during the cutscene, kx
(Cd
· kw),
and how long the player has to sit around an not be allowed to make choices, ka
t.
Some
observations:
The
expanded equation
If
I insert the new terms, I get:
E = Sd
( E(c)
- P(y|d)
A(c')
- A(c, t) + ki
) I(d)
P(d)
E
= Sd (
(Ec
+ kx (Ec
- Exc)) · kw
-
P(y|d) (kx (Exc'
- Exc) · kw
+ ka) Xc'
-
(kx (Cd
· kw) + ka
t) Xcs
+
ki
)
I(d) P(d)
E
= Sd (
(Ec
· kw) + kx
(Ec · kw)
- kx (Exc
· kw)
-
P(y|d) Xc' kx
(Exc' · kw)
+ P(y|d) Xc' kx
(Exc · kw)
· kw - P(y|d)
Xc' ka
-
Xcs kx
(Cd · kw)
- Xcs ka
t
+
ki
)
I(d) P(d)
E
= Sd (
Ec
· kw
+
(kx Ec
- kx Exc
- P(y|d) Xc' kx
Exc' + P(y|d)
Xc' kx
Exc - Xcs
kx Cd)
· kw
-
P(y|d) Xc' ka
- Xcs ka
t
+
ki
)
I(d) P(d)
E
= Sd (
Ec
· kw
+
kx (Ec
- Exc - P(y|d)
Xc' Exc'
+ P(y|d) Xc' Exc
- Xcs Cd)
· kw
-
ka (P(y|d)
Xc' + Xcs
t)
+
ki
)
I(d) P(d)
E = Sd
(
Ec
· kw
+ kx
(Ec
- Exc
- Xc'
P(y|d)
(Exc'
- Exc)
- Xcs
Cd)
· kw
- ka
(Xc'
P(y|d)
+ Xcs
t)
+ ki
) I(d)
P(d)
In
English:
The
enjoyment from a game is the sum over all decisions of the following factors, all
scaled by how important the player thinks the decision is, along with the
probability of encountering the decision.
TV,
movies, and books are just one long cutscene
Television,
movies, and books can be thought of as a game with one long cutscene.
With this assumption,
the equation for linear fiction becomes:
E
= Sd (
Ec
· kw
+
kx (Ec
- Exc - Xc'
P(y|d) (Exc' - Exc)
- Xcs Cd)
· kw
-
ka (Xc'
P(y|d) + Xcs
t)
+
ki
)
I(d) P(d)
Xc'
= Xcs, since Xc'
can only go as low as Xcs.
Basically, the player doesn't expect to make choices in linear fiction, but
will still be slightly annoyed at the inability to do so.
I(d) = 1, for the sake of
simplification.
P(d) = 1, since there's only one
decision.
P(y|d) = 1, since any choices the player
makes will always result in "You can't do that."
ki
= 0, since there are no choices.
Cd
= (Exc' - Exc),
since the cutscene and the choice are the same.
E
= (
Ec
· kw
+
kx(Ec
- Exc - Xcs
1.0 (Exc'
- Exc) - Xcs
(Exc' - Exc))
· kw
-
ka (Xcs1.0
+ Xcs t)
+
0
)
1.0 1.0
E =
Ec
· kw
+ kx
((Ec
- Exc)
- 2 Xcs
(Exc'
- Exc)
) · kw
- ka
Xcs
(1 + t)
Although
this is a somewhat silly equation, it provides some useful conclusions:
An
important observation,
people that like TV, movies, and books will tend to have:
Real
life
Using
the same equation to describe
real life, I get:
E
= Sd (
Ec
· kw
+
kx (Ec
- Exc - Xc'
P(y|d) (Exc' - Exc)
- Xcs Cd)
· kw
-
ka (Xc'
P(y|d) + Xcs
t)
+
ki
)
I(d) P(d)
P(y|d) = 0, since there are no
artificial limitations.
Cd
= 0, since real life doesn't have any non-interactive cutscenes.
t
= 0, since real life doesn't have any non-interactive cutscenes.
E
= Sd (
Ec
· kw
+
kx(Ec
- Exc - Xc'
0 (Exc' - Exc)
- Xcs 0) · kw
-
ka (Xc'
0 + Xcs
0)
+
ki
)
I(d) P(d)
E = Sd
(
Ec
· kw
+ kx
(Ec
- Exc)
· kw
+ ki
) I(d)
P(d)
The
equation says:
People that like real life have the following personality
traits:
Gamers
People
that enjoy real life, live in real life. People that enjoy TV (and movies and
books) spend their time watching TV. The rest are often drawn to computer
games, some of them to interactive fiction.
Therefore,
a successful interactive
fiction title/game should target the following personalities:
Boiling
this down even further, hard-core
game players:
It's nice to see that the interactive-fiction equation
agrees with conventional wisdom! ;-)
26 January 2007
by Mike Rozak
Discuss on www.mXac.net/forums
A
few years ago, I had an E-mail discussion about personal virtual worlds (PVWs). The general
idea is that if virtual
worlds were easy (low-skilled) to create, and cheap to host,
then millions of people would create their own virtual worlds, just like
millions of people have their own blogs, MySpace pages, web sites,
etc. PVWs would be a
form of self-expression, just like blogs.
Personal
virtual worlds "won't work"
I wasn't too keen on PVWs for the following reasons:
1.
Inevitably, all "development kits", including virtual world
development kits, encounter a tradeoff between ease-of-authoring and
flexibility. In order to make virtual world creation so easy
that millions of people could create their own worlds, the toolkit would have
to limit the variety of worlds that could be created.
In
less abstract terms, a
virtual world creation toolkit that's easy enough for millions of people to use
will end up being a virtual dollhouse; players will be able to
position stock objects around their world, and that's about it. No custom 3D
objects, other than colours selected from a palette. And the stuff that really
brings a world alive, scripting, will be non-existent because scripting is an
uncommon skill.
2.
Virtual dollhouses might be fun for the
authors to create, but they're
not very entertaining for players, other than a quick look
around to see what garish colour combination the author managed to invent. By the tenth such dollhouse, players
will give up and never visit a virtual world created by "Easy-to-use
virtual world creator"
again.
3.
Chatting with other players might be
fun. Unfortunately, with millions of worlds,
players would be so thinly scattered that they wouldn't produce a critical mass. A
player would log onto a world, see that it's empty, and immediately log out.
One minute later, a different player would do the same. As far as the players
are concerned, the millions of worlds might as well be single-player
"games", but they're not even games; they're just empty chat rooms.
4.
With a slightly more complex toolkit, and fewer authors who could drive it,
authors could create games
instead of chat rooms. However, since the easy-to-use toolkit
wouldn't require scripting, all the games would end up being exactly the same.
With
only a hundred professionally-created MMORPGs in the world, an obvious and
now-cliche Diku-game
has already emerged. A
million Diku PVWs would only be worse because they'd be exactly the same game,
not just strikingly similar games. After the 10th Diku PVW, players would get
bored and swear off playing in worlds created by "Easy-to-use Diku
creator".
5.
Of course, the toolkit could allow for more complex gameplay and
authoring choices via scripting, resulting in something like Neverwinter
Nights (I or
II). Instead of millions of dollhouse PVWs, however, the
toolkit's complexity would limit the target market to only a few thousand
authors. Unfortunately, NWN authors can't alter the fundamental game
logic/programming, so the thousand worlds would still be very similar. NWN worlds do attract players though,
even if the majority remain empty.
6.
Text MUDs go a step beyond NWN
worlds; authors have access to the MUD source code (even more complicated than
scripting) and can change everything about the game.
Over the last fifteen years, www.mudconnect.com
has amassed a list of 1700 MUDs, many of them defunct, and most of them
player-less. And they're almost
all clones based on Diku-MUD!
MUD clones exist because customising a MUD is too much
work. Authors download the source code with
accompanying "default" content of 2000+ rooms. They quickly realise
that any change they can
make to the MUD's 10 man-years of code and content is minimal, unless they're
able to commit 10 man-years themselves. So, with no other
choice, the defeated authors rename "Orcs" to "Sporks" and
"Elves" to "Ethereal Beings", add a few hundred rooms to
the 2000 stock rooms, and mis-label their MUD as "completely
original" (since no one will try a MUD that's clearly marked as a
"clone").
Some
fundamental reasons why PVWs don't work
Let
me rephrase some of what I stated above:
1.
It can be fun for an author to create
a personal virtual world, even if all the author does is move
virtual furniture around.
2.
Creating a virtual world that is
interesting to players, however, requires 3D modelling skills, audio skills,
and most importantly, programming skills. (Not to
mention creativity, storytelling, and whatnot.) If an author doesn't have such
skills then they can still have fun creating the world, but it's unlikely that
players will stick around.
To
play devil's advocate, I'll
point out blogs, which as easy to write, attract readers:
Millions of people write blogs, and at least 10,000 blogs are actually read.
Blogs aren't difficult to write. They don't require any ultra-complex modellers
or programming languages, but they still manage attract readers/players. Why
shouldn't the same hold for PVW toolkits? Why can't a PVW toolkit be as easy to
use as a word processor?
Blogs
actually require enormous skill to write. The reason blogs are so easy to create (relative to PVWs)
is that much of your education was devoted to learning how to write,
not to mention all the conversations you have every day of your life. Very
little, if any, of your education included 3D modelling, programming, and
creating audio files. If you were lucky, you took a few art and music classes
in elementary school. If
your education emphasised 3D modelling and programming as much as it did
writing, creating amateur virtual worlds would be a piece of cake.
Continuing
the blog analogy: A
virtual world development toolkit that doesn't require modelling and
programming is like a blog server that only lets bloggers choose paragraphs out
of a standard library; actually choosing individual sentences,
or (God forbid!) individual words and forming unique sentences, is too
complicated for illiterate people. (Notice that bloggers aren't forced to
create their own fonts, though!)
3.
The more PVWs that exist, the lower the
player density in the worlds. Low
player densities turn the worlds into single-player games... that occasionally
contain other players. If a PVW's design assumes that other
players create most of the fun, then an empty PVW won't be much fun, which
creates a self-reinforcing feedback loop when players log on, see no one
around, and then immediately log out.
4.
Text MUDs, in particular, try to be enormous,
some claiming to be as large as 20,000 rooms. I suppose that larger worlds
attract more players to try the world. However, huge worlds are problematic:
o They
reduce the player
density still further.
o They
take more time for players to complete, so they're not accessible to the 75% of the
population that works, and/or has children, and/or has a life.
o An
emphasis on "huge" causes authors to reduce quality in favour of quantity...
but quantity isn't needed by PVWs because there will hypothetically be
thousands (or millions) of them, most of dubious quality merely because their
authors aren't skilled enough. Inducing a further decline in quality by
encouraging quantity is merely another nail in PVWs' coffin.
5.
Players' expectations are high. After having played in a world that
took 300 man-years to create, a text MUD that took 10 man-years is, for the
most part, unappealing.
Beginning
with a 10 man-year text
MUD as a base, an amateur author could hypothetically devote one man-year to
changing and customising the code and content, and numerically create an
experience that is 10%
different from the original code/content. Users would perceive
the difference between the new MUD and its ancestor to be greater than 10%,
though. I guesstimate that a
10% real change will be perceived as a 20% difference. 20% is
on the threshold of being different-enough that players who played the original
MUD will want to try the modified one. (The exact number isn't important here;
You can come up whatever scaling and threshold makes sense to you.)
However,
only 50,000 - 200,000 people play text MUDs; There isn't enough eye candy to
attract a larger audience. 20 million (to use a round number) play 300 man-year
eye-candy-laden MMORPGs.
If
a player suddenly got a hold of WoW's source code and models, and
devoted a man-year to customising it, the game would still be 99.7% WoW!
Even doubling this value to simulate how the changes are perceived, after one year's work, the game would
be perceived as 99.4% WoW... which means that
the experience would be almost exactly the same. To create a "new"
experience from a WoW base, a team would require 30 man-years (10% of
300) of customisation... hardly an amateur endeavour.
The
solution lies in stating the problem
If
I invert the problems, a
possible solution reveals itself:
1.
Create a virtual world toolkit that
encourages quality over
quantity. If the world takes more than six hours to play through, it's probably too
long.
Side
note: The more years I spend thinking about amateur virtual worlds, the shorter
I think they should be. A few years ago I anticipated a 50-hour MMORPG, like GuidWars,
in my anti-MMORPG
writeup. I now think that even 50 hours is too long.
2.
The toolkit should encourage authors to create a fun
single-player game (or experience), such as an interactive
fiction, CRPG, or FPS. If other players happen to show up, they're an added
bonus to the fun, but their presence can't be required.
3.
The toolkit must emphasise
customisation. For the most part, the game code and
graphics should all be replaceable, including fundamental assumptions such as
gravity.
4.
Here's a tricky (and contentious) solution: The toolkit should only be a few
man-years of work, or rather, the parts important to a player's
perception of what makes a world different should only be a few man-years. If not, any attempt that authors make
to customise the experience will be overwhelmed by the mass of pre-existing
code/content, as in Diku-MUD clones that are all similar
because authors don't have the manpower to significantly change them.
I
suspect that there are ways to circumvent this limitation, to an extent, such
as allowing third party
models and code to be bolted in by authors. However, for this
to work, authors must have a large selection of models and code, and the models
and code must themselves be customizable.
5.
Include all of the necessary tools in
the toolkit, and perhaps even offer hosting. Requiring
the author to download the toolkit, then also
install a separate 3D package, database, scripting language, audio editor, etc.
isn't good enough. There's no reason to make things more difficult than they
need to be.
This
is only one solution.
There are others:
1.
Create a dollhouse PVW toolkit that's designed to
be fun and easy for everyone to use, but where no one seriously expects players
(other than the author's friends) to show up.
2.
Create an amateur PVW toolkit, like I described
above.
3.
Create a professional virtual-world toolkit that
is used to create worlds in the top-100 list.
Attracting
players to amateur PVWs
What
attracts players to an amateur PVWs?
Postscript
I
thought of an alterative name for PVW's... "Mini-MMORPGs" or
"Micro-MMORPGs"... which would be abbreviated as "MMMORPG"!
I decided that PVWs
sounded better. :-)
18 March 2006
by Mike Rozak
Discuss on www.mXac.net/forums
People
usually associate "topography" with geography, but games can include
a variety of topographies.
Alternate
realities
Some
avatar games include alternate realities:
Random
observations:
Geographic
spaces
Several
different geographic spaces have been explored in avatar games:
Random
observations:
Social
spaces
When
I have said, "NPCs are the game!", here is part of what I
meant:
Random
observations:
Of
course, the NPCs exist
within geographic space.
Idea
spaces
Ideas
(and knowledge) have their own space too:
Random
observations:
Ideas
exist within NPC social-space. As per Chris Crawford's work, ideas and knowledge can flow through
social space, affecting characters.
18 March 2006
by Mike Rozak
Discuss on www.mXac.net/forums
In
this article I will explain what
(I think) "multiplayer interactive fiction" (MIF) is, and how it differs
from other game genres.
A
definition of multiplayer interactive fiction
Multiplayer
interactive fiction is an online computer "game" which:
1.
Lets you play a character in a virtual world. The
world isn't limited a standard fantasy or science fiction setting, and might
take place in Victorian England, modern-day New York City, or even a dream
world.
2.
The world is centred around a story that your character participates
in, such as a detective solving crimes in Victorian England, a
romance in New York City, or the more familiar "save the world"
fantasy and science fiction stories.
3.
Your actions affect the story's
direction, although this is limited by current
technology and how much time the author had to write in narrative choices. You
can even forgo the story altogether and follow your own muses, although the
game experience may not be as rich.
4.
The world is filled with sub-games. IF titles
aren't limited to the standard combat
sub-game (common to computer role-playing games) and puzzle sub-game
(common to adventure games). Sub-games, which are customised to the story,
might involve interrogating
non-player characters, romancing them, or trying to run a property-rental
empire.
5.
In (multiplayer) interactive fiction, sub-games are used to enhance the
story, much as music and special effects are used to enhance a
movie's story... instead of the movie's story merely being a vehicle to show
off music and special effects.
6.
Much of an IF title's experience comes
from the "scenery". You might find yourself spending much
of your time in activities that have nothing to do with the sub-games, such as
listening non-player characters' anecdotes, exploring the world, or chatting
with other players, much like you'd do on a real-life vacation.
7.
Many (but not all) IF titles are
multiplayer. You can team up with other players, just
sit around and chat, or stick to yourself. Sometimes you'll even compete
against them.
8.
(Multiplayer) interactive fiction
titles tend to be short, requiring between two to ten hours to complete.
When you finish one, visit the CircumReality
web page and download a link to another. (Or for those who like a challenge,
write your own.)
9.
Most IF titles are created by
hobbyists, who author the titles for the fun of it,
just like people write blogs. If you enjoy a title, or even if you find some
problems with it, make sure to tell the author what you thought; They like to
hear that players are enjoying their works of art, and are always interested in
improving their creations. Also, if
you enjoy playing an IF title, make sure to tell your friends.
Being hobbyists, authors don't have an advertising budget!
10.
IF titles that use CircumReality
will tend to be slow-paced
and intellectual due to the way CircumReality uses
still scenes and spoken narration.
Elements
of multiplayer interactive fiction
Since
the previous definition was probably insufficient, let me cover the elements of MIF:
Comparison
to other genres
To
understand how MIF differs from other genres, I'll first explain how other genres use the above
elements to create gameplay:
What
players do... (a 30,000 ft. view)
Here's
a high level view of what
players do in MIF:
MIF's
core gameplay loop
In,
The
game loop, I described a loop of actions that seems to
be common to every avatar
game. For example: In a CRPG, the game loop is to
kill small monsters, to get treasure and experience points, to enable the
character to kill even bigger monsters. In an adventure game, the game loop is
to solve puzzles, to enable the player access to other parts of the world,
allowing them to solve even more puzzles.
In
MIF, players wander around a world filled with hundreds (perhaps thousands) of
NPCs. The core gameplay
experience comes from interacting with the NPCs, mostly by
talking to them, although combat and other forms of interaction are possible.
In simplistic terms, the game
loop is:
1.
Players encounters a NPC somewhere in the world.
2.
They figure
out how to befriend (or defeat) the NPC.
3.
Players then act on their plan and try to befriend (or
defeat) the NPC.
4.
Once friendly (or defeated), a NPC may provide material assistance,
information, or contacts that help the player befriend (or
defeat) more NPCs and/or access other parts of the world.
5.
Repeat
until all the content is used.
What
players do... befriending NPCs
In
MIF, non-player characters are (relatively) complex AIs. They have personalities, relationships with
other NPCs, and opinions about the player character (as well as
other NPCs). Just as with real people, every NPC has a different personality.
To
befriend a particular NPC, the player must first figure out "what makes that NPC tick"...
Once
a player learns what makes a NPC "tick", they need to act on this. Such
action might include:
Once
friendly, what does the
NPC do for the the player?
Player
vs. player
I'm
not sure if I want to create a player-vs.-player game, but the same core
gameplay could be used for player-vs.-player interactions:
One
problem with including player-vs.-player is that it encourages players to use
game walkthroughs in order to get as influential with NPCs as possible. Once
players start using walkthroughs, puzzles have to be removed from gameplay
because they're trivialised by walkthroughs. This leaves only
"grind"-like gameplay, a condition that I'd rather avoid.
Story
I
hate to use the term "story" because it's so overloaded with
meaning. Having said that, one important way that MIF differs from other games
is that the world is
filled with stories:
1.
Players can partake in larger
pre-programmed "story", such as
defeating the evil emperor, figuring out who the murderer is, cleaning up the
city so it wins the "Tidy town" award (as a sanitation engineer),
etc. This is nothing new, since adventure games and CRPGs always include this
form of "story".
2.
Players can become secondary
characters in "stories" between NPCs. A player
might help two lovers elope, then help later when their child becomes ill.
While many adventure games and CRPGs incorporate such stories, MIF will make
greater use of them.
3.
NPCs have their own stories,
regardless of what the player does. While
talking to the village gossip, players might hear short tales about a villager
that just won the lottery, or one that is having an affair... which might prove
to be a useful skeleton to bring up in conversation with another NPC.
4.
Being a multiplayer game, players will generate their own gossip
(and stories) about what other players are doing.
What
players do... Sample gameplay
Since
gameplay might still be a little unclear, I thought I'd illustrate some sample
gameplay.
The
scenario is one in which an old woman, the local town gossip, knows a salacious
rumour that the player needs in order to boot the town mayor out of office.
Unfortunately, the woman doesn't trust the player at first, and isn't too eager
to give up her prize rumour. She is, however, an avid collector of snow globes.
To
demonstrate the gameplay, I'll also explain how the scenario would be
implemented for adventure games and CRPGs, so you can see the difference.
Note:
This scenario isn't the only way that multiplayer interactive fiction can play
out. It's just pointing out how the experience can be very different from an
adventure game or CRPG/MMORPG. Multiplayer interactive fiction often
incorporates elements from CRPGs, MMORPGs, and adventure games, and might
require a few goblin kings to be slain too.
Usurping
the term "Interactive fiction"
The
term, "interactive fiction" (IF), was coined in the 1980's
to describe the genre of game that Infocom were creating. Over the
past 25 years, Infocom has died and professional "interactive
fiction" has been superseded by "(action) adventure games",
but the term, "interactive fiction", continues to be used by
a small group of dedicated hobbyists.
People
who write interactive fiction consider their work to be different than
adventure games and CRPGs for the following reasons:
I
have adopted the term "multiplayer interactive fiction"
(MIF) even though the beast that I've created doesn't fall neatly into the
traditional interactive fiction definition:
1 January 2008
by Mike Rozak
Discuss on www.mXac.net/forums
I
have lately been trying to create content for my game, CircumReality,
but content design has been "slow as molasses" because of a mental
block I've had; I haven't been happy with my intended content, and I wasn't
sure why.
A
few nights ago, I had a dream. While wandering around the dream and doing
dreamy stuff, I thought to myself, "Why isn't my game as fun as my
dreams?" I have since tried to answer that question, and this writeup
is my answer:
Cravings
Have
you ever noticed that after a hot, sweaty day that you have cravings for salt?
Actually, you probably don't think of salt directly, but you want potato chips
or some other very salty food. That's because your body is low on salt, and it
uses cravings to get you to eat something salty.
Players play games because they have mental cravings.
They're looking for some sort of experience, feeling, or emotion that they know
can be provided by the game.
For
example: Some players like MMORPG "crafting", whose
"experiential" ingredients/nutrients are gathering (collecting
materials), meeting new people (to buy/sell goods to), a challenge (to get the
highest price), and changing the world (as crafters see customers using their
equipment).
According
to my "cravings" theory, someone
that likes crafting should like other activities (sub-games) so long as they
provide the same basic nutrients.
I
know this isn't entirely true with food, so don't expect it to hold for
gameplay cravings: Chocolate chip cookies are made from flour, sugar, butter,
eggs, and chocolate. If I ask for a chocolate chip cookie and you give me the
raw ingredients, I won't be happy. Nor will I be entirely satisfied if you mix
them up in a different configuration, such as a chocolate cake... although I
won't complain that much. With
familiarity, I might even prefer the chocolate cake to chocolate chip cookies.
The
whole is more than the sum of its parts, and any activity is more than the sum of its experiential
ingredients/nutrients. Despite that, understanding what
ingredients/nutrients make up an experience is a valuable tool for game design
because is it predicts what activities a player might like, and what activities
can be substituted for one another, allowing
MMORPGs to escape from the "Kill ten rats" cliches.
Here's
an incomplete
list of some of the
ingredients/nutrients that players get from game activities (sub-games):
Activities (sub-games) in a game need to support one or
more of these ingredients/nutrients, the more the
better. For example:
If a game offers activities that provide players with
experiential ingredients/nutrients that players crave, players will naturally
gravitate towards whatever activity meets their needs.
In other words, if a
game offers a buffet, people will pick and choose as is best for them,
which sounds reasonable to me, and happens to be the way most MMORPGs are
organized.
I
used to oppose
offering MMORPG activities "buffet style",
but I have changed my mind (somewhat). However, I still feel that there should
be limits to the buffet:
Eat
your Brussels sprouts!
Players
don't always partake in the activities that are "good for them". A
few reasons exist:
Several
techniques (aka: spices and artificial flavours) can encourage a player to try
the activity, or to "make an activity more fun":
Avatar
games (such as MMORPGs, CRPGs, adventure games, and first-person shooters) are
not stories, but stories
frequently use several of the above-mentioned techniques.
(Avatar games aren't really games either.) The most common is to create a
character that readers become "friends" with, such as Harry Potter,
and then put him in danger, adding a touch of mystery. It's a proven formula to
get readers to read "just one more chapter" even though they'd rather
be going to sleep.
Use these techniques sparingly because they're a finite
resource!
Use
these techniques to get
players to eat their Brussels sprouts.
Conversely:
Here's
an anecdote: I created a hobbit character in Lord of the Rings Online.
The Shire had oodles of
fed-ex quests. Some of them were sub-games, involving time limits and
NPCs that would "catch you" and abscond with the
sweets you were carrying. They
came with rewards, which was good, because I wouldn't have played the
moderately-fun sub-games without the offer. However, most
fed-ex quests had no
sub-game component, but they still included rewards. I found
myself crisscrossing the
shire dozens of time delivering messages and packages, something that I found
quite boring. But, I
was rewarded for it, so I did it. And I was rewarded for killing rats
(or whatever they were called), so
I didn't kill any rats along the roadside until I was given a quest to kill
them. In fact, I
didn't do anything unless I had a quest for it because there
was always a quest for everything, and quests always provided better rewards.
Ultimately, I found
myself being led around "by the nose", feeling like I didn't have any
choice in the matter. The world conceptually changed from a
beautiful landscape populated by (boring) NPCs and monsters into a task list.
If
killing rats is a "fun" activity by itself and players know that,
then there's no reason
for NPCs to offer rewards to kill rats. A NPC should still
point out the rats so that players know where to find them, and so that the
rats' existence is explained for the sake of the world's realism, but the NPC
doesn't need to offer anything extra. Rats
might still provide experience points as a way to introduce new
gameplay so that players don't get bored, and/or loot to synergise with the
trading/crafting game.
A
typical contemporary MMORPG has the NPC offer a reward of money or loot, and
experience points, in addition to whatever the rats provide. This turns the MMORPG into New York
City, where tips are expected for everything, which also means
that like NYC, tips no longer provide any incentive to provide good service. The act of always providing
activity-external rewards means that activity-external rewards can't be
occasionally used by game designers to encourage players to "eat their
Brussels sprouts".
To
play devil's advocate, having
NPCs offer rewards for killing rats solves a few problems:
Family
restaurants
As
I've written about before, there's
a reason why most restaurants are family restaurants. Family
restaurants provide a
varied menu so that everyone in the family can find something they like.
The reason they're popular is because every
family member has veto powers, and any restaurant which is disliked by even one
of the family members is struck from the list. Only those
restaurants that don't
get vetoes survive.
The
same is true for MMORPGs. A
mass-market MMORPG must cater to all players' mental cravings, not just a few
specialised ones. Any MMORPG that specialises has exponentially fewer players.
Games
are memes
I
wasn't sure whether to put this at the beginning or the end of the article since
it belongs in both places.
People won't play your game unless they know about it and
hear that it's good. (The same goes for restaurants.) In
order, players are more likely to try a game (or visit a restaurant) if:
1.
They first play the game (or visit the
restaurant) with friends.
2.
They have a recommendation from friends, but
play/visit alone.
3.
They have a recommendation from reviewers.. who, due
to legal and commercial threats, rarely give an clear opinion about the
experience.
4.
They see an ad.
5.
They happen to like the look of the game's box when they
see it at the store (or happen to like the look or name of the restaurant).
In
other words:
1.
If my friends are enjoying a MMORPG,
they'll tell me about it too, since playing a MMORPG with friends makes it more
enjoyable. This is an incredibly strong positive (or
negative) feedback cycle, and explains why mass-market MMORPGs must be family
restaurants.
2.
If I ask my friends for a good MMORPG (or
game) to play, they'll
tell me about the one which made the biggest "impression" on them.
This is where "eat your Brussels sprouts" comes in. The reason that "classic
novels" (which include plenty of Brussels sprouts) have stayed around so
long is because they made a lasting impression on people (partly due to the
Brussels sprouts) that causes readers to remember and recommend the books to
other people (aka: a meme, a viral idea). Airport novels, on
the other hand, don't say anything meaningful and are quickly forgotten, never
to be recommended, and soon out of print.
3.
If your game can't rely on 1 or 2,
then you'll need a really good PR team and a huge marketing budget.
Conclusion
Getting
back to my dream. Why was it more fun than my in-progress game? Because my dream satiated some of my
mental cravings and included some deeper meaning ("Brussels
sprouts"), neither of which are being implemented properly
by my current content... but I'll change that now.
Some
of the changes are:
22 March 2008
(Revised 27 Mach 2008)
by Mike Rozak
Discuss on www.mXac.net/forums
As
I stated in Nutritional
game design, players play games because games provide
something they want (or need). For example: A player may wish to play a game because he wishes to be
Sir Lancelot.
However,
there's a problem: While
playing the game (of being Sir Lancelot), it is blatantly obvious to players
that they are merely playing a game, and that they're not really Sir Lancelot.
The reality is that they're sitting in front of a 19" monitor, pressing
'A', 'W', 'S', and 'D' to move, 'space' to attack, and '1' through '4' to speak
different sentences to obviously artificial characters. Somehow, 'space' is
supposed to mimic the thrill and excitement of a real sword swing, and '1'
through '4' the attacks in a verbal battle of wits.
Just
like readers of a novel, game
players must willingly suspend their disbelief.
Unfortunately
for the player:
The more that a game calls
attention to its nature (by using a 'space'
button to swing a sword, as opposed to a real sword with motion sensors), the less able the player is to
fulfil his dream of being Sir Lancelot!
To
flip this statement around:
Immersion is necessary for a
game's activities to fulfil a player's "nutritional" needs.
To some extent, a player must forget that they're
playing a game for the game to work.
Game
designers know this, and employ a number of techniques to immerse players:
Some
hybrid techniques are also used:
If
immersion is required for a game's activities to fulfil a players' needs (which
is why they're playing the game), then some conclusions come to mind:
1 May 2008
by Mike Rozak
Discuss on www.mXac.net/forums
When
I first started thinking about writing a game, I considered the following
“game” design:
Imagine wandering around a world and just watching
– “smelling the roses”, so to speak. A player could wander around, watch what
the inhabitants were doing, and be entertained by just viewing, just as a story
entertains readers, who are merely watchers (not doers).
Theoretically,
players wouldn’t be able
to affect the world at all.
Except,
of course, the design wouldn’t
let players miss the important action. Scenes would be frozen
until the player was there to observe them.
And
players would be able to
talk to NPCs, and maybe even interact by playing cards with
them, or partake in
other sub-games that wouldn’t interfere with the world/story.
So,
the player would affect
the world somewhat, but only on the margins, like deciding
which shade of black to paint their Model-T Ford. (Henry Ford is attributed
with the quote, “The customer can have any colour he wants so long as it’s
black.”)
As
I said, I was originally going to write such a “game”, but decided not to. I
didn’t have any firm, logical reasoning for not wanting to; it just didn’t feel right.
For years now, I’ve been wondering if the idea would “work”.
It won’t work.
The
reasons are:
1.
Games are about choice.
Without choice, players might as well read a book or watch a movie.
2.
Choices without consequence (change/agency)
aren’t valid choices. If you’re given a choice of what’s behind door A or
what’s behind door B, and later discover that the exact same thing was behind
both, you’ll be miffed. (See Choices III.)
3.
Without choice, players would feel
like ghosts, unable to actually do anything. Most would
realize this and leave unsatisfied. Those that remained would be tormented
souls, spending eternity wandering the world, rattling their virtual chains.
But,
you might say, typical MMORPGs are exactly that, except the NPCs hardly exist
and those that do don’t actually do anything. MMORPGs are exactly what I described
except (a) there are very few roses to smell, and (b) character advancement is
king.
It still won’t work.
(And typical MMORPGs
don’t work either; I’ll explain later.)
I’ll
expand my “smell-the-roses” world to include typical MMORPG features, though
still without world-changing agency:
1.
Players can walk around and smell the roses.
2.
The roses
make sure not to actually bloom until players are around to see
and smell them.
3.
Players aren’t limited to smelling the
roses; they can also touch them.
4.
Players can partake in rose-like
activities, such as flower arranging, playing poker
with rose-themed cards, and/or killing rose-monsters.
5.
Players can smell the roses together.
6.
Players can collect roses of different
varieties and colours. These collections allow players to
ultimately collect bigger and better roses from more dangerous parts of the
world.
7.
Roses can be used to affect (prick?)
other player characters, and indirectly affect other players.
What do players think of this kind of world?
To
simplify things to a controversial
statement:
MMORPG
fundamental problem #1: MMORPGs
are fundamentally broken because players can’t change the world.
Even
though gameplay involves millions of choices, players eventually realize that
all those choices have only two effects: (a) advancing their characters, and/or
(b) affecting their relationships with other players.
Thus,
the types of players attracted to MMORPGs are (a) people whole like advancing
their characters (achievers), and (b) people who like hanging around people who
like advancing their characters (socialisers). A few killers come along for the
ride, because they like having people around that they can dominate. And a few
explorers find the paucity of roses (explorable bits) present in a MMORPG
enough to keep them interested.
I’ve
been lying (somewhat). Some
MMORPGs have limited world-affecting agency:
However,
these change-powers
aren’t enough, for two reasons.
MMORPG
fundamental problem #2: MMORPGs
are fundamentally broken because MMORPGs where players can change the world
(often by owning something) all tend provide the same “you can change the world
in such-and-such a way” feature.
Face
it, almost every MMORPG has housing. It’s nearly as cliché as orc whacking.
Players have already owned MMORPG houses before, so being able to own more
housing isn’t seen as terribly desirable. Vehicle ownership is the new housing
(housing was new for MMORPGs circa 2000). Personal NPCs will be next.
What players can change about the world needs to be a
unique selling point!
The
other fundamental problem is much scarier for MMORPGs:
MMORPG
fundamental problem #3: MMORPGs
are fundamentally broken because when players do change the world, it will
eventually revert back.
It
must; letting a sieged castle stay occupied by a guild forever, for example,
means that new players will never get to siege that castle, instantly “wasting”
all the time and effort that went into the castle’s design. Even worse, if a
group of players were to actually defeat the evil overlord, a whole new one
would have to be invented, including all the quests that lead to the final
defeat.
The
only solution to this problem is to “rush the players out the door” soon after
they’ve changed the world in the largest and most meaningful way. Failure to do
so shatters the illusion of change, and quickly negates any solutions to
problems #1 and #2.
MMORPGs don’t rush the player out the door because their
business model relies on keeping players around so they keep paying.
Consequently, players eventually learn that all changes are ephemeral, except
(you guessed it) changes to their character or their relationships with other
players. (...as well as whinging on the forums and harassing other players off
the game.)
So
what does a virtual
world that allows change look like?
A
word about MMORPG
business models, since the problems I described represent
classic paradigm shift
conditions:
25 June 2008
by Mike Rozak
Discuss on www.mXac.net/forums
A few years ago, I read the book, Creating Emotions in Games, by David Freeman. I didn't entirely agree with the book. Two main messages from the book rubbed me the wrong way:
As I pointed out in Making players forget that they're playing a game, an important feedback loop between immersion and emotion exists. The two are intertwined.
The same loop happens in TV/Movies and books. An excellent example is the classic movie, Cat on a Hot Tin Roof, which gradually draws the viewer into a troubled family. It begins with mystery, and rollercoaster's its way through a plethora of emotions.
Emotion is critical. Emotion is critical. Emotion is critical!
It's so important that I'd go as far as saying that:
One more thing...
Many "How to write a story" books say that "story = conflict" and that it's impossible to write a good story without conflict. I'll rephrase that equation: "Story requires immersion. Emotion encourages immersion. Conflict encourages emotion. Thus, conflict encourages story." Don't mistake the symptoms of the disease for the disease itself!
Likewise, many people assume that "computer game = gameplay". In other words, everyone assumes that a "computer game" (for lack of a better term) must include gameplay. Gameplay is the technical definition of opponents, choices, winners and losers, etc.
As with stories, don't mistake the symptoms for the disease:
"Computer games" require immersion. Gameplay encourages immersion. Gameplay also encourages emotion. Thus, gameplay is a useful device in making immersive "computer games".
The reason why gameplay is often an integral part of "computer games" is because:
At this point, I could go a step further an claim that the traditional Bartle player types (achiever, killers, explorers, socializers) have emotional correlations too, but I still need to think about it.
25 June 2008
by Mike Rozak
Discuss on www.mXac.net/forums
Before you read this article, you should familiarize yourself with Choices, part 3.
Computer games (and gameplay) require choice. Player's choices must have consequences. This article discusses consequences.
The consequences of a choice can be categorized in two ways:
Thus, for every choice a player makes, a designer can create a table of the consequences. This table for "walking forward" might be:
|
Expected |
Expected |
Unexpected |
Unexpected |
|
|
Positive |
Character
moved forward, which aids in completeing the goal, "Walk towards the
castle." |
|||
|
Neutral |
The
grass is flattened where the player walks. |
The
garden-gnome is annoyed by the player walking on the grass, and never talks
to the player. |
||
|
Negative |
The
character was blocked after a few steps by an invisible wall. |
A
16-ton weight drops on the character's head. |
Why is this table useful? (Here are some random bullet points that explain "why".)
Creating a table of different consequences hints at the different types of effects for consequences. Some of them are:
19 September 2008
by Mike Rozak
Discuss on www.mXac.net/forums
Since I began my interactive fiction "game" project, I've been asking myself over and over: Why do I want to make an interactive experience? Why not just write a story?
My initial answer was:
While this reason is correct, it's incomplete.
Interactivity comes at a cost. It introduces all sorts of problems that non-interactive entertainment (stories) doesn't have:
These are enormous problems, which make me wonder why I even bother trying to write interactive entertainment.
Interactive entertainment has its advantages though:
So what's the moral of the story?
27 November 2008
by Mike Rozak
Discuss on www.mXac.net/forums
As anyone who reads my write-ups knows, I like looking at the elephant from every angle (see the blind men and an elephant anecdote, http://en.wikipedia.org/wiki/Blind_Men_and_an_Elephant), so to speak. I keep coming up with different theories (angles to look from, or parts of the elephant to feel out), and testing them against successful games.
Recently, I’ve been playing Fallout 3 for fun… and at the same time, seeing how well my theories match what Fallout is doing design-wise. (For all those Fallout/Oblivion bashers: Yes, Fallout has many flaws, but I don’t expect perfection and can look beyond the flaws.).
Fallout is “fun” to play because it does a decent job of meeting a few basic requirements:
Making a world that’s interesting and relevant to the player
If players could use a device to teleport them to the alternate reality of Fallout (yet not be subject to injury by the alternate reality – radiation, bullets, etc.), what would make the alternate-reality Fallout “fun” for players?
o Escapism – From the escapist’s POV, Fallout is fun simply because it isn’t the real world… and it isn’t yet another cliché Tolkien-based world either.
o Identity experimentation – Players can experiment with being evil (or good).
o Growing up – For teenagers, the game is about growing up: Freedom, learning, becoming more powerful, and making your own choices.
o Fallout answers the question, “What is it like to live in a post-apocalyptic world?” (I suspect people were more concerned with this question in the 1950’s than the 2000’s.)
o Memelets – Memelets are small ideas that are scattered throughout the game world, such as observations about the sanitized 1950’s culture, thoughts about the fragmentation of society, human behavior under difficult circumstances, etc.
o Mystery – The world is filled with conspiracies and unknowns, attractive to many players.
o Combat – Excitement, frustration, success.
o Stories that tug on emotions (through quests and NPCs) – Better NPC and story design could have produced a greater emotional impact.
o Satisfaction when quests/goals are completed
o A sense of wonder from the scenery and memelets scattered throughout the world.
o NPCs – If players can be made to care about some of the NPCs, then players care what happens to the NPCs, and will “fight” the rest of the world to help the NPCs.
The recipe
In other words, here’s the generic recipe that Fallout and other CRPGs follow:
Games don’t need to follow this recipe exactly, but (I suspect) leaving out ingredients will result in a less-successful game.
Notice how Richard Bartle’s explorers, socializers, killers, and achievers appear. According to this theory, player types are people who prefer one ingredient in the recipe more than others… kind of like chocoholics or sweet-tooths.
The events coordinator
Any good holiday resort has an event coordinator, who is tasked with making the visitors’ experiences enjoyable and memorable. Partly, the event coordinator makes reality more fun than it normally world be.
The event coordinator also keeps the tourist/player busy so they don’t get bored and start whinging about how rainy it is, or how the local bakeries don’t make pasties like they do at home. Fallout keeps the player’s brain (and virtual body) constantly occupied so that players don’t notice they’re not a real world:
o Lots of visual stimulation (eye candy), and motion
o Acoustic stimulation (sound)
o Language center stimulation – Not only do NPCs speak, but a radio is playing in the background.
o Movement requires that the player concentrates so they don’t get lost
o Traps further encourage players to pay attention
o Monsters also require concentration
o Players must keep an eye out for resources (like ammunition)
o Puzzles bar the way – Although limited in Fallout
o Players must remember and recall the layout of the land.
o NPCs and their relationships with other NPCs must be remembered
o Players must remember what objects and monsters do
o Memelets are scattered around
o Players must learn and master combat techniques
o Quests and goals – In Fallout, quests handed out by NPCs and the radio station.
o Players must decide which resources they will need in the future.
Note: The concentration, memory and learning, and planning aspects tie in with Raph Koster’s “A Theory of Fun”.
Making the virtual world feel like a real world
What if the hypothetical alternate reality (step through a machine and get to the alternate reality of Fallout) were more of a Disneyland-like theme park in the real world? The world would still be in real, but located on real-life Earth, populated by robot monsters, and run by real actors, all for the benefit of players. What would the requirements be for a “fun” world?
· All of the above.
· The props department would have to manufacture realistic costumes, monster robots, and sets… also known as “eye candy”
· An Earth-sized Fallout-land wouldn’t be possible. Fallout-land would still need to be fairly large though, with plenty of manufactured detail. A few thousand acres might suffice, large enough that boundaries aren’t easily encountered. The world would need to be detailed enough that it looked “real” to a casual observer.
· NPCs (actors) would need to wander around the world and ostensibly live their own lives, pretending that the world is real whenever players are watching.
· Players must be able to change the world (to an extent) with their actions.
“Please ignore the man behind the curtain”
What issues would occur if the Disneyland version of Fallout were converted into a virtual reality simulation (aka: computer game)?
As a game, Fallout tries to NOT remind players that they’re playing a game:
o The “most important parts” of reality are simulated, such as the 3D, the ability to move around, etc. (Eye candy)
o NPC AI is critical. If quality AI isn’t achievable (which it isn’t), then the world needs to have fewer NPCs, or use NPCs in ways that don’t require much AI (such as combat).
o Fallout employs realistic-looking graphics (although a bit to grey for my tastes)
o Fallout’s NPC speech is audio, not just text scrolling across the screen
o The screen is completely filled with world-simulating 3D graphics.
§ Displays like the compass and player hit points have minimal visual impact.
o Even when stats and maps are pulled up (on the Pip-Boy), they’re made to look like an in-game object.
§ Note: Thinking about how Fallout minimizes clutter on the screen caused me to change my game UI. I now hide the auto-map until players specifically ask for it, and even then it only appears temporarily. After making these changes, I immediately noticed an increase in my game’s “immersion”.
Semi-relevant random thoughts
Random thought #1:
Many of the design goals are in conflict.
For example: Making the control mechanism simpler (increasing immersion) reduces the number of choices that players can make, which makes the virtual world feel less real (reducing immersion).
For example: Allowing players to make choices (increasing immersion) reduces the quality of eye candy that’s available (reducing immersion), and vice versa.
Game designers must make tradeoffs between the many conflicts.
Random thought #2:
In any game, eye candy can be improved by using more cut scenes, or by turning the game into a Choose Your Own Adventure (CYOA) experience. However, CYOA reduces the player’s choices, which counteract much of the “immersion” produced by providing the eye candy.
Historically, CYOA has only been successful when the eye candy provided by the CYOA experience has been MUCH better than the eye-candy provided by the contemporary game-like (procedural) experience.
For example:
· CYOA and Fighting Fantasy books – They eye candy was only verbal (see below). There were no alternative game-like experiences to compete with, so CYOA and Fighting Fantasy books sold well.
· Dragon’s lair – The eye-candy was cell-animated, stored on laser disc. The alternative procedural games were Robotron and Pac Man, both with poor eye candy.
· Phantasmagoria – The eye-candy was pre-rendered 3D graphics combined with video of live actors. The alternative procedural games had sprite-based characters, or extremely primitive 3D characters, both inferior to the CYOA-based Phantasmagoria eye candy.
As far as I know, those are the only successful CYOA “games”. Since Phantasmagoria’s success in 1996, 3D accelerators have improved enough that no subsequent CYOA has been able to produce vastly-superior eye-candy. Hence, no CYOA since 1996 has been successful.
Random thought #3:
Why do books “work” if don’t have any eye candy?
Two reasons: (a) We’re trained from an early age (and perhaps even genetically enabled) to use words as substitutes for realistic visuals. (b) Writing is the “eye candy”, or at least good writing is.
To illustrate: Think of a paragraph from a good writer (like Dickens) and then consider that same paragraph as it would be displayed in a text MUD/IF, procedurally generated from smaller text snippets.
Dickens:
The evening arrived; the boys took their places. The master, in his cook's uniform, stationed himself at the copper; his pauper assistants ranged themselves behind him; the gruel was served out; and a long grace was said over the short commons. The gruel disappeared; the boys whispered each other, and winked at Oliver; while his next neighbours nudged him. Child as he was, he was desperate with hunger, and reckless with misery. He rose from the table; and advancing to the master, basin and spoon in hand, said: somewhat alarmed at his own temerity:
'Please, sir, I want some more.'
The master was a fat, healthy man; but he turned very pale. He gazed in stupified astonishment on the small rebel for some seconds, and then clung for support to the copper. The assistants were paralysed with wonder; the boys with fear.
Versus procedural MUD/IF English:
> Go south
You are in a dingy dining room. The room contains: Bill. Ted. Oliver Twist. The Master. Exits are: North.
> Examine master
The master is a fat, healthy man.
> Wait
Oliver Twist eats a gruel.
> Wait
Oliver Twist finishes eating a gruel.
> Wait
Oliver Twist approaches The Master.
> Wait
Oliver Twist says, “I’m still hungry.”
If a text MUD/IF could write as well as Dickens, I suspect it would be successful.
March 13, 2012
you need a checkmate-detecting ai to ensure that
players don't dead-end the game 1/3 of the way through. This ai looks forward
in time to make sure that given the players prior bad choices, they can
continue to have fun in the game. Very important! Games are limited to
prebalanced simplelistic gameplay without this.
Another way to think about game design is this
model: Grand-unified theory of world-like games.
1) You have AI's for every NPC. NPCs are
contolled by their AIs MUCH-MORE than they are controlled by a movie-like
script.
2) You have a predictive psychology model for
the player... determining how intelligent (good at gameplay) they are, as well
as what choices they are likely to make.
3) You have an "AI" predictor that
looks forward through a (theoratical) ALL-possible-choices that the player can
can make, as well as the NPCs. This will consume most of your computer's CPU.
If the AI-predictor determines that the player
has gotten into a losing checkmate solution, say within the next 5 - 10 hours,
then then it (a) stears the player into a non-losing direction, (b) stears the
NPCs in a direction that won't encourage the player to lose (such as ensuring
that a hard-to-find NPC suddenly needs to visit town more-often to pick up
groceries), (c) makes gameplay easier, (d) etc.
I am also including my text-to-speech papers in
this document, written for the “Blizzard Challenge”. I believe that
text-to-speech is a CRITICAL technology for future virtual-world avatar-games.
Contemporary computer-games limit themselves, by only using recorded speech-prompts
for non-player-character dialogue. (http://www.synsig.org/index.php/Blizzard_Challenge)
Text-to-speech
Designed For a Massively Multiplayer Online Role-Playing Game (MMORPG)
Mike
Rozak
mXac,
Darwin, NT, Australia
Mike@mXac.com.au, http://www.CircumReality.com
Abstract
CircumReality is a niche-market
massively-multiplayer online role-playing game (MMORPG or MMOG) that relies
heavily on text-to-speech (TTS) for narration, non-player character (NPC) speech,
and text chat between players. Associated speech technologies enable voice chat
and voice transformation/disguise. Most contemporary TTS engines are geared
towards hand-held devices or telephony, resulting in technology that is not
ideally suited for games. This paper discusses how a game-oriented TTS engine
differs from a telephony or device-oriented TTS engine, and how those
differences affect the technology.
1.
Introduction
In the early 1980’s, most computer games
relied on “sprites” to generate their visuals. [1] Sprites are
pixelated images that are moved around the screen by offsetting their X and Y
origin. They are animated by quickly redrawing different images in the same
location. Very few games used real-time 3D rendering in the 1980, the most
memorable being Atari’s Battlezone.[2] Most real-time 3D rendering
was used for CAD/CAM[3] and military flight simulators.[4]
Twenty-seven years later, real-time 3D rendering in games is ubiquitous, and
sprites are rarely used.
In 2007, most computer games use
recorded speech, or even just text without any audio component. Recorded speech
is expensive to produce, and requires enormous amounts of storage when used
with a computer role-playing game (CRPG) or MMORPG. Everquest 2, a recent
MMORPG, recorded over 200 actors [5] to produce 130 hours of
voice-acting audio.[6]
No contemporary MMROPGs or CRPGs use
TTS, just as in 1980, very few games used real-time 3D rendering. Current TTS
isn’t acceptable for game developers because of the lack of emotion, and because
contemporary TTS engines target telephony and handheld devices, resulting in
some features that are net negatives for games. Recorded speech vs. TTS in 2007
is analogous to sprites vs. 3D rendering in 1980.
CircumReality is an experimental
niche-market MMORPG [7] that is designed to survive financially with
only a small base of players. Since recorded speech is so expensive and
produces such large downloads, TTS is employed as a cost saving technology, as
well as an enabler of new gameplay. Because of CircumReality’s niche-market
nature, unemotional TTS, while still an issue, is not the show-stopper that it
would be with mass-market games, where players demand the best visual and audio
effects.
CircumReality also lets players create
their own “world” that other players can enter. Based on statistics from older
MMORPG development toolkits, [8] around 1% to 0.1% of the players
are likely to create content. Many of these amateur authors will also wish to
create their own TTS voices since that is part of the fun.
When CircumReality was first envisioned,
a survey of TTS engines was conducted, but significant anti-game design
decisions were found in existing engines:
1. Many
MMORPGs, such as the popular Runescape [9], are distributed to
players for free. Such products earn money from advertising or virtual item
sales.[10] Most TTS engines are licensed on a per-unit royalty,
which isn’t acceptable for free-to-play games because they only earn income
from around 10% of their players, and only from around 1% to 0.5% of all the
copies of their software that are downloaded.[11]
2. MMORPGs
and CRPGs include up to a thousand computer-controlled non-player characters
(NPCs).[12] While having a
unique voice for every NPC isn’t necessary, enough voices should exist that
players don’t notice their re-use. Contemporary TTS engines focus on producing
a few high-quality voices that, for the purposes of telephony, can easily
require one hundred or more megabytes of RAM per voice.[13][14][15]
MMORPGs require around a hundred voices, most procedurally derived from a few
real voices, all fitting in a few hundred megabytes of RAM and download.
3. In
the case of CircumReality and many other games, some players will become
authors and create their own content. This practice is often called “modding” [16]
I expect that many authors will create their own text-to-speech voices, using
their own voice or their friends’ voices for training. Most TTS engines don’t
distribute their voice-creation tools, or make them easy enough for non-experts
to use.
4. Many
of the authors will want to create non-English content. Precisely because
CircumReality is targeting niche markets, many of the players who speak less-common
languages will want to create a content that represents their language and
culture. Such languages are not supported by contemporary TTS engines because
the market it too small. Although not fully implemented, CircumReality’s TTS
toolkit will allow motivated authors to create their own language with a
minimum of linguistic knowledge.
This paper will examine how
game-specific requirements affect TTS technology, and how the resulting design
decisions impacted the Blizzard 2007 Challenge results.
The majority of the game-specific design
decisions affect:
1. The
acoustic feature set – The voice must be stored in a compact format that is
easily mutable into new voices.
2. Speech
recognition – ASR is not only used for segmentation, but for eliminating bad
units and selecting the “best” units to keep.
3. Unit
concatenation – The acoustic feature set’s design has consequences for unit
concatenation.
4. Prosody
model – The prosody model must be automatically generated with a minimum of
linguistic knowledge.
2.
Acoustic
feature set
MMORPG-oriented
TTS needs to be able to synthesize enough different voices that players don’t
notice duplications, while still retaining a download of a few hundred
megabytes. Not only are approximately one hundred voices required, but some
must be exotic-sounding voices, such as growling dragons and bell-like fairies.
To meet these
flexibility requirements, the CircumReality TTS engine encodes voices using an
acoustic feature set based on a two spectral envelopes: one for voiced and one
for unvoiced, along with an F0, and per-harmonic phase. No residual is stored
since residuals aren’t readily modified during voice transformation.
A screenshot of
the feature set appears below. The top lines are the transcription, with F0
appearing underneath. The top histogram represents the voiced audio, and the
bottom unvoiced. Each horizontal band in the histograms is one octave, with the
lowest frequency being 100 Hz and highest 12.8 kHz. The bottom portion of the
display is the colour-coded phase angle for 64 harmonics, with the fundamental
at the bottom.
Figure 1: Acoustic feature set for
“Fast, but endure”.
The original
signal is encoded by detecting F0 as accurately as possible since even a small
error in F0 detection causes severe errors in the encoded signal. To generate
the spectral envelopes, a pitch period is stretched in time domain to a
power-of-two width needed for an FFT. Adjacent pitch periods are also analysed,
with differences in energy and phase in each harmonic being used to guestimate
how much of the signal is voiced or noise; Noise has more variation in the
harmonic’s energy and phase. Additional heuristics are used to minimize errors
due to incorrect F0 estimation, such as using a windowed FFT for higher
frequencies, as well as voiced vs. noise calculation. Phase is easily obtained
from the FFT. All phases are rotated so that F0 always has a phase angle of 0.
The voiced signal
is regenerated using additive sine-wave synthesis, adjusting for an
interpolated phase. The unvoiced signal is synthesized using three sine waves
per harmonic, with a randomly perturbed frequency whose variation is a function
of the noise-to-voiced ratio. Using three randomized sine waves allows for
smoother transitions from voiced to unvoiced than simply adding in a filtered
noise signal, and provides more flexible monster-like voice transformations.
The phase angles are also used for resynthesis; ignoring the phase information
produces a “buzzy” voice.
This feature set
has one major disadvantage: Because there is no residual, all of the voices
have a slight “vocoder” sound. In the case of the voice used for the Blizzard
2007 Challenge, the vocoder sound was particularly noticeable.
Despite the
feature set’s flaws, it has many game-specific advantages:
·
Voice variation:
o
Voice transformation/disguise algorithms
can easily modify the spectral envelopes and produce new human-sounding voices.
o
Non-integer harmonics can be employed to
generate unusual bell-sounding voices.
o
Looped waveforms can be used instead of
additive sine-wave synthesis, good for growling monsters.
o
Converting portions of the voiced energy
to unvoiced can also be used for monsters.
o
Some “emotions” such as whispering,
speaking softly, or shouting also involve transformations of the acoustic
feature set.
·
The feature set is easily compressed
with a lossy encoding, producing smaller versions of a voice. Players with more
memory and bandwidth can download larger uncompressed voices.
·
The feature set affects unit
concatenation. See below.
·
Re-use of technology:
o
The same feature set, without F0 and
phase, is used for ASR’s segmenter and unit scoring. (See below.)
o
The same feature set is used for
encoding of voice chat. Voice chat includes the ability to transform/ disguise
the players’ voices, using the same algorithms that modify the TTS voices.
o
The same feature set is used for
automatic lip sync of player’s characters when they speak using voice chat.
3.
Speech
recognition
Some players will
create their own TTS voices as part of the effort to create content for their
world. Either they or their friends will record several thousand utterances.
Consequently, the
process of creating a voice must be reasonably automatic, and can’t require any
speech expertise. For example: Authors can’t be required to review all the
units and select the best one, nor can they be expected to speak exactly what
they’re prompted to speak.
CircumReality’s
voice-development toolkit employs ASR not only as a segmenter, but also as a
filter to eliminate bad units, and to automatically select the best unit. ASR
is included in the toolkit’s install and fits seamlessly into the toolkit’s
user interface.
Traditional
HMM-based speech recognizers split a unit into three-to-six time-slices, and
create a frequency-domain mean and variance for each windowed time slice.[17,
pp. 307-413] While this is adequate for dictation and phoneme
segmentation, the speech recognition score can’t be used to accurately identify
the “best” TTS unit for a given context. Most times, a simple mean/variance
will choose a distinctly “poor” unit.
Poor selection
happens because the “best” units often have the brightest, most prominent
formants, narrow energetic peaks in the spectrum. The peak’s central frequency
varies significantly throughout the frames of an individual unit, and more so
across thousands of units. Averaging several thousand spectrums with
narrowly-peaked formants together produces a single spectrum with blurry,
ill-defined formants. A speech recognizer that uses the blurry formants to pick
the “best” unit out of thousands inevitably chooses the unit that is most
similar, which ends up sounding “muffled” and difficult to understand.
Many TTS systems
avoid this problem by using speech recognition to eliminate the worst
candidates, but leave the selection of the “best” up to luck and
ASR-independent scores.[17, pp. 817] ASR-independent heuristics are
used to identify the “best” candidate, such as unit duration, energy, and F0.
ASR’s tendency to select muffled units is further minimized when synthesizers
store contiguous unit sequences from the original data and select the longest
contiguous candidate; a contiguous sequence of five or six phonemes only has a
couple of other candidates as competitors, so even if mean/variance speech
recognition were used to select the “best”, the phoneme sequence wouldn’t have
enough available candidates to reliably select the most muffled candidate.
CircumReality’s
TTS cannot leave unit selection up to chance, and it cannot rely on expert
speakers or expert prompt reviewers. Amateur speakers are likely to misspeak or
mispronounce the prompts. They are unlikely to review what they have recorded,
failing to check for bad segmentation or pronunciations. Because of memory
constraints for the voices, voice files won’t have long stretches of contiguous
phonemes. Consequently, relying on ASR’s mean/variance to select a unit fails
under the conditions that CircumReality will encounter.
CircumReality’s
TTS voice generator attempts to work around muffled units by stochastically
storing 48 exemplar spectrum-samples per sub-segment instead of storing one
mean and variance.
Figure 2: 48 spectrum samples for
each of 4 sub-segments, for “iy1” phoneme. Voiced is on top, with unvoiced
below.
To produce a
score for a feature-set spectrum time-slice, ASR performs a difference
comparison between the spectral envelope in question and each of the 48
spectrums stored for the sub-segment of the recognition unit. Difference is
calculated by performing a dot-product on the energy-normalized spectrums. A
penalty is added based on the difference in total energies. The 48 scores are
sorted, and the top 12% of the scores are combined together to produce the
final score, using a weighted average.
By using only the
top 12%, the speech recognizer is fairly confident that the score is
representative of the unit. Considering the extremes, if scores from all 48
comparisons were averaged, the result would be similar to the single mean (and
no variance) comparison common to ASR, preferring muffled formants. If only the
top score were used, then incorrect units and alignments in the training set
would easily derail the recognizer. Using the top 12% is a compromise.
Even using the
stochasticly chosen spectrums, ASR has a tendency to choose muffled formants.
To further minimize this propensity, the spectrum comparison algorithm is
augmented to allow small frequency shifts in the formant peaks. Each octave of
spectrum can be shifted plus or minus half an octave without penalty, as well
as a +/- 3 dB energy change. This causes peaks with slightly different centre
frequencies or energies to be treated as identical.
Even with these
adjustments, ASR still tends to select the more muffled formants, particularly
with the Blizzard Challenge 2007 voice. (See “Lessons learned”.)
CircumReality’s
phoneme segmenter uses a Viterbi search, hypothesizing over all word
pronunciations, and all possible phoneme durations. To create a score for a
hypothesized phoneme, individual time-slice scores are averaged.
When building a
TTS voice, a context-dependent (CD) ASR model is built for each phoneme,
stochastically selecting the 48 x 4 exemplar spectrums from all of the matching
CD phonemes in the model. If the data set is large, the context is the left and
right phonemes, if small, the acoustic groups for the left and right phonemes.
The ASR model of a CD phoneme is used to represent the “ideal” for the phoneme.
A “quality” score
is generated for each unit to determine how accurately it matches the “ideal”
for the CD phoneme. As per typical TTS unit-selection, differences between the
duration, F0, and energy of the unit and those of the mean unit, are included
as a penalty. The CD ASR score is also incorporated as a major portion of the
unit’s quality score. The final quality score, which includes the CD ASR score,
is stored with the unit to aid the concatenation Viterbi search.
Individual units,
diphones, and contiguous sequences of units are selected using a weighted
average of the individual unit scores. For a given sequence, the candidate with
the highest aggregate quality score is retained.
4.
Unit
concatenation
CircumReality
unit concatenation is fairly traditional. [17, pp. 804-817] A
Viterbi search tries to find the highest scoring sequence of units. Demiphones
are used.
Target costs
include:
·
The unit’s quality score, a major
portion of which is the CD ASR score, is used.
·
Differences between the original and
target unit’s F0, F0 delta, energy, and duration have minimal effects.
·
Penalties for CD phoneme mismatches are
also included. Small penalties are used if the left/right phonemes differ from
the desired context only in phoneme stress. Larger penalties are used is the
left/right phonemes differ, but are from the same acoustic group.
Join costs
include:
·
The difference between the two
edge-spectrums, calculated using the same difference measure employed by ASR.
·
Weighting to encourage contiguous units
is important. Penalties for non-contiguous units are amplified if either of the
units is plosive since the acoustic feature set does a better job of joining
non-plosive unit halves. Breaks are preferred between two non-plosive units, or
in the middle of a single non-plosive unit.
·
F0 and the harmonic’s phase are not
used.
Concatenated
units are smoothed by pitch-shifting and energy-adjusting the formants at the
boundaries. These adjustments, while easy to perceive in the visualized feature
set, provide only a minor improvements to the voice.
Phase is blended
at non-contiguous unit boundaries. Around 1/16th of a second is blended
for the lowest harmonics. Higher harmonics do not require blending. Failure to
blend phase is most noticeable in the low-F0 Blizzard voice.
5.
Prosody
model
A prosody model
is learned from the voice. This is critical for two reasons: (1) It helps make
each voice sound more unique, and (2) Learned prosody simplifies localization,
important for authors customizing CircumReality to their own language; they
won’t have professional linguists to hand-generate prosody rules.
To train prosody:
F0 and energy curves are calculated for the utterances. Phoneme segmentation,
word segmentation, part-of-speech, and punctuation are also used. A list of
syllables is extracted from the audio sample, with punctuation being treated as
a silent syllable. The following information is extracted for each syllable:
·
The average F0 of the syllable, the rise
of the F0 over the syllable, and the amount of “bend” in the F0. Three values
produce a second-order approximation of the F0 curve. These numbers are
relative to the sentence’s average F0.
·
The average energy of the syllable,
relative to the sentence’s average energy.
·
The duration of the syllable relative to
the duration predicted by the syllable’s phonemes, and the duration of the
syllable relative to the average duration of all syllables.
·
The number of phonemes in the syllable.
·
What word the syllable came from,
particularly important for function words.
·
Whether the syllable is stressed or
unstressed.
·
The syllable index number within the
word.
·
Whether there is silence before the
beginning of the syllables that begin words.
·
The depth of the word in the
context-free grammar (CFG) parse tree used for part-of-speech disambiguation.
The syllable
information is used to create a number of simple prosody models, such as:
·
How stressed/unstressed syllables affect
F0, duration, and energy.
·
How the location within the sentence of
length N affects F0, duration, and energy.
·
How the previous and subsequent
parts-of-speech and stresses affect F0, duration, and energy.
·
The silence bit is used to determine the
probability of a short pause before the word given the part-of-speech context,
often indicative of a phrase break.
Just as using a
single mean and variance for ASR produces muffled units, using simple models
for prosody produces uninteresting and bored-sounding prosody. To work around
this problem, the prosody generated by the simple prosody models is
“subtracted” from the original sentences, which are then stored in the prosody
model as “residuals”.
When prosody is
synthesized, the synthesizer finds the longest contiguous segment of residual
that’s the best match. A “contiguous segment” requires an exact match for the
syllables’ part-of-speech and stressed/unstressed values. The quality of the
match is determined by differences in the numbers of phonemes in each syllable,
the syllable index within the word, and the original word (important for
function words).
If only the
longest/best match is chosen, however, the synthesized prosody becomes a
somewhat erratic, speaking some words far too quickly, slowly, loudly, or
quietly.
To stabilize the
system, the top residual choice is compared to the next seven-best residual
choices. A difference score is calculated by comparing the difference in the
synthesized prosodies for each of the candidates, using F0, duration, and
energy. The four most-similar prosodies are averaged together, largely
eliminating the erratic prosody, while still maintaining interesting prosody.
To regenerate the
prosody, the simple prosody models are first run, and then the prosody residual
is incorporated. Adjustments are made where the synthesized sentence doesn’t
exactly match the sentence used to generate the residual. For example: If the
synthesized prosody has a syllable with three phonemes, but the chosen residual
had only two phonemes for that syllable, then the syllable will be lengthened
according a database value that was generated as part of the prosody model.
This prosody
model has several advantages:
·
Every voice has its own prosody, making
the voices sound more unique.
·
It requires no linguists (or any input)
for localization. Unfortunately, part-of-speech determination still requires a
context-free grammar with a few dozen hand-programmed linguistic rules.
·
Prosody can be transferred or merged
between voices.
·
Components of the prosody model can be
procedurally adjusted to create new prosody models from the original.
·
If an author has a problem with a common
phrase not being spoken properly, he merely has to record how it should be
spoken, and add that recording to the TTS or prosody-model training set.
·
The residual match scores are randomly
perturbed so that a sentence is spoken differently every time it is heard,
which is particularly important in a game where players may repeatedly hear the
same sentence, such as “You open the door.”
6.
2007
Blizzard challenge
The CircumReality
TTS engine didn’t perform well in the 2007 Blizzard Challenge, coming in last
for the mean opinion score and similarity tests.
Figure 3: Mean opinion score for
all listeners.
The major
problems with the voice were:
· Existing
voice encoding/decoding algorithms didn’t work well with the Blizzard Challenge
voice, producing a noticeable “vocoder” effect. While the vocoder effect
occurred with other test voices, it was much more noticeable with the Blizzard
2007 voice.
Switching to a
different method for encoding the voice, or adding a residual, would eliminate
the vocoder effect, but it would also hinder the engine’s ability to
procedurally create a variety of voices from a single source.
· Speech
recognition tended to select muffled units for the voice. I think this happened
because the brightness of the formants varies greatly between units, a useful
skill for a professional voice talent trying to create an expressive sentence.
However, ASR ends up learning that the “best” brightness for a unit is
someplace between bright (and easy to understand) and muffled, which results in
slightly muffled units, suboptimal for a TTS voice.
Oddly, the engine
did relatively better with the word error rate:
Figure 4: Word error rate for all
listeners.
One explanation
for this discrepancy is that despite the “vocoder” distortion caused by
encoding to the acoustic feature set, and unit selection preferring “muffled”
units, ASR successfully chose one of the “better” muffled units, although not
the “best” un-muffled unit.
Synthesized
prosody performed better than I expected. Prosody quality wasn’t isolated out
by any of the Blizzard tests, but my own non-scientific comparison of the
CircumReality prosody to that of other engines puts it closer mid-range. It
sounded less stiff and more natural than many of the engines, probably because
it doesn’t use any hand-generated prosody rules. Ironically, natural-sounding
prosody was also a minus: The Blizzard speaker’s volume often trailed off near
the end of a sentence, and so too did CircumReality’s synthesized prosody.
CircumReality’s synthesized prosody performs less well on long sentences
though.
7.
Lessons
learned
Since the
listening test samples for the Blizzard challenge were submitted,
CircumReality’s algorithms have been improved so that the Blizzard 2007 voice
sounds noticeably better. The improvements that made the greatest difference
were:
·
Due to memory constraints, the voice
generator tool couldn’t produce a voice larger than 20,000 units on 32-bit
Windows. This wasn’t a problem for a game-oriented voice since 20,000 units,
around 100 megabytes uncompressed, is at the extreme upper range of acceptable
voice size. Multiplied by 8 voices, this produces an 800 megabyte download,
which is far too large for most players. After installing 64-bit Windows Vista
and recompiling the toolkit for 64 bits, a better-sounding larger voice was
produced.
·
The algorithms to extract the acoustic
features from the Blizzard 2007 voice needed improving. The voice’s combination
of low F0, well defined, crisp formants, large dynamic range, large F0 range,
and occasional vocal fry all proved troublesome. The improved feature
extraction algorithms noticeably reduced the “vocoder” sound of the voice,
although it still exists. Even though storing a residual would eliminate this,
doing so would impair the flexibility of voice transformation, an important
feature for games.
·
Unit scoring has been improved so that
when the “ideal” units are trained using ASR, training is weighted by the unit
energy, causing ASR to prefer the louder units, which also tend to have
brighter and more well-defined formants.
·
Unit selection has been augmented to
store longer unit sequences, as well as store multiple versions of a unit
sequence based on F0. Again, such affordances produce better-sounding but
larger voices, less suited for games.
Frequency shifting a unit of the Blizzard 2007 voice
had significantly worse effects on quality than the other voices that
CircumReality was tested with. I think this is because of errors in the
harmonics’ phases: Looking at the visual representation of the feature set,
it’s obvious that phase tracks with the formants. Harmonics at the peak of the
formant incur the least amount of phase delay, while those at the edges more.
Importantly, harmonics outside a formant are so quiet that their phase is
undetectable or completely inaccurate. When a unit’s F0 is shifted, the phase
relationship with the formant is broken; the harmonic at the peak of the
formant no longer has the lowest phase delay. Even worse, some of the
undetectable harmonics are frequency-shifted into the formant, and consequently
resynthesised with either a random or 0-degree phase. The Blizzard 2007 voice
illuminated the problem because it had a low F0 and narrower formants.
8.
Future
work
Although acoustic
feature extraction still induces noticeable errors, the largest problem with
the CircumReality TTS engine, in terms of games, is the synthesized prosody.
Even if prosody were as good as the best prosody example in the Blizzard
Challenge, it wouldn’t be good enough; even the best TTS engines produced
speech that sounds like a “bored telephone operator”, destroying any illusion
that the NPCs are real.
Recorded speech
is much more emotionally powerful. To experience an example of NPC interaction
with emotionally recorded speech, see the Facade [18] interactive
storytelling [19] demo. Facade’s 167 megabyte distribution also
illustrates the problems with recorded speech: Most of the download size is
used for speech audio recordings of only two NPCs speaking in an extremely
limited domain. Extrapolating to a thousand NPCs with a much broader
conversation domain reveals the ultimate impossibility of using recorded
speech, despite its emotional quality.
For the next
10-20 years, I suspect the best solution to this problem will be to use
transplanted prosody wherever possible. Of course, many phrases that NPCs speak
are procedurally generated and must still revert to synthesized prosody.
The key to good
transplanted prosody is the integration of the transplanted prosody tools into
the game development toolkit. Recording an utterance for transplanted prosody
needs to be so convenient, quick, and easy that authors automatically and
effortlessly record the transplanted prosody utterance when they type in a
sentence for a NPC to speak.
Thus, the key to
better game TTS isn’t just the algorithms, but also TTS’s integration into the
game-development toolkit.
The tools for
producing a CircumReality TTS voice are publicly available as part of the “3D
Outside the Box” software package on http://www.mXac.com.au/m3d. The
CircumReality game, still not finished, is available from http://www.CircumReality.com. It
uses many of the open-license voices from the 2005 Blizzard Challenge.
9.
References
[1] http://en.wikipedia.org/wiki/Sprite_%28computer_
graphics%29
[2] http://en.wikipedia.org/wiki/Battlezone
[3] http://en.wikipedia.org/wiki/CAD/CAM
[4] http://en.wikipedia.org/wiki/Flight_simulator
[5] http://pc.gamezone.com/news/08_02_04_10_57AM.htm
[6] http://www.mobygames.com/game/everquest-ii
[7] http://en.wikipedia.org/wiki/Mmorpg
[8] http://www.mudconnect.com/ - Around 1650
text MUDs have been created by a community of 50,000 to 200,000 players.
[9] http://www.runescape.com/
[10] http://en.wikipedia.org/wiki/Real-money_trading
[11] http://en.wikipedia.org/wiki/Shareware
[12] http://en.wikipedia.org/wiki/Non-player_character
[13] http://www.cepstral.com/downloads/
[14] http://www.naturalvoices.att.com/products/tts_data.html
[15] http://www-306.ibm.com/software/pervasive/
voice_server/technical_details/
[16] http://en.wikipedia.org/wiki/Mod_%28
computer_gaming %29
[17] Huang, Xuedong, Acero, Alex, and Hon,
Hsiao-Wuen, Spoken Language Processing, A
Guide to Theory, Algorithm, and System Development, 2001, New Jersey,
Prentice Hall PTR.
[18] http://www.interactivestory.net/
[19] http://en.wikipedia.org/wiki/Interactive_storytelling
CircumReality
functionality delta: Blizzard Challenge 2007 to 2008
Mike Rozak
mXac, Darwin, NT, Australia
Mike@mXac.com.au, http://www.CircumReality.com
Abstract
Although performing poorly in the Blizzard Challenge 2008, the CircumReality text-to-speech engine improved significantly from the Blizzard 2007 test. The engine’s acoustic model, prosody model, and acoustic synthesis were improved between tests. This paper discusses the CircumReality engine’s test results and reasons why it did poorly. The paper provides a list of improvements that resulted in higher test scores in 2008, as well as implementation details one change: objectively-calculated target costs.
Index Terms: speech synthesis, games, Blizzard Challenge
The Blizzard Challenge was devised “to better understand and compare research techniques in building corpus-based speech synthesizers on the same data. The basic challenge is to take the released speech database, build a synthetic voice from the data and synthesize a prescribed set of text sentences. The sentences from each synthesizer will then be evaluated through listening tests.” [1] Participants then write a paper discussing their results. Over the course of years, the intent of the competition and publication cycle is to improve the quality of TTS engines.
The CircumReality TTS engine is designed for the CircumReality multiplayer online game. [2] The engine uses concatenative synthesis with a trained prosody model. Half-phone units are used with a triphone context.
The engine was first entered in the Blizzard 2007 challenge and did poorly, ranking last on nearly all the tests.[3] Although the CircumReality engine ranked poorly in the latest Blizzard 2008 challenge, its scores improved significantly from 2007. This paper discusses why the CircumReality engine did poorly, what changes to the engine significantly improved the quality, and implementation details of one change: objectively calculated target and join costs.
Although CircumReality TTS engine did poorly in the 2007 and 2008 challenges, it showed significant improvement.
CircumReality’s mean-opinion score (MOS) rose 0.7, from 1.3 for the “A” voice in 2007, to 2.0 in 2008. (See Figures 1a and 1b.) The average of all other engines’ MOS (excluding the original speaker and two 2008 benchmark engines, Fest and HTS) was 2.95 in 2007 and 2.92 in 2008, down slightly. Of course, the 2007 and 2008 voices were different, so only large changes in score or relative to other engines are meaningful. Nor are the participants in 2008 the same as 2007.
In contradiction, the Festival benchmark engine’s MOS improved from 3.0 to 3.3 despite the average engines’ MOS declining slightly. Since participants in both years are largely the same, I suspect this represents either an engine bias towards American English, or voices recorded with 2007’s “news presenter” prosody.
Figure 1a: Blizzard 2007 MOS
Figure 1b: Blizzard 2008 MOS
CircumReality performed relatively better with the word-error-rate test (WER), both in 2007 and 2008:
Figure 2a: Blizzard 2007 WER
Figure 2b: Blizzard 2008 WER
CircumReality’s mean WER only dropped 2%, from 47% in Blizzard 2007 to 45% in Blizzard 2008. The mean WER for all other voices (excluding the original speaker and two 2008 benchmark voices, Fest and HTS) was 35% in 2007, and 40% in 2008, an increase of 5% due to the 2008 voice’s ebullient British prosody. The benchmark engine, Festival, also had an increased WER, increasing from 25% in 2007 to 35% in 2008. Despite the aggregate WER increasing, CircumReality’s WER decreased marginally.
As discussed in CircumReality’s Blizzard 2007 paper [3], the CircumReality engine uses an acoustic feature set consisting of a voiced and unvoiced spectrum. The feature set was chosen to enable easy voice transformations, important for games. Unfortunately, the feature set introduces vocoder-like artifacts, more prominent in some voices than others. The 2007 CircumReality TTS engine (CR2007) used additive sine-wave synthesis to synthesize the wave.
The Blizzard 2007 voice exposed many problems with the acoustic feature extraction. Between Blizzard Challenges, the feature extraction algorithms were improved, but the Blizzard 2008 voice still exhibited significant artifacts.
The feature set had pitch-synchronous PCM added. A full wavelength was included with each frame, and time-stretched to the required wavelength when synthesized. PCM functionality was originally included in the feature set for testing and debugging purposes only. Its functionally was kept to a minimum since PCM isn’t flexible enough for voice transformation, an important feature for game synthesis. Consequently, TD-PSOLA was not implemented to save development time, despite TD-PSOLA sounding better.
The 2008 CircumReality TTS engine (CR2008) could synthesize with either PCM or additive sine-wave synthesis. PCM synthesis improved acoustic naturalness, but introduced other errors:
Overlapping pitch-synchronous PCM with a Hanning window reduces unvoiced energy at high frequencies, particularly impacting the “brightness” of the “s” phoneme. Energy at high frequencies was amplified to counteract this effect, improving the intelligibility of “s”. Unfortunately, the high-frequency energy boost changed the voice’s quality, impacting the “similarity” score.
PCM is a proverbial double-edged sword. PCM produces high fidelity speech synthesis, but introduces large artifacts when pitch shifted. The target costs for pitch-shifting PCM were calculated and included in the unit-selection search. (See section 5.). The costs, as expected, were large.
Even using large F0 target costs, the unit-selection search would occasionally select a unit requiring a substantial pitch shift, producing “hiccups” in the speech synthesis. The hiccups undoubtedly lowered the voice’s MOS, counteracting some of the MOS improvements gained by using PCM.
To minimize the hiccups, units’ original F0 contours were averaged into the synthesized-prosody’s F0 contours, producing higher acoustic quality at the expense of lower prosody quality.
Extremely high F0 target costs outweighed all other join and target costs: duration, energy, and phoneme context. Consequently, the use of PCM forced less well-fitting units to be substituted, reducing the voice’s quality.
At the time of the 2008 test, PCM sounded marginally better than additive sine-wave synthesis, despite all its negative consequences. PCM was used for the tests.
For computer games, personality is often more important than intelligibility. CircumReality’s synthesized prosody is designed to try and reproduced the prosody of the training voice, often at the expense of intelligibility. CR2008’s prosody algorithms produce lower-quality prosody than hand-generated rules.
The 2008 speaker spoke in an “ebullient” manner that, even before synthesis, was more difficult to understand than the “news presenter” prosody spoken by the 2007 Blizzard Challenge voice.
Ebullient speech exposed weaknesses in CR2008’s prosody algorithms that weren’t as obvious in the “news presenter” voice from 2007. CR2008 wasn’t able to synthesize ebullient prosody that well, certainly less well than it could synthesize “news presenter” prosody.
CR2008 did manage to approximate ebullient prosody. Unfortunately, in partially succeeding, CR2008’s prosody modeling made the voice more difficult to understand because ebullient prosody is inherently more difficult to understand than “news presenter” prosody, even when spoken by a real person.
Figure 3: Blizzard 2008 similarity scores
When listening to the test sentences, I thought CircumReality mimicked the voice’s prosody better than many of the other engines. I expected CircumReality’s similarity score (see Figure 3) to be relatively higher than its MOS score. This didn’t happen; both the MOS and similarity scores, and their positions relative to other engines, were approximately the same. Either my perception of how well CircumReality mimics prosody is incorrect, or prosody is only a very minor part of how people perceive voice similarity. Acoustic similarity seems to be a much larger component, at least when listeners are presented with an unfamiliar voice. If the voice were Winston Churchill’s, with its own unique prosody, would prosody modeling count for more?
CR2008’s prosody model failed in other ways:
As already stated, the prosody model was hindered by the need for PCM to minimize F0 changes.
Synthesized prosody was further impaired by problems with F0 detection. Pitch doubling would occasionally happen, particularly at the end of utterances. Units that are pitch-doubled are normally eliminated from the acoustic model because an F0-mismatch results in distorted features that produce a low ASR score; low-scored units are automatically discarded. The prosody model doesn’t have any equivalent F0 integrity checks, so a pitch-doubled word results in synthesized prosody that suddenly doubles F0 for a word or two, usually at the end of a sentence.
Below is a list of major changes between CR2007 and CR2008. All of these changes produced at least minor improvements to voice quality. Some will be discussed in detail, in section 5.
· Bugs that produced minor reductions to speech quality were found and fixed.
· F0 detection was improved by assuming that F0 stayed near to the median F0 throughout the sentence.
· Acoustic feature extraction was improved. The same features were used, but new algorithms more-accurately extracted the features from the training utterances.
· Pitch-synchronous PCM was stored, allowing CR2008 to synthesize using PCM.
· CR2007’s voice-construction tool ran out of 32-bit memory when building the Blizzard 2007 voice, limiting the voice to 20,000 units. CR2008’s tool was rebuilt with 64-bit pointers, easily allowing a 265,000 unit voice.
· When building a voice, all units were scored by a combination of ASR and target costs based on F0, duration, and energy. CR2008 automatically discarded the bottom 25% of all units to minimize bad units.
· In CR2007’s voice building tool, an ASR model for each triphone was trained and used to compute the unit’s score. In CR2008’s tool, nine ASR models for each triphone model were calculated as a two-dimensional matrix of low, medium, and high F0 by low, medium, and high energy. This improved the voice’s clarity and eliminated some “muffled” units.
· In CR2007’s voice building tool, ASR was used to test how similar the unit sounded compared to its triphone model. In CR2008, this value was modified based on how differently the unit compared to other phonemes, comparing the unit against ASR models for similar phonemes. This discouraged units that were in-between two phonemes, producing a voice that was easier to understand.
· CR2008 differentiates between triphones at the start, middle, or end of a word. CR2007 did not.
· The prosody model was refined. The same basic principles were used.
· In CR2008, F0 and duration of synthesized units are now affected by the original unit’s F0 and duration. When PCM acoustic synthesis is enabled, F0 from the original unit is weighted much more strongly than the F0 from the synthesized prosody model.
· The acoustic-unit-selection Viterbi-search was refined.
· The acoustic unit search for CR2007 voice used ad-hoc target and join costs. For CR2008, these were calculated using ASR. See section 5.
· F0, duration, and energy of the prior unit are included in the target cost to encourage smooth transitions when non-contiguous units are used.
· As stated earlier, CR2008 can synthesize using PCM instead of additive sine-wave synthesis.
Once change between CR2007 and CR2008 warrants further discussion.
In CR2007, target costs were based on ad-hoc guestimates. For example: Left/right context substitutions were assigned a very high target penalty, around ten times higher than F0, energy, and duration target costs.
The MARY-TTS Blizzard 2007 paper [4] implied that objective target costs for concatenative synthesis hadn’t been calculated before. This challenge intrigued me so I decided to calculate the costs. I later learned that the USTC/iFlytek Blizzard 2007 paper [5] discussed target cost calculations for HMM synthesis. I’ll discuss the differences later.
The target-cost calculating tool was created, and values were calculated from 9000 sentence-length recordings of my own voice. What follows is a list of the calculated target-cost values, and the algorithms used to calculate them.
To calculate F0 target costs, an ASR model was trained for every triphone. (To reduce computation time and memory, the left and right content phonemes of the triphone were grouped into one of 17 groups. For example: “m”, “n”, and “ng” were placed in the same group.) Importantly, not all versions of the triphone unit were included in the ASR model; only phonemes whose F0 fell near the median F0 for the triphone were trained.
In a second pass, all of the phonemes in the training data were compared against the F0-limited triphone ASR models. The ASR scores were graphed on a scatter plot.
Figure 4: F0 target costs for (l, r) – eh1 – (m, n, ng). The vertical axis shows the ASR score, with large values being poor matches. The horizontal axis shows the number of octaves that F0 was above or below the triphone’s median F0.
To ensure that enough data existed to produce an accurate linear fit, the data points were combined into four sets based on broad phoneme categorization. Phonemes were categorized into voiced (V) or unvoiced (U), and plosive (P) or non-plosive (N). For example: The phoneme, “m”, is voiced non-plosive (VN), while “t” is unvoiced plosive (UP).
|
|
Per octave higher |
Per octave lower |
|
UN |
3.26 |
6.97 |
|
UP |
2.53 |
1.13 |
|
VN |
5.43 |
6.52 |
|
VP |
3.76 |
3.71 |
Table 1: F0 target costs per octave the target is higher or lower than the original data.
The calculated F0 target costs, although lower than expected, make intuitive sense; F0 target costs for unvoiced plosives (UP) are much lower than costs for voiced non-plosives (VN).
The F0 target costs in 5.1 were calculated assuming that additive sine-wave synthesis would be used. Synthesizing with PCM requires additional target costs since even small F0 shifts in PCM produce extreme artifacts.
To calculate the PCM F0 target costs, the voiced and unvoiced spectrums were shifted up and down by half an octave, simulating a PCM F0 shift of half an octave. The shifted spectrums were compared against the triphone ASR models. The ASR score for the original un-shifted unit was also calculated, and subtracted from the two shifted ASR scores. All the shifted scores were averaged based on UN, UP, VN, and VP.
|
|
Per octave higher |
Per octave lower |
|
UN |
18.28 |
18.82 |
|
UP |
11.06 |
7.44 |
|
VN |
20.94 |
19.8 |
|
VP |
10.9 |
8.8 |
Table 2: PCM F0 target costs per octave the target is higher or lower than the original data.
PCM F0 target-cost values are very high, especially for non-plosives (UN and VN). Because TD-PSOLA has fewer acoustic artifacts than the simplistic PCM synthesis I used, I suspect that TD-PSOLA would have produced lower F0 target-costs, although still significant.
Energy target cost was calculated using the same basic approach as F0 target costs. Instead of training an ASR model with F0’s near a target, only units with an energy-value near the target energy were trained.
|
|
Energy doubled |
Energy halved |
|
UN |
5.95 |
4.94 |
|
UP |
6.58 |
1.62 |
|
VN |
4.04 |
6.37 |
|
VP |
7.11 |
3.13 |
Table 3: Energy target costs based on the target’s energy relative to the unit’s original energy.
Duration target costs were calculated in the same way that F0 and energy target costs were calculated.
|
|
Duration doubled |
Duration halved |
|
UN |
0.05 |
1.5 |
|
UP |
3.02 |
15.53 |
|
VN |
1.04 |
5.04 |
|
VP |
4.45 |
4.81 |
Table 4: Duration target costs based on the target’s duration relative to the unit’s original duration.
CR2008 differentiates units occurring at the start, middle, or end of a word, and applies a target-cost penalty if there’s a mismatch.
To calculate the target cost, four ASR models were trained per triphone: At the start of a word, middle of a word, and end of a word, and triphones that were the entire word.
A second pass compared every phoneme in the training data against each of the four ASR models for the triphone. The ASR score from the correctly-matched word-position model was subtracted from all the others so the target-cost penalty for an exact match is always 0.
All of the ASR scores were averaged:
|
|
Target-cost penalty |
|
No mismatch |
0.00 |
|
Start-of-word mismatch |
3.56 |
|
End-of-word mismatch |
3.38 |
|
Start and end-of-word mismatch |
5.61 |
Table 5: Target-cost penalty for word-position mismatches.
CircumReality’s unit selection search can substitute in a different triphone with the same centre phoneme. For example: The “a” in “cat” might be used to synthesize the words “cap” or “bat” even though the “a” in “cap” and “bat” should use different triphones.
Calculating the context target costs requires an enormous number of ASR models to be trained:
· Exact match ASR models – An ASR model was trained for every triphone, using the left and right phonemes as the left and right triphone context.
· Narrow-group ASR models – The left and right phonemes were categorized onto one of 17 groups, and the triphone trained based on the left and right phonemes’ groups.
· Broad-group ASR models – The left and right phonemes were categorized into one of 5 groups, and the triphone was trained based on the left and right phonemes’ groups.
In a second pass, every phoneme in the training database was compared against numerous “exact match”, “narrow group” and “broad group” ASR models:
1. “Exact-match” target cost– The phoneme was compared against the appropriate “exact match” ASR model.
2. “Mismatched-stress” target cost – If the right context was a stressed or unstressed phoneme, then the phoneme was compared against the “exact match” ASR model of the opposite stress-context. For example: If the right context was “eh1”, the phoneme was compared against the model with “eh0” as the right context.
3. “Mismatched-phoneme in narrow group” target cost – The phoneme was compared against all the “exact match” ASR triphone models with varied right contexts, such that: (a) the right context phoneme was part of the true right-phoneme’s “narrow group”, and (b) the right context’s phoneme was not a stressed or unstressed version of the true right-context phoneme. The ASR scores were then averaged.
4. “Mismatched-phoneme in broad group” target cost – The phoneme was compared against all possible right-context variations of the “narrow group” triphone ASR models, except the phoneme’s true “narrow group” ASR model. The results were averaged.
5. “Mismatched-phoneme not in broad group” target cost – The phoneme was compared against all the “broad group” triphone ASR models whose right-context did not match the phoneme’s true right context. The results were averaged.
6. To ensure that an exact left/right context match would have 0 target cost, the “exact match” score from step 1 was subtracted from all the other scores (steps 2 through 5).
7. Steps 1 through 6 were repeated, but the left context was varied instead of the right.
The resulting target costs are:
|
|
Left context mismatched stress |
Left context mismatched phoneme in narrow
group |
Left context mismatched phoneme in broad
group |
Left context mismatched phoneme not in
broad group |
|
UN |
0.67 |
1.30 |
1.67 |
2.90 |
|
UP |
0.3 |
1.47 |
2.13 |
4.84 |
|
VN |
0.43 |
0.99 |
1.51 |
2.22 |
|
VP |
0.51 |
1.20 |
1.60 |
5.76 |
Table 6a: Left context target costs.
|
|
Right context mismatched stress |
Right context mismatched phoneme in narrow
group |
Right context mismatched phoneme in broad
group |
Right context mismatched phoneme not in
broad group |
|
UN |
1.28 |
7.41 |
7.44 |
5.87 |
|
UP |
2.16 |
4.83 |
5.29 |
11.21 |
|
VN |
2.71 |
3.30 |
2.62 |
4.36 |
|
VP |
3.02 |
7.46 |
5.61 |
8.54 |
Table 6b: Right context target costs.
As anticipated, mismatched plosives (P) tend to incur a higher target cost then non-plosives (N). Unexpectedly, right-context-substitution target costs are much higher than left-context costs.
Context target costs were much lower than anticipated, less than one tenth the ad-hoc values used in CR2007.
The data also illustrates some noise. Theoretically, all values should be monotonically increasing from left to right. Values don’t always do this, as in the case of the right context being an unvoiced non-plosive.
Join costs between two units were calculated using a distance measure between the spectrums of the two boundary frames. With thousands of possible joins considered by the unit-selection Viterbi search, this process can be very slow. Pre-calculating all the join costs is not an option for CR2008 due to the memory and file size requirements of games.
As an optimization, the “mean join costs” based on diphones are calculated and used to reduce the number of candidates for which accurate boundary scores must be calculated. The unit-selection search’s hypothesized units are narrowed down to the top 100 candidates by sorting based on their anticipated scores, including all the target costs and the mean join costs. Join-costs are then accurately calculated between the existing hypothesis and 100 new candidates. (See section 5.8.)
To calculate the mean join costs, a diphone ASR database is trained. Unlike the other ASR training, only the last frame in the first unit is trained – where the join occurs. CR2008’s ASR comparison for a single frame is exactly the same mathematics as used to calculate join costs.
ASR scores for the diphones are calculated and averaged into a database. As with other target cost calculations, phonemes are categorized into one of four groups:
|
|
Right context is UN |
Right context is UP |
Right context is VN |
Right context is VP |
|
Left context is UN |
20.98 |
17.38 |
17.88 |
13.75 |
|
Left context is UP |
23.42 |
25.67 |
16.51 |
22.20 |
|
Left context is NV |
21.42 |
22.52 |
11.80 |
15.89 |
|
Left context is VP |
24.71 |
31.30 |
12.59 |
13.46 |
Table 7: Mean join-costs given the left and right contexts.
Join costs between non-contiguous units were calculated using a distance measure between their adjacent spectrums. As shown earlier (Section 5.7), the mean join cost was around 20, with higher values indicating poorer joins.
The unit’s score and all the target costs have a “per second” connotation. When the unit-selection Viterbi search includes them in the hypothesis score, the ASR scores are scaled by the unit’s duration.
Join costs are different because they are the calculation of an instantaneous value, over one frame, not the duration of the phoneme. Because join cost is instantaneous, the join cost value should theoretically be scaled by one frame (5 milliseconds) and added to the Viterbi search score.
This doesn’t work well in practice. A scaling value of 25-50 milliseconds produces better-sounding results. I haven’t yet determined why the theoretical value doesn’t work well.
I hadn’t noticed the Blizzard 2007 USTC/iFlytek paper [5] discussing their target and join cost calculation methods until long after implementing my own algorithms.
CR2008’s target/join-cost algorithms differ from those iFlytek’s in a number of ways:
· CR2008 uses a different acoustic feature set than iFlytek, so per-frame acoustic distance calculations are handled differently, but with the same intent.
· iFlytek uses F0 as a join cost, probably because the iFlytek’s acoustic synthesis employs a PCM acoustic representation. Since PCM doesn’t handle pitch bending well, little to no pitch bending would be applied to iFlytek’s synthesized units, leaving only F0 mismatches at joins. CR2008 is designed for games, where transplanted prosody is required. F0 is dictated by the transplanted prosody or prosody model. Synthesized F0 is never the same as the original unit’s F0. Consequently, F0 must be part of the “per second” target cost instead of the instantaneous join cost.
· iFlytek’s duration model is built into the same framework as its acoustic and concatenation models. F0 and energy are also included in that framework. CR2008 separates F0, duration, and energy into a separate model. They’re either provided by a prosody model or transplanted prosody. In CR2008, the prosody model drives F0, duration, and energy, in turn driving the unit selection. Conversely, iFlytek’s HMM synthesis, with no explicit prosody model, appears to let unit selection drive F0, duration, and energy, which in turn drives prosody.
· Context mismatch costs are implicitly handled by iFlytek’s HMM acoustic distance measures. CR2008 must explicitly include them.
The CircumReality TTS engine improved significantly between the 2007 and 2008 Blizzard Challenges. This was achieved through a variety of changes in the acoustic model, unit selection, prosody model, and acoustic synthesis.
Using PCM in CR2008 was a mistake; although it improved the acoustic quality of the voice, PCM’s F0 inflexibility hurt many other TTS subsystems. TD-PSOLA is expected to sound better, but isn’t ideal for games, so it may not be worth the experimental effort. CR2008’s acoustic feature extraction algorithms have been improved in the months since submitting synthesis results for the 2008 Blizzard Challenge. The quality of the additive sine-wave synthesis voice now matches or exceeds the PCM voice.
I plan to improve acoustic synthesis in a number of ways:
1. More-accurate ASR, since ASR is the foundation of good unit selection.
2. More-accurate unit scores, such as different scores for each half of the unit.
3. More-accurate target and join costs, using more than the four groups (UN, UP, VN, VP) discussed here.
4. The join-cost scaling problem from 5.8 needs to be solved.
5. Smoother unit joins are needed, the exact location of the join determined by ASR.
6. Prosody tradeoffs that improve the aggregate (unit + target + join) scores for a synthesized utterance.
The prosody model needs to be improved too, although the tradeoffs between intelligibility and mimicking the original voice’s prosody will continue to be an issue.
[2] Rozak, M., “What is CircumReality?”, mXac.
Online: http://www.CircumReality.com, accessed on 16 July 2008.
[3] Rozak, M., “Text-to-speech Designed for a
Massively Multiplayer Online Role-Playing Game (MMORPG)”, in The Blizzard Challenge 2007, Bonn,
Germany. mXac. Online: http://festvox.org/blizzard/bc2007/index.html,
accessed on 16 July 2008
[4] Schroder, M. and Hunecke, A., “MARY TTS
Participation in the Blizzard Challenge 2007”, in The Blizzard Challenge 2007, Bonn, Germany. Online: http://festvox.org/blizzard/bc2007/
index.html, accessed on 16 July 2008
[5] Zhen-Hua Ling, Long Qin, Heng Lu, Yu Gao,
Li-Rong Dai, Ren-Hua Wang, Yuan Jiang, Zhi-Wei Zhao, Jin-Hui Yang, Jie Chen,
Guo-Ping Hu, “The USTC and iFlytek Speech Synthesis Systems for Blizzard
Challenge 2007”, in The Blizzard
Challenge 2007, Bonn, Germany. Online: http://festvox.org/ blizzard/bc2007/index.html,
accessed on 16 July 2008
CircumReality
text-to-speech, a talking speech recognizer
Mike Rozak
mXac, Darwin, NT, Australia
Mike@mXac.com.au,
http://www.CircumReality.com
Abstract
The CircumReality text-to-speech engine’s mean opinion (MOS) and similarity-to-original scores have improved significantly over the last three Blizzard Challenges [1] [2]. MOS has increased from 1.3 in 2007 to 2.8 in 2009. This paper describes the algorithmic improvements made to the CircumReality engine between the 2008 and 2009 Blizzard Challenges. The most significant improvements stemmed from a shift in the underlying philosophy of the engine: integrating automatic speech recognition (ASR) into the speech synthesis engine and creating a “talking speech recognizer”.
Index Terms: speech synthesis, speech recognition, games, unit selection, Blizzard Challenge
The annual Blizzard Challenge allows text-to-speech researchers to compare their engine technologies against one another by providing a common speech database from which all entered voices are created [3]. Such a test allows speech researchers to eliminate voice-database quality, and to a lesser extent, hand-tuning, as factors in synthesized voice quality. Researchers employ the test’s results, including associated Blizzard-Challenge papers explaining other entrants’ results, to improve their engines.
The CircumReality text-to-speech engine has improved significantly over the three years that it has been entered in the Blizzard Challenge. (See figure 1.)
Figure 1: CircumReality
text-to-speech engine similarity and mean-opinion scores (MOS) over three Blizzard
Challenges.
The improvements to CircumReality’s speech quality came from two directions:
· Acoustic features – The use of TD-PSOLA proved superior to time-domain stretched PCM (in the 2008 entry [2]) or additive sine-wave synthesis (in the 2007 entry [1]).
· Using ASR for target and join costs – A new philosophical foundation for the text-to-speech engine was employed: The process of speech synthesis was understood in terms of a “talking speech recognizer”, and ASR (automatic speech recognition) was tightly incorporated into the synthesis process.
CircumReality was entered in the 2009 Blizzard Challenge as voice “H”. The CircumReality engine was entered for the EH1 (10 hours of speech data), EH2 (1 hour of Arctic speech data) and ES1 (100 sentences from the Arctic dataset) tests. A Mandarin Chinese voice was generated, but not entered due to the CircumReality engine’s poor synthesis of Chinese.
The CircumReality engine uses unit-selection synthesis; unlike standard unit-selection synthesizers [4, pp 475-493], CircumReality runs its own speech recognizer in tandem with voice generation and synthesis [2] to help determine which units to select. Voice creation is mostly automated, although for the Roger dataset, some sentence utterance groups were manually eliminated from the prosody model because they contained atypical prosody.
In 2008, the CircumReality engine ranked at the bottom of the submitted entries [2]. The engine performed significantly better in the 2009 Blizzard Challenge, achieving better-than-average MOS and similarity scores. (See figure 2.) Interestingly, the engine didn’t do well on the word-error-rate test, as will be discussed later.
Figure 2: Mean opinion
score (MOS) for EH1, EH2, and ES1. “Orig” is the original voice, “Circum” is
CircumReality. “Fest”, “HTS 2007”, and “HTS 2005” are reference engines.
Stereotypical unit-selection synthesizers employ ASR for phoneme segmentation [4, pp 467-471], and may use the ASR phoneme scores, along with extremes in F0, energy, and duration, to eliminate the “worst” sounding phonemes.
From early in its development, the CircumReality text-to-speech engine has incorporated units’ ASR scores in the unit-select Viterbi search. [1]
CircumReality’s 2008 engine (CR2008) used ASR extensively. [2] Most notably, the target cost for F0 and duration mismatches were calculated using ASR; target costs no longer needed to be manually generated. Target costs for mismatched start/end of a word, and mismatched left/right contexts were also calculated using ASR. Unit join costs were calculated using the same feature-comparison algorithms employed by ASR.
CircumReality’s 2009 entry (CR2009) integrated ASR even more extensively than CR2008. ASR is so intertwined into the text-to-speech algorithms that CircumReality’s synthesis and ASR are inseparable.
All values used in the unit-selection Viterbi search come directly from, or are derived from ASR. Many values in the prosody model also originate from ASR.
In other words: CircumReality’s built-in ASR “listens to” and fine-tunes the output of the text-to-speech algorithms before they’re heard by the listener.
Significant engine changes between CR2008 and CR2009 that affected the 2009 Blizzard Challenge are:
· Phoneme categories – CR2008 grouped phonemes into four categories when using ASR to calculate target costs: voiced plosive, unvoiced plosive, voiced non-plosive, and unvoiced non-plosive. [2] CR2009 increased the number of categories to sixteen, resulting in more accurate target cost calculations.
· Half-phone target cost base – CR2008 and CR2009 synthesize using half units. CR2008 incorporated the unit’s context-dependent ASR score as the base value for the unit-selection score, using the same value for both the left and right halves of the unit. CR2009 uses ASR to calculate unique values for the left and right halves of the unit.
· Explicitly calculated left/right context mismatches – In CR2008, if a unit with mismatched left/right phoneme context was used, a score penalty (calculated using ASR) would be applied. CR2009 still does this, in most cases. However, CR2009 identifies units with mismatched left/right contexts that are likely substitutes, and then explicitly calculates the unit-substitution’s score against the ASR context-dependent phoneme model of the desired unit. Doing so eliminates the need to use the estimated mismatch score penalty, producing a more-accurate target cost for likely substitutions.
· Join cost calculation using a triangular window – In CR2008, join costs were calculated using an impulse window, using ASR to compare frames to the immediate left and right of the join. [2] CR2009 uses a triangular window with a width of around half a phoneme.
· Target and estimated join costs calculated per voice – CR2008’s target and estimated join costs were calculated from 10 hours of recordings of my own voice. CR2009 calculates the target and estimated join costs from the Roger voice, improving target cost accuracy when synthesizing the Roger voice. The resulting target-cost values are significantly different to those derived from my own voice.
· TD-PSOLA target costs – It is well known that TD-PSOLA distorts the original signal, and “as a rule of thumb” [4, pp 416] can only be used to double or halve the duration of a unit, and increase or decrease its F0 by half an octave. CR2009 used ASR to determine how much changing the duration and/or F0 using TD-PSOLA affected the speech quality, and included this into the target cost. I won’t detail the CR2009 algorithm to derive TD-PSOLA costs because recently-improved algorithms (see below) have made the TD-PSOLA target-cost algorithm employed in CR2009 obsolete.
· “Snap to” F0 and duration affected by target costs – Stereotypical unit-selection synthesizers maintain the original units’ F0 and duration. Due to transplanted prosody requirements for games, the CircumReality engine modifies F0 and duration using TD-PSOLA. To minimize the signal distortions created by TD-PSOLA, CR2008 and CR2009 adjust the F0 and duration of a unit away from the values requested by the prosody model, and towards the F0 and duration of the original unit. This approach reduces prosody quality to improve acoustic quality. In CR2009, the amount of adjustment is controlled by the target-cost penalty per octave shift of F0 or doubling of duration. For example: Phoneme groups that have higher F0 target-cost penalties, meaning that they don’t sound as good when pitch shifted using TD-PSOLA, have their F0 weighted more towards the unit’s original F0.
· TD-PSOLA – CR2008 synthesized audio using time-domain stretched PCM, causing audible artifacts when F0 was modified even slightly. [2] CR2009 used TD-PSOLA, resulting in improved acoustic synthesis.
· Prosody model – The ASR-calculated F0, duration, and energy target costs are employed by the prosody model to estimate how perceptible altering a syllable’s F0, duration, or energy is. Such values are only a guestimate, to be used until better approaches for calculating prosody-specific F0, duration, and energy target costs can be devised.
Other significant improvements to CR2009 didn’t affect the 2009 Blizzard Challenge:
· Small voices – The CR2008 and CR2009 Blizzard-Challenge voices used around 350,000 units and several gigabytes on disk. Text-to-speech voices for games must be smaller, around 8000 units and 30 megabytes. CR2008 eliminated units by retaining the 8000 best-scoring, most-commonly-used phoneme sequences, based on an average of the unit’s ASR-generated base score. CR2009 includes estimated join costs for non-contiguous units between the first two and last two phonemes of the sequences, also calculated by ASR. For example: Estimated join costs around the phoneme “t” are always high, due to coarticulation. Conversely, “m” has low estimated join costs. As a result, triphone sequences with “t” occurring in the middle of sequence are more likely to be included in the 8000-unit voice than triphone sequences with an “m” in the middle.
· Randomly generate several sentences and select the “best sounding” one – CR2009 can randomly generate several different prosodies for a given sentence, along with randomly selected alternative pronunciations from the lexicon. All variations are synthesized, and the synthesized sentence with the best unit-selection score is spoken. This technique was not used for the Blizzard Challenge 2009 because it proved to be too slow, and produce only a marginal improvement in speech quality.
· TD-PSOLA target cost improvements – After submitting the synthesized results for Blizzard Challenge 2009, further improvements were made to calculating TD-PSOLA target costs. The CircumReality engine now uses pitch-detection confidence scores from the training data to adjust the “per octave shift” and “per duration doubling” target costs of TD-PSOLA. In general, the higher the pitch-detect confidence, the lower the TD-PSOLA target cost. ASR is used to automatically calculate the TD-PSOLA per-octave/duration target costs as a function of pitch-detection confidence.
TD-PSOLA and the “talking speech recognizer” philosophy significantly improved CircumReality’s MOS and similarity scores.
However, CircumReality’s 2009 “word error rate” was still very high. (See figure 3.)
Figure 3: Word error rate
The high word-error rate is unexpected, and needs to be explored. Several possible causes for the word-error rate will be investigated:
· The ASR algorithm may not be accurate enough – CircumReality’s ASR algorithms haven’t yet been tested for accuracy. Future plans involve writing a phoneme-based ASR accuracy test, and then fine-tuning constants and algorithms to improve ASR accuracy.
· Join cost triangle window size – Join costs are calculated by comparing the boundaries of a join using a triangle window of approximately half a phoneme. Since the units of the beam search are “frame comparison error * time”, a shorter-duration window causes the join cost to affect the beam search more. Thus, a shorter-duration triangle window encourages more non-contiguous units. The word-error-rate listening test was based on short, confusable word pairs, implying that a longer triangle-window would encourage contiguous units and reduce the word error rate.
· Join cost vs. target cost weight – In CR2009, the join cost score is combined with the target cost score using a weight of 1.0. The reasoning for using 1.0 may need to be re-examined; a different weight might make more logical sense. Increasing the join cost weight would encourage contiguous units.
The CircumReality text-to-speech engine was created for the CircumReality game [5], and all work on the engine is done with game development in mind. The 2009 Blizzard Challenge has provided some information relevant to game development:
· Minimizing voice-recording costs – While 10,000 sentences for each voice would be ideal, recording so many sentences isn’t possible on a small financial budget. 1000 sentences appear to be the minimum number of recordings needed before MOS declines dramatically. (See figure 2.) CircumReality’s low MOS for ES1 (generated from 100 sentences) illustrates the rapid drop-off in quality resulting from less data. Due to the CircumReality game’s low budget, most voice data will come from free public sources where 1-hour voiced databases are common, but 10-hour voice databases are rare.
· Quality vs. quantity – Another voice-design tradeoff is whether the game should ship with a couple of large voices generated from 10 hours of speech, and then use extensive voice transformations to create voices for one hundred characters, or to ship with 20-40 smaller voices and employ only minor voice transformation to cover the one hundred characters. HMM synthesis using highly-parameterized speech audio would enable both significant voice transformations and small voices, with HTS 2007’s ES1 matching its EH1 and EH2 scores. (See figure 2.) But, from the Blizzard Challenge 2008’s overall results, it is obvious that “the best” concatenative PSOLA synthesizers still have a significantly higher MOS for EH2 (small voices) than “the best” parameterized-speech HMM synthesizers have for EH1. These results show that 20-40 smaller PSOLA voices will produce a better over-all MOS than highly-parameterized voices.
· Prosody – Listening to the restaurant query-responses sub-test of the 2009 Blizzard Challenge clearly demonstrated how poor CR2009’s prosody was. Unfortunately, separate test results weren’t provided, so no numerical comparison is possible; I suspect CircumReality’s MOS would be relatively higher (compared to other entrants) if the restaurant-query test results were removed. However, better prosody is not that critical for games. Long sentences such as those used in the restaurant-query sub-test don’t appear often in games; players get bored listening to even medium-length sentences. Furthermore, half of the sentences that are spoken during gameplay can be “prerecorded” with transplanted prosody, overriding the lower-quality synthesized prosody.
· Expert speech listener bias – “Speech experts” consistently gave all entrants the same or higher MOS and similarity scores. In terms of gameplay, this implies that players will “grow accustomed to” text-to-speech voices over time. (See figure 4.)
Figure 4: Expert
speech listener bias
[1] Rozak, M., “Text-to-speech Designed for a
Massively Multiplayer Online Role-Playing Game (MMORPG)”, in The Blizzard
Challenge 2007, Bonn, Germany. mXac. Online:
http://festvox.org/blizzard/bc2007/index.html, accessed on 19 July 2009.
[2] Rozak, M., “CircumReality functionality
delta: Blizzard Challenge 2007 to 2008”, in The Blizzard Challenge 2008,
Brisbane, Australia. mXac. Online: http://festvox.org/blizzard/ blizzard2008.html,
accessed on 19 July 2009.
[3] Karaiskos, V., King, S., Clark, R., Mayo, C.,
“The Blizzard Challenge 2008”, in The Blizzard Challenge 2008, Brisbane,
Australia. University of Edinburgh. Online: http://festvox.org/ blizzard/blizzard2008.html,
accessed on 19 July 2009.
[4] Taylor, P., Text-to-Speech Synthesis, 2009, New York,
Cambridge University Press.
[5] Rozak, M., “What is
CircumReality?”, mXac. Online: http://www.CircumReality.com, accessed on 19
July 2009.
I have suspended work on my www.CircumReality.com graphical-MUD.
(Multiplayer interactive-fiction game.)
A 1.5-gigabyte download of the source-code is
available for free, from this web-site, http://www.CircumReality.com/mXacSourceCode.zip.
A piece-wise download of the .zip file is available at:
1. http://www.CircumReality.com/mXacSourceCode_part1.zip
2. http://www.CircumReality.com/mXacSourceCode_part2.zip
3. http://www.CircumReality.com/mXacSourceCode_part3.zip
4. http://www.CircumReality.com/mXacSourceCode_part4.zip
5. http://www.CircumReality.com/mXacSourceCode_part5.zip
6. http://www.CircumReality.com/mXacSourceCode_part6.zip
7. http://www.CircumReality.com/mXacSourceCode_part7.zip
The .zip file includes source-code for:
·
Text-to-speech – As described in my
Blizzard papers. (http://www.synsig.org/index.php/Blizzard_Challenge)
Free source-code is also available for the “Festival” text-to-speech engine. Just
search for it on the internet. (http://festvox.org/festival/, http://www.cstr.ed.ac.uk/projects/festival/)
·
A multi-core 3D renderer
·
A 3D editor.
To work-around a “This is only a beta” limiter, just set
your computer’s date-time year to 2007.
·
A graphical MUD client (Multiplayer
interactive-fiction client)
·
Multiplayer interactive-fiction server – An LPMUD-like
server with an IDE (integrated development-environment). (http://en.wikipedia.org/wiki/LPMud)
The MUD scripting-code is
targeted less-towards combat, and more towards interactive-fiction and
NPC-interaction.
You should look at the built-in NPC artificial-personality scripting-code (sometimes called “artificial-intelligence”),
visible through the IDE (integrated development environment).
Multiplayer Interactive-Fiction Scripting-Code
This is ALL (hopefully) of the script-code for
my multiplayer interactive-fiction game. You can look at this code to come-up
with ideas for how to make more interesting non-player characters (NPCs) in the
virtual-world or adventure games.
If I am missing anything (which I may be), then
look through my source-code or the scripting source-code (available through the
CircumReality Integrated Development Environment). (BUGBUG – Download link)
Used to import functions and methods from C++
code.
<p>
A function or method can "import" code from a C/C++ library.
To do this it uses the following code:
</p>
<p align=center><table><tr><td><font face=courier>
#importfunc <italic>FuncName</italic>
</font></td></tr></table></p>
<p>
"FuncName" is replaced by a function name that is supported
by the C/C++ application using MIFL.
</p>
Steps you through the process of writing a
"Hello world" application.
<p><bold><big>Comparing MIFL to other languages</big></bold></p>
<p>
MIFL is similar to <bold>C, C++, Java, and Flash's Actionscript</bold>.
If you've used either of these before then you'll find it
very easy to pick up. If you have used other programming
languages, such as Visual Basic, Python, Inform, or TADs then
MIFL will take a bit more getting use to, although it's
still just a programming language.
</p>
<p>
If you haven't done programming before then I suggest you get
a good book on programming C, C++, Java, or Flash and learn
from that. These tutorials may not be detailed enough to
help you learn how to program from scratch; you're welcome
to try though.
</p>
<p/><p><bold><big>Libraries</big></bold></p>
<p>
A MIFL program (also called a project) is pieced together
using one or more <bold>libraries</bold>. A library is a
collection of code (and documentation) that the programmer
things fits together. Each library is one file saved on disk.
</p>
<p>
Building a program from a collection of libaries rather than
one monolithic file has advantages; it allows the programmer to
share code between different projects.
</p>
<p>
For example: If a programmer writes a lot of code to do fancy
math (like Fourier Transforms) then he/she could put that in
it's own library, separate from the libary for the application-specific
code. Then, if the programmer (or someone else) ever needs
Fourier Transforms in another program, he/she just has to
include the Fourier Transform library in the new program.
</p>
<p>
A MIFL program (project) will contain a minimum of two libraries:
</p>
<ul>
<li>
<bold>Standard library</bold> - This library contains necessary
functions, globals, method definitions, and property definitions
for using MIFL. It should <bold>always</bold> be part of the
project. (Unless you're a really advanced user and know what you're
doing.)
</li>
<li>
<bold>Context-specific library</bold> - This library defines
functions, etc. necessary for the context of the application.
For example: If you're using MIFL for an interactive fiction
application, then an "Interactive Fiction Library" that's common
to all IF applications will be needed.
</li>
<li>
<bold>Application specific library</bold> - This is one or more
libraries specific to your program.
For example: If you're using MIFL for IF, this would be the
code for the IF's rooms, items, and NPCs.
</li>
</ul>
<p>
To set up the libraries you need for the "Hello world" application:
</p>
<ol>
<li>
Click on the <bold>Add new libraries</bold> menu item under
the <bold>Libraries</bold> menu.
</li>
<li>
In the "Add a new library" page, look for the "Standard library" under
"Built in libraries". If it's not there (which it shouldn't be) then you
already have the standard library included (automatically) in your
project. If the button for "Standard library" exists then <bold>press it</bold> to
include the library.
</li>
<li>
You will also need a library to store the code that displays "Hello world!".
To do this, press <bold>"New library file"</bold> in the "Add a new library" page.
</li>
<li>
In the dialog asking for the file name, <bold>type in a name</bold>, such
as "HelloWorld.mfl".
</li>
<li>
Once you've added the file, you should see the center title of the
page change to "HelloWorld.mfl" (in yellow text) to indicate that any
changes you make affect that library. If it doesn't, then
select <bold>HelloWorld.mfl</bold> from the <bold>Libraries</bold> menu.
</li>
</ol>
<p/><p><bold><big>Functions</big></bold></p>
<p>
The next thing you need to do is create a <bold>function</bold>. In C, C++, or
Java you type in code like "int Function (int a, double b) {}" to declare
a function. It's much easier to create a function in MIFL.
</p>
<ol>
<li>
Select the <bold>"Add a new function"</bold> menu item
underneath the <bold>"Functions"</bold> menu.
</li>
<p>
A new function will be created and the "Modify function" page will be
displayed so you can edit it.
</p>
<li>
In the "Modify function" page, type in a new <bold>"Name"</bold> for
the function; change it to "HelloWorld".
</li>
<p>
If you don't see any entry for "Name" then press
the <bold>"Description"</bold> tab in the menu.
</p>
<p>
Function names (and object, method, variable, etc. names) <bold>cannot
contain any spaces.</bold> They can only contain letters, numbers (but not
the first character) and underscores.
</p>
<li>
While you're at it, you can also <bold>type in</bold> a one-line
description, like "Displays Hello World!", and even a longer multi-line
description. The descriptions and help categories are used like
comments in front of functions. They are also automatically
included in the help system; all you need to do to see them is
press the <bold>"Rebuild help"</bold> button at the bottom of the help
page.
</li>
</ol>
<p>
When you are modifying a function (or object or method) the menu
will be extended to include "Description", "Parameters",
"Code", and "Misc." Pressing these switches to different
sub-pages in the function.
</p>
<ul>
<li>
<bold>Description</bold> - Lets you modify the function's
name, description, and where it appears in help.
</li>
<li>
<bold>Parameters</bold> - Use this to control what parameters
are passed into the function and what it returns.
</li>
<li>
<bold>Code</bold> - This page lets you write the code and
test it.
</li>
<li>
<bold>Misc.</bold> - Miscellaneous options, such as moving
the function to a different library.
</li>
</ul>
<p/><p><bold><big>Writing code</big></bold></p>
<p>
To actually write the code for the "HelloWorld"
function, press the <bold>"Code"</bold> tab. The page will
change to show you an edit box for the code.
</p>
<p>
Type in:
</p>
<p align=center><table><tr><td><font face=courier>
Trace ("Hello world!");
</font></td></tr></table></p>
<p>
Your "Hello world" program is written. All it does
is pass the string, "Hello world!", into the Trace() function.
The Trace() function (from the "Standard Library") just
writes the string to the debug log. You'll see this in a moment.
</p>
<p>
To verify that you haven't made any typing mistakes,
press the <bold>"Test compile this function"</bold> button.
The menu will be extended to show you any errors or warnings;
clicking on one an error or warning will highlight the code
where the error/warning occurred.
</p>
<p/><p><bold><big>Testing your application</big></bold></p>
<p>
To test your application:
</p>
<ol>
<li>
Select the <bold>Compile</bold> menu item under
the <bold>Misc</bold> menu. This is different than the
"Test compile this function" option in the code window
because it compiles all the functions and objects, as
opposed to just the one you're working on.
</li>
<p>
If there were any errors then fix them.
</p>
<li>
When you're finished with the errors press
the <bold>"Hide errors"</bold> link.
</li>
<li>
Select the <bold>"Test compiled code"</bold> menu
item under <bold>"Misc"</bold>.
</li>
</ol>
<p>
The "Test compiled code" page will appear. Its window is
divided into several panes:
</p>
<ul>
<li>
<bold>Right</bold> - The right side of the page lists
all the objects, global variables, etc. running in the
program. Clicking on "Functions" will expand the list
to show all the functions; your "HelloWorld" function
will be among them.
</li>
<li>
<bold>Left, top</bold> - The single-line edit box lets
you type in code to run. You'll be using this in a minute.
</li>
<li>
<bold>Left, center</bold> - The "Watch variables" section
shows you the values of variables in globals, objects, and
within a function (while you're debugging). For example:
One variable included in the Standard Library is "Pi";
if you type "Pi" into one of the fields you'll see
"3.14159..." appear next to it.
</li>
<li>
<bold>Left, bottom</bold> - The "Debug trace" shows you
any run-time errors and anything passed into
the <bold>Trace()</bold> function call, which HelloWorld()
uses.
</li>
</ul>
<p>
To run your HelloWorld function:
</p>
<ol>
<li>
<bold>Type</bold> "HelloWorld()" into the single-line
edit field at the top.
</li>
<li>
Press <bold>"Run this"</bold>. (Directly under the field.)
</li>
</ol>
<p>
That's it, your program has been run. You can see the
results in the "Debug trace" window below. It should
contain:
</p>
<p align=center><table><tr><td><font face=courier>
Execute code: "HelloWorld()"<br/>
Hello world!Returns: undefined
</font></td></tr></table></p>
<p>
The "Hello world!" on the second line is the output of the
Trace() call. "Returns: undefined" tells you what the
function returned, which was nothing.
</p>
<p/><p><bold><big>Comments</big></bold></p>
<p>
I haven't used any comments in my sample code, but
rest assured, MIFL allows for comments. There are two types,
and act just like comments in C, C++, Java, and ActionScript:
</p>
<ul>
<li>
<bold>// comment</bold> - If you use a double-slash then
all the text to the end of the line will be ignored by
the compiler.
</li>
<li>
<bold>/* comment */</bold> - If you use a /* then
all the text until the next */ will be ignored by
the compiler.
</li>
</ul>
Discusses how variables and operations work.
<p><bold><big>Variables</big></bold></p>
<p>
MIFL variables behave almost exactly like those in Flash's
ActionScript because they are "un-typed".
They are very different to variables in Java, C, and C++,
which are "typed".
</p>
<p>
In MIFL, a variale it a storage slot for data that can
store <bold>any</bold> type of data. MIFL handles several
data types. The important ones are:
</p>
<ul>
<li>
<bold>Number</bold> - This is a floating point number.
For those of you familiar with C, it's a "double".
(Example: 123.435)
</li>
<li>
<bold>Boolean</bold> - This can either
be <bold>"True"</bold> or <bold>"False"</bold>.
</li>
<li>
<bold>Character</bold> - A single character, such as
'a' or '!'. Characters are identified by the
single-quotes (') surrounding it.
</li>
<li>
<bold>Null</bold> - This can only be one
value, <bold>Null</bold>. It represents an empty value.
</li>
<li>
<bold>Undefined</bold> - This can only be one
value, <bold>Undefined</bold>. Undefined implies that the
value is not only empty, but it wasn't even initialized.
</li>
<li>
<bold>String</bold> - A series of characters.
Strings are identified by the quotes (") surrounding them.
(Example: "Hello world!")
</li>
<li>
<bold>List</bold> - A list if an array of variables.
Lists are identified by the brackets ([ ]) that surround it.
(Example: [1, 3, "hi there", true] is a list with the 1st
element contain the number 1. 2nd is 3. 3rd is the
string "hi there", and 4th is the boolean, true.
</li>
<li>
<bold>Object</bold> - This is reference to an object.
I'll explain this in detail when I get into objects.
</li>
<li>
<bold>Function</bold> - A reference to a function.
In C or C++ this is equivalent to a pointer to a function.
</li>
<li>
<bold>Method</bold> - A reference to a method within an object.
In C or C++ this is equivalent to a pointer to a method.
</li>
<li>
<bold>String table entry</bold> - I'll discuss this later.
</li>
<li>
<bold>Resource</bold> - I'll discuss this later.
</li>
</ul>
<p>
Unlike C, C++, and Java, a variable can <bold>change data types</bold> at
run-time. So, even if "x" were initialized to 54.45, it
could be changed to "Hi there" later one.
</p>
<p/><p><bold><big>Using variables in functions</big></bold></p>
<p>
Creating a variable in a function is easy. In your
HelloWord() code, just add the following line:
</p>
<p align=center><table><tr><td><font face=courier>
var x = 54.3534;<br/>
Trace (x);
</font></td></tr></table></p>
<p>
If you then <bold>Run this</bold> the "Debug trace" will
also display "54.3534".
</p>
<p>
(Actually, it probably won't do this unless you first
run <bold>"Compile"</bold> under <bold>"Misc."</bold> again.)
</p>
<p>
In C or C++ you would have typed in "double x = 54.3534" or
"int x = 53" or "char x[] = "hello there!"" because variables
are "typed". You don't need to do this in MIFL. It's
just "var x = 54.3534", "var x = 53", or "var x = "hello there!"".
</p>
<p>
As with C, C++, Java, and ActionScript, you can <bold>also create
several variables at once</bold> using "var x,y,z;" or
"var x=43, y="hi there", z=true;".
</p>
<p>
<bold>Note:</bold> All names (including variables)
are case <bold>insensative</bold> in MIFL. That means that
varible "x" is equivalent to "X", and functon "HelloWorld"
is equivalent to "helloWORLD". C, C++, and Java work
just the opposite, being case sensative, and differentiating
between HelloWorld() and helloWORLD().
</p>
<p/><p><bold><big>Operators</big></bold></p>
<p>
Once you have variables, you'll want to do operations on them,
such as addition and assignment. The operators in MIFL are
almost identical to C, C++, Java, and ActionScript. They are (in
order of precedence):
</p>
<table width=100%>
<tr>
<td width=33%><bold>.</bold></td>
<td width=66%>Object property or method access, like "object.property".</td>
</tr>
<tr>
<td width=33%><bold>[ ]</bold></td>
<td width=66%>Creating a list or indexing into a list or string.</td>
</tr>
<tr>
<td width=33%><bold>( )</bold></td>
<td width=66%>Parenthesis or parameters for a function call.</td>
</tr>
<tr><td/></tr>
<tr>
<td width=33%><bold>++ --</bold></td>
<td width=66%>
Increment or decrement a variable. If ++ (or --) is to the right of
the variable the variable's value is put on the stack and then incremented/decremented.
If ++ (or --) is to the left, the variable is first incremented or
decremented, and then the variable's value is put on the stack.
</td>
</tr>
<tr>
<td width=33%><bold>-</bold></td>
<td width=66%>Negation, such as in "-(x+y)"</td>
</tr>
<tr>
<td width=33%><bold>~</bold></td>
<td width=66%>Bitwise not.</td>
</tr>
<tr>
<td width=33%><bold>!</bold></td>
<td width=66%>Logical not.</td>
</tr>
<tr><td/></tr>
<tr>
<td width=33%><bold>* / %</bold></td>
<td width=66%>Multiply, divide, and modulo.</td>
</tr>
<tr><td/></tr>
<tr>
<td width=33%><bold>+ -</bold></td>
<td width=66%>Addition and subtraction.</td>
</tr>
<tr><td/></tr>
<tr>
<td width=33%><bold><< >></bold></td>
<td width=66%>Bitwise shift left or right.</td>
</tr>
<tr><td/></tr>
<tr>
<td width=33%><bold>< <= > >=</bold></td>
<td width=66%>Less than, less than or equal, greater than, greater than or equal</td>
</tr>
<tr><td/></tr>
<tr>
<td width=33%><bold>== !=</bold></td>
<td width=66%>Equality or not equality</td>
</tr>
<tr>
<td width=33%><bold>=== !==</bold></td>
<td width=66%>
Strict equality or strict not equality. These <bold>do not</bold> appear
in C, C++, or Java. They test equality but are strict about it.
Because of automatic conversions between numbers and strings,
43 == "43" <bold>but</bold> 43 !== "43", although 43 === 43. Strict equality does not
do automatic conversions.
</td>
</tr>
<tr><td/></tr>
<tr>
<td width=33%><bold>&</bold></td>
<td width=66%>Bitwise AND</td>
</tr>
<tr><td/></tr>
<tr>
<td width=33%><bold>^</bold></td>
<td width=66%>Bitwise XOR</td>
</tr>
<tr><td/></tr>
<tr>
<td width=33%><bold>|</bold></td>
<td width=66%>Bitwise OR</td>
</tr>
<tr><td/></tr>
<tr>
<td width=33%><bold>&&</bold></td>
<td width=66%>Logical AND</td>
</tr>
<tr><td/></tr>
<tr>
<td width=33%><bold>||</bold></td>
<td width=66%>Logical OR</td>
</tr>
<tr><td/></tr>
<tr>
<td width=33%><bold>? :</bold></td>
<td width=66%>
Contional. Example: "(1 < 2) ? 5 : 3" will return "5".
</td>
</tr>
<tr><td/></tr>
<tr>
<td width=33%><bold>= += -= *= /= %= <<= >>= &= ^= |=</bold></td>
<td width=66%>Various forms of assignment</td>
</tr>
<tr><td/></tr>
<tr>
<td width=33%><bold>,</bold></td>
<td width=66%>Comma. Separates parameters, list elements, and (sometimes) lines of code.</td>
</tr>
</table>
<p/>
<p>
You can combine variables and operators to make your
HelloWorld() function do some calculations. Try tying
in and then running:
</p>
<p align=center><table><tr><td><font face=courier>
var x = 18;<br/>
var y = 6;<br/>
var z = x * y + 89;<br/>
z *= 2;<br/>
Trace (z);
</font></td></tr></table></p>
<p/><p><bold><big>Debugging</big></bold></p>
<p>
MIFL includes a debugger that allows you to step through the
code and see the flow of control along what variables
are at each step of the execution.
</p>
<p>
To test this:
</p>
<ol>
<li>
<bold>Type in</bold> the code (as per above).
</li>
<li>
Select <bold>"Compile"</bold> underneath
the <bold>"Misc."</bold> menu.
</li>
<li>
Select <bold>"Test compiled code"</bold> underneath
the <bold>"Misc."</bold> menu.
</li>
<li>
In the "Test compiled code" page, check
the <bold>"Debug"</bold> checkbox.
</li>
<li>
Type in <bold>"HelloWorld()"</bold> and
press <bold>"Run this"</bold>.
</li>
<li>
The "Debug" page will be displayed with "HelloWorld()" being
highlighted. Press <bold>"Step in"</bold> to step in.
</li>
<p>
The Debug page will not display the code from the HelloWorld()
function.
</p>
<li>
To see the values of the variables, x, y, and z, type
in <bold>"x", "y", and "z"</bold> (each on their own line)
into the "Watch variables" list. At first each will
be "undefined" since they haven't been set.
</li>
<li>
Press <bold>"Step"</bold> to step to the next line of code.
If, for example, you stepped over the "x = 18;" line then
you'll see the watched variable, x, change to "18".
</li>
<li>
Keep on pressing <bold>"Step"</bold> until the code finishes
executing. You can watch how the variables change with each
step.
</li>
<p>
If your function called another function (other than Trace()),
you could press <bold>"Step in"</bold> to debug that function.
(It's not worth stepping into Trace() because it just ends up
calling a built-in function, which can't be debugged.)
</p>
<p>
You can also press <bold>"Run"</bold> to run the code without
stepping, or <bold>"Exit"</bold> to stop running the code.
</p>
</ol>
<p/><p><bold><big>Passing parameters to functions</big></bold></p>
<p>
Just like other languages, you can pass parameters to functions.
To do this:
</p>
<ol>
<li>
Select <bold>"HelloWorld"</bold> under
the <bold>"Functions"</bold> menu.
</li>
<li>
Press the <bold>"Parameters"</bold> tab in the "Modify
function" page.
</li>
<li>
Type in a parameter name, <bold>"Input"</bold> and
a description for the meaning of the parameter.
</li>
<p>
If you want more than one parameter, just
press <bold>"Add more parameter slots"</bold> to add another
slot. When you fill that one in, you can press "Add more
parameter slots" again to add more.
</p>
<li>
Type in a short desciption for the <bold>Return value</bold> of
HelloWorld(), such as "Does complicated math on Input and then
returns that."
</li>
<li>
Switch to the <bold>"Code"</bold> tab.
</li>
<li>
<bold>Type in</bold> the following code:
</li>
<p align=center><table><tr><td><font face=courier>
var x = 18 + Input;<br/>
var y = 6;<br/>
var z = x * y + 89;<br/>
z *= 2;<br/>
Trace (z);<br/>
return z;
</font></td></tr></table></p>
<li>
<bold>Compile</bold> the project.
</li>
<li>
Select the <bold>"Test compiled code"</bold> menu item.
</li>
<li>
Type in <bold>"HelloWorld (53)"</bold> into the edit field,
and then press <bold>"Run this"</bold>.
</li>
<p>
If you have debuggin turned on you'll be able to watch the
value of "Input", in addition to that of the variables,
"x", "y", and "z".
</p>
<p>
If you look at the debug log, you'll see that the Trace(z)
outputs "1030" and then the return value is printed out
as "Returns: 1030".
</p>
</ol>
<p>
In addition to the parameters you add to the function, MIFL
functions (and methods) support two "hidden" parameters:
</p>
<ul>
<li>
<bold>Arguments</bold> - This is a list of all the parameters
passed into the function. In the case of the HelloWorld()
example it would be [53]. If more parameters were passed in
the list would have more elements.
</li>
<li>
<bold>This</bold> - When you run HelloWorld(), This will be
set to <bold>Undefined</bold>. However, if HelloWorld() were
a method in an object, This would contain a reference to the
object. C++, Java, and ActionScript have a similar construct.
</li>
</ul>
<p/><p><bold><big>Global variables</big></bold></p>
<p>
Like other languages, MIFL supports "global variables" which
are variables that can be accessed by any function or method, and
which persist from one function call to the next.
</p>
<p>
To create a global variable:
</p>
<ol>
<li>
Select the <bold>"Add new variable"</bold> menu item
under the <bold>"Variables (global)"</bold> menu item.
</li>
<li>
In the "Modify global variable" page that appears, type
in a <bold>"Name"</bold> for the variable, such as "MyGlobal".
</li>
<li>
Under "Initialized to", <bold>type in</bold> the value you'd
like the global to being with, such as 54.456. If you leave
this blank the global will initially be set to <bold>Undefined</bold>.
</li>
<p>
You can also type in a short and long description, just like
with the function.
</p>
<li>
Re-<bold>Compile</bold> the project and return
to <bold>Test compiled code</bold>.
</li>
<li>
<bold>Type</bold> "MyGlobal" into the watch list to see
it's value, which should be 54.456.
</li>
<p>
You can also see the value of MyGlobal by clicking
on the <bold>"Global variables..."</bold> link on the right
side of the test window. This will display the list of all
global variables. Hovering the mouse over the "MyGlobal" entry
will show it's value in a tooltip.
</p>
<li>
You can change the value of MyGlobal by writing some code
into HelloWorld(), and then compiling and running it, or
by <bold>typing</bold> "MyGlobal = 12345" in the test
edit window and pressing <bold>"Run this"</bold>.
</li>
</ol>
<p>
<bold>Warning:</bold> A global variable (or object property)
can initialize itself (with "initialized to") to use the contents of another global
variable. This has two potential problems:
</p>
<ul>
<li>
<bold>Undefined</bold> - If the global variable that's being
referenced has not yet been initialized due to the
semi-arbitrary initialization errors of globals, then the
global variable will be <bold>undefined</bold>. This problem
won't happen when initializing an object's property to a global
variable because globals are always initialized first.
</li>
<li>
<bold>Fracturing</bold> - If a global variable or property is
initialized by referencing a global variable which is in turn
initialized by a string or list, then the new global variable
or property will contain the correct string/list <bold>but</bold> the
string/list will be different pieces of data.
</li>
<p>
Hence, if
global variable A is initialized to <bold>"Hello"</bold>, and global variable
B is initialized to <bold>A</bold>, then global variable
B will also be initialized to <bold>"Hello"</bold>.
However, If A.StringAppend(" there") is called, A will
be "Hello there", but B will still be "Hello".
</p>
</ul>
Tutorial about how to use strings in MIFL.
<p><bold><big>Strings</big></bold></p>
<p>
Strings and lists are similar to ActiveScript's strings and arrays.
They are very different from strings in C and C++; C/C++ don't
even contain lists.
</p>
<p>
To create a string in code, just use quotes around some text,
such as <bold>"Hello world!"</bold>.
</p>
<p/><p><bold><big>String operators</big></bold></p>
<p>
Many of the operators work with strings:
</p>
<ul>
<li>
<bold>x = "Hello" + " there!"</bold> - This fills x
with <bold>"Hello there!"</bold>, concatenating two or
more strings together.
</li>
<li>
<bold>x += "Hello"</bold> - This is interpreted as "x = x + "Hello"".
</li>
<li>
<bold>"abacus" < "zebra"</bold> - The <, <=,
> >=, ==, and === operators do a case <bold>sensative</bold> comparison
of the two strings. You need to use StringCompare() to do a case
insensative compare.
</li>
<li>
<bold>x = "Hello"[2]</bold> - Fills x in with the 3rd character (0 being the
first character). x = 'l'.
</li>
<li>
<bold>x = "Hello"; x[2] = 'a';</bold> - This fills x with "Hello",
but then changes the 3rd character to 'a', resulting
in "Healo". If the index is beyond the end of the string, the
string will be extended with spaces.
</li>
</ul>
<p>
Depending upon the operator and the location of the string
(whether it's to the left or right of the operator), strings will
be converted to other data types (such as numbers) or other
data types will be converted to strings.
</p>
<p>
Some examples:
</p>
<ul>
<li>
<bold>x = "89" + 42;</bold> - The number is converted to a string.
This results in the <bold>string</bold> "8942".
</li>
<li>
<bold>x = 42 + "89";</bold> - The string is converted to a number.
This results in the <bold>number</bold> 131.
</li>
</ul>
<p>
For more details on type conversions, see the reference section.
</p>
<p/><p><bold><big>String "methods"</big></bold></p>
<p>
Strings support "methods", which are functions that automatically
modify the string that they appear after. Example: To use the
method "StringAppend" with the string in variable x,
just use "x.StringAppend()".
</p>
<p>
Below is a partial list of methods supported by strings.
A complete list can be found under the "Reference" section of
help, in "Data types", "Strings".
</p>
<ul>
<li>
<bold>StringAppend</bold> - Append one string onto the end of another.
</li>
<li>
<bold>StringCompare</bold> - Case <bold>insensative</bold> comparison between strings.
</li>
<li>
<bold>StringInsert</bold> - Insert one string into another.
</li>
<li>
<bold>StringLength</bold> - Returns the number of characters in a string.
</li>
<li>
<bold>StringSearch</bold> - Search through the string for a matching string.
</li>
<li>
<bold>StringSlice</bold> - Cut a portion of the string out and into a new string.
</li>
</ul>
<p/><p><bold><big>String escape sequences</big></bold></p>
<p>
If you wish to include quotes within a string, you'll need to
use some "escape sequences":
</p>
<p align=center><table>
<tr>
<td width=33% align=center><bold>\"</bold></td>
<td width=66%>
Places a quote in the string.
Example: <bold>"This is a \"quote\"."</bold> will be displayed as <bold>This is a "quote".</bold>
</td>
</tr>
<tr>
<td width=33% align=center><bold>\'</bold></td>
<td width=66%>
Single quote.
</td>
</tr>
<tr>
<td width=33% align=center><bold>\n</bold></td>
<td width=66%>
Newline. Places a line-break between characters.
</td>
</tr>
<tr>
<td width=33% align=center><bold>\r\n</bold></td>
<td width=66%>
Alternate way of writing a newline.
</td>
</tr>
<tr>
<td width=33% align=center><bold>\t</bold></td>
<td width=66%>
Tab separator.
</td>
</tr>
<tr>
<td width=33% align=center><bold>\\</bold></td>
<td width=66%>
Backslash
</td>
</tr>
<tr>
<td width=33% align=center><bold>\</bold>ddd</td>
<td width=66%>
Octal character, where d is a digit from 0..7.
</td>
</tr>
<tr>
<td width=33% align=center><bold>\x</bold>ddd</td>
<td width=66%>
Hex character, where d is a digit from 0..f.
</td>
</tr>
</table></p>
<p/><p><bold><big>String reference counting</big></bold></p>
<p>
When you create a string, in the background MIFL allocates some
memory for the string. Associated with the memory is a "reference count"
that lets MIFL know if the string is still remembered by
any variables, parameters, properties, etc. If the MIFL
script "forgets" about the string then the memory is automatically
deleted. This ensures that you won't get memory leaks from stings
that are no longer in use.
</p>
<p>
It also causes some noteworthy behavior. Look at the following
code:
</p>
<p align=center><table><tr><td><font face=courier>
var x = "Hello world!";<br/>
var y = x;<br/>
x.StringAppend ("more text");<br/>
return y;
</font></td></tr></table></p>
<p>
This function will return a string, "Hello world!more text".
Here's why:
</p>
<ol>
<li>
"var x = "Hello world!"" creates a string and then stores
a <bold>reference</bold> to it in x.
</li>
<li>
"var y = x" does <bold>not</bold> copy the string, but copies
the reference to the string. Therefore, x and y <bold>refer
to the same string.</bold>
</li>
<li>
"x.StringAppend("more text")" appends "more text" onto the
string that <bold>both</bold> x and y are referencing.
</li>
<li>
"return y" just returns another reference.
</li>
</ol>
<p>
This can get you if you're not careful because you may assume
that "y = x" will copy the string, but it only copies the
reference. Then, if you modify x you'll find that y is also
modified, or vice versa.
(Conversely, this can be a very useful feature.)
</p>
<p>
If you want y to contain a copy of the string (so that modifying
it won't affect x) then use:
</p>
<p align=center><table><tr><td><font face=courier>
y = x.Clone();
</font></td></tr></table></p>
<p>
This duplicates the string referred to by variable x,
and sets y to the refer to the new string.
</p>
<p>
Note: <bold>Concatenation with the + operator</bold> creates a
copy of the strings, which is different behavior than StringAppend(),
which modifies a string in place.
</p>
Tutorial about how to use lists in MIFL.
<p><bold><big>Lists</big></bold></p>
<p>
A "list" is a data type that stores zero or more values in
sequence. A list can store any mix of values.
Lists are similar to ActiveScript's arrays.
C/C++ don't even contain lists.
</p>
<p>
To create a list in code, use the left bracket ([), followed
by some comma separated values, and finished up with the
right bracket (]). A list can contain other lists.
</p>
<p>
Some examples of lists are:
</p>
<ul>
<li>
<bold>[1, 54, 43, 23]</bold> - A list containing 4 numbers.
</li>
<li>
<bold>["Mike", "George", "Amy", "Chris"]</bold> - A list containing 4 names.
</li>
<li>
<bold>[1, "hello", 43, true]</bold> - This list has a mix of data types,
including numbers, strings, and booleans.
</li>
<li>
<bold>[1, ["hello", 43], true]</bold> - This list contains another
list as it's 2nd element.
</li>
<li>
<bold>[]</bold> - An empty list, with no elements.
</li>
</ul>
<p/><p><bold><big>List operators</big></bold></p>
<p>
Many of the operators work with lists:
</p>
<ul>
<li>
<bold>x = [53,324] + 435</bold> - This fills x
with <bold>[53, 324, 435]</bold>, appending data onto the
list.
</li>
<li>
<bold>x = [53,324] + [435, 89]</bold> - This fills x
with <bold>[53, 324, 435, 89]</bold>, appending the elements
from the second list onto the first. If you wanted to
generate [53, 324, [435, 89]] you'd need to call
[53, 324].ListConcat ([435, 89]).
</li>
<li>
<bold>x += [54,34]</bold> - This is interpreted as "x = x + [54,34]".
</li>
<li>
<bold>[1,2] < [2,2]</bold> - The <, <=,
> >=, ==, and === operators do element-by-element comparisons
of the two lists. Any string comparisons are case <bold>sensative</bold>.
You need to use ListCompare() to do a case
insensative compare.
</li>
<li>
<bold>x = [54,34]; y = x[1];</bold> - Fills y in with the 2rd element (0 being the
first element). y = 34.
</li>
<li>
<bold>x = [54,34]; x[1] = "hello";</bold> - This fills x with [54,34],
but then changes the 2rd element to "hello", resulting
in [54, "hello"]. If the index is beyond the end of the list, the
list will be extended with <bold>undefined</bold> values.
</li>
</ul>
<p/><p><bold><big>List "methods"</big></bold></p>
<p>
Lists support "methods", which are functions that automatically
modify the list that they appear after. Example: To use the
method "ListAppend" with the list in variable x,
just use "x.ListAppend()".
</p>
<p>
Below is a partial list of methods supported by lists.
A complete list can be found under the "Reference" section of
help, in "Data types", "Lists".
</p>
<ul>
<li>
<bold>ListAppend</bold> - Append a list or other data type onto the end of the list.
</li>
<li>
<bold>ListCompare</bold> - Case <bold>insensative</bold> comparison between lists.
</li>
<li>
<bold>ListInsert</bold> - Insert one list into another.
</li>
<li>
<bold>ListNumber</bold> - Returns the number of elements in the list.
</li>
<li>
<bold>ListSort</bold> - Sorts a list alphabetically or numerically.
</li>
<li>
<bold>ListSlice</bold> - Cut a portion of the list out and into a new list.
</li>
</ul>
<p/><p><bold><big>List reference counting</big></bold></p>
<p>
When you create a list, in the background MIFL allocates some
memory for the list. Associated with the memory is a "reference count"
that lets MIFL know if the list is still remembered by
any variables, parameters, properties, etc. If the MIFL
script "forgets" about the list then the memory is automatically
deleted. This ensures that you won't get memory leaks from lists
that are no longer in use.
</p>
<p>
It also causes some noteworthy behavior. Look at the following
code:
</p>
<p align=center><table><tr><td><font face=courier>
var x = [1,4,6];<br/>
var y = x;<br/>
x.ListAppend (99);<br/>
return y;
</font></td></tr></table></p>
<p>
This function will return a list, [1,4,6,99].
Here's why:
</p>
<ol>
<li>
"var x = [1,4,6]" creates a list and then stores
a <bold>reference</bold> to it in x.
</li>
<li>
"var y = x" does <bold>not</bold> copy the list, but copies
the reference to the list. Therefore, x and y <bold>refer
to the same list.</bold>
</li>
<li>
"x.ListAppend(99)" appends the element, 99, onto the
list that <bold>both</bold> x and y are referencing.
</li>
<li>
"return y" just returns another reference.
</li>
</ol>
<p>
This can get you if you're not careful because you may assume
that "y = x" will copy the list, but it only copies the
reference. Then, if you modify x you'll find that y is also
modified, or vice versa.
(Conversely, this can be a very useful feature.)
</p>
<p>
If you want y to contain a copy of the list (so that modifying
it won't affect x) then use:
</p>
<p align=center><table><tr><td><font face=courier>
y = x.Clone();
</font></td></tr></table></p>
<p>
This duplicates the list referred to by variable x,
and sets y to the refer to the new list. It <bold>also</bold>
duplicates all the data within the list, so any sublists or
strings will also be cloned.
</p>
<p>
Note: <bold>Concatenation with the + operator</bold> creates a
copy of the list, which is different behavior than ListAppend(),
which modifies a list in place.
</p>
Describes flow-of-control commands.
<p>
MIFL supports several different ways to control program flow,
including if-then-else, while, do-while, for, and switch statements.
I won't go into detail because the constructs are standard to
all languages.
</p>
<p/><p><bold><big>Debug</big></bold></p>
<p>
While technically not a flow-of-control statement, "Debug" fits
best in this category. When a "Debug" statement is encountered,
<bold>and</bold> the program is being run with debugging enabled,
the debuggin user interface will be brought up so the user
can step through the code.
</p>
<p>
In C or C++ this is similar to placing an Assert() in the code
and causing the development environment to bring up the debugger.
</p>
<p align=center><table><tr><td><font face=courier>
<bold>Debug;</bold>
</font></td></tr></table></p>
<p>
From within the for() loop you can use the
following:
</p>
<ul>
<li>
<bold>Break;</bold> - Exits out of the statement.
</li>
<li>
<bold>Continue;</bold> - Exits out of the statement, runs
the increment expression, and
returns to evaluating the conditional expression.
</li>
</ul>
<p/><p><bold><big>For</big></bold></p>
<p>
The for(;;) {}; loop works the same as C, C++, Java, and ActionScript:
</p>
<p align=center><table><tr><td><font face=courier>
<bold>for (</bold><italic>init expression</italic><bold>,</bold><br/>
&tab;<italic>conditional expression</italic><bold>,</bold><br/>
&tab;<italic>increment expression</italic><bold>)</bold><br/>
&tab;<italic>statement</italic><bold>;</bold><br/>
</font></td></tr></table></p>
<p>
From within the for() loop you can use the
following:
</p>
<ul>
<li>
<bold>Break;</bold> - Exits out of the statement.
</li>
<li>
<bold>Continue;</bold> - Exits out of the statement, runs
the increment expression, and
returns to evaluating the conditional expression.
</li>
</ul>
<p/><p><bold><big>If, then, else</big></bold></p>
<p>
You can use the if-then-else statement to test conditionals.
The format is the same as C, C++, Java, and ActionScript:
</p>
<p align=center><table><tr><td><font face=courier>
<bold>if (</bold><italic>expression</italic><bold>)</bold><br/>
&tab;<italic>statement</italic><bold>;</bold><br/>
</font></td></tr></table></p>
<p align=center><table><tr><td><font face=courier>
<bold>if (</bold><italic>expression</italic><bold>)</bold><br/>
&tab;<italic>if true statement</italic><bold>;</bold><br/>
<bold>else</bold><br/>
&tab;<italic>if false statement</italic><bold>;</bold>
</font></td></tr></table></p>
<p>
You can also use the <bold>? :</bold> operator, as in
other languages.
</p>
<p align=center><table><tr><td><font face=courier>
<italic>expression</italic> <bold>?</bold><br/>
&tab;<italic>if true statement</italic><br/>
<bold>:</bold><br/>
&tab;<italic>if false statement</italic><bold>;</bold>
</font></td></tr></table></p>
<p/><p><bold><big>Return</big></bold></p>
<p>
Return works the same as C, C++, Java, and ActionScript.
Calling return without an expression will
return <bold>Undefined</bold>.
</p>
<p align=center><table><tr><td><font face=courier>
<bold>return </bold><italic>expression</italic><bold>;</bold>
</font></td></tr></table></p>
<p align=center><table><tr><td><font face=courier>
<bold>return;</bold>
</font></td></tr></table></p>
<p/><p><bold><big>Switch</big></bold></p>
<p>
The switch(){}; statement is the same as C, C++, Java, and ActionScript:
Unlike C and C++, the switch() statement can compare strings, objects,
and any other data type, not just numeric values.
</p>
<p align=center><table><tr><td><font face=courier>
<bold>switch (</bold><italic>expression</italic><bold>) {</bold><br/>
<bold>case</bold> <italic>expression 1</italic><bold>:</bold><br/>
&tab;<italic>statement</italic><bold>;</bold><br/>
&tab;<bold>break;</bold><br/>
<bold>case</bold> <italic>expression 2</italic><bold>:</bold><br/>
&tab;<italic>statement</italic><bold>;</bold><br/>
&tab;<bold>break;</bold><br/>
<bold>default:</bold><br/>
&tab;<italic>statement</italic><bold>;</bold><br/>
&tab;<bold>break;</bold><br/>
<bold>};</bold>
</font></td></tr></table></p>
<p>
From within the switch() statement loops you can use the
following:
</p>
<ul>
<li>
<bold>Break;</bold> - Exits out of the statement.
</li>
</ul>
<p/><p><bold><big>While and do-while</big></bold></p>
<p>
The while() {}; statement and do {} while (); statements
allow for looping.
The format is the same as C, C++, Java, and ActionScript:
</p>
<p align=center><table><tr><td><font face=courier>
<bold>while (</bold><italic>expression</italic><bold>)</bold><br/>
&tab;<italic>statement</italic><bold>;</bold><br/>
</font></td></tr></table></p>
<p align=center><table><tr><td><font face=courier>
<bold>do</bold><br/>
&tab;<italic>if true statement</italic><bold>;</bold><br/>
<bold>while (</bold><italic>expression</italic><bold>)</bold>
</font></td></tr></table></p>
<p>
From within the while() and do()-while loops you can use the
following:
</p>
<ul>
<li>
<bold>Break;</bold> - Exits out of the statement.
</li>
<li>
<bold>Continue;</bold> - Exits out of the statement and
returns to evaluating the expression.
</li>
</ul>
Tutorial on how to define and create objects.
<p><bold><big>MIFL objects vs. other languages</big></bold></p>
<p>
MIFL is an object oriented language. It supports objects that are
similar, but not exactly the same as those in C++, Java, and
ActionScript.
</p>
<p>
The basic premise of an object is a collection of related data
(called properties)
along with code (called methods) that are used to access or
modify that data.
</p>
<p>
For example: In an interactive fiction setting, two
objects might be a "Parrot" and a "Penguin". Each would probably
support the properties of "Weight" and "Height", but only
the parrot object would have "FlyingSpeed" properties. They
would both have "Walk" methods, but only the parrot would have
a "Fly" method, and the penguin would have a "Swim" method.
</p>
<p>
MIFL objects also include:
</p>
<ul>
<li>
<bold>Super-classes</bold> - The ability to "inherit" properties
and methods from other objects. Both "Parrot" and "Penguin" might
inheret some attributes and methods from the "Bird" super-classs
(also an "object"). That might in turn inheret properties from
the "Animal" super-class.
</li>
<li>
<bold>Multiple inheritance</bold> - Objects can inherit properties
and methods from more than one superclass. For example: The parrot
might not only inherit from the "Bird" class, but from
the "TalkingCreature" class. The penguin object might inherit
from the "Bird" and "SwimmingCreature" class.
</li>
<li>
<bold>Private methods and properties</bold> - Objects have
methods and properties that can only be accessed from within
the object. Usually, private methods and properties are used
for "safety" reasons to ensure that foreign code doesn't
call the private method or set a private variable that
will "mess things up".
</li>
<li>
<bold>Public methods and properties</bold> - All public methods
and properties are globally defined. For example: Once the Walk() public
method is defined, all objects that use the method Walk() must
conform to the definition, including the number of parameters
passed in. If they don't, compilation will fail. Conversely,
C++ and Java allow a different Walk() to exist for each class.
</li>
<li>
<bold>Dynamically created methods and properties (called "layers")</bold> - MIFL
allows methods and properties to be added and removed dynamically
to objects. C++ does not allow this.
</li>
<li>
<bold>Timers</bold> - In MIFL, timers are associated with objects.
C and C++ treat timers differently.
</li>
<li>
<bold>Containership</bold> - In MIFL, an object can be contained
by an object, and contain its own objects. Containership
is very useful for interactive fiction. C, C++, Java, and
ActionScript do not have containership.
</li>
<li>
<bold>Blurring of the line between object and class</bold> - In
C++, Java, and ActionScript, a class is a definition for an object,
and an object is an instance of a class. To create an
object the programmer must call "new object". MIFL also has this,
but an class can automatically be created as an object and
referenced in a global variable of the same name.
This is useful for interactive fiction.
</li>
<li>
<bold>Unique ID</bold> - Every MIFL object has an ID
(128-bit GUID) that uniquely identifies it from all other objects
created on this or other systems. The unique ID makes it easier
to write objects to disk and then restore them. It also
provides protection if a program access an object after it
has been deleted.
</li>
</ul>
<p/><p><bold><big>Creating an object/class</big></bold></p>
<p>
Creating an object (or class of objects) is easy:
</p>
<ol>
<li>
Select the <bold>"Add new object"</bold> menu
item in the <bold>"Objects"</bold> menu.
</li>
<li>
In the "Modify object" page that appears,
type in a <bold>"Name"</bold> for the object, such as "Parrot".
It must be unique.
</li>
<li>
You can also <bold>type in descriptions</bold> for the object and under
what help categories it will be placed.
</li>
</ol>
<p>
The "Modify object" page has several tabs:
</p>
<ul>
<li>
<bold>Description</bold> - You just visited this.
</li>
<li>
<bold>Super-classes</bold> - This lets you specify the
super-classes of the object.
</li>
<li>
<bold>Properties</bold> - The properties page lets you add
or remove properties to the object.
</li>
<li>
<bold>Methods</bold> - The methods page lets you add
or remove methods to the object.
</li>
<li>
<bold>Misc.</bold> - This page lets you move the object
to a different library, and other settings.
</li>
</ul>
<p/><p><bold><big>Making it an object</big></bold></p>
<p>
MIFL makes it easy to automatically create an object from
a class:
</p>
<ol>
<li>
Click on the <bold>"Super-classes"</bold> tab.
</li>
<li>
Check <bold>"Automatically create as an object"</bold>.
</li>
<p>
In future, if you want this object to be created within
another object then select the other object from
the drop-down immediately below the check-box.
You could use this for interactive fiction to create objects
that are contained with or held by other objects, such as
a "Peanut" being held by the "Parrot" object.
At the
moment you can't do this because you only have one object.
</p>
<p>
You can also change the ID object (underneath the super-class).
In general, you only need to do this if you want to make sure
that an object whose definition you deleted and then recreated
will still use the same ID. This doesn't happen too often.
</p>
</ol>
<p>
If you didn't check the "Automatically create as an object" button
then your definition would be more of a class (in C++ and Java
terms).
</p>
<p>
To test that your object actually works:
</p>
<ol>
<li>
<bold>"Compile"</bold> your project.
</li>
<li>
Switch to the <bold>"Test compiled code"</bold> page.
</li>
<li>
Click on the <bold>"Objects..."</bold> link on the right
side of the page.
</li>
<p>
This will expand to list all the objects (not classes).
</p>
<li>
Click on the <bold>"Parrot"</bold> object.
</li>
<p>
Underneath the "Parrot" object will be sub-tables
for properties, methods, and timers supported by the
object. (Right now they should be empty.) You can also hover
your mouse over the "Contains" and "Layers" entries to
see what objects your object contains (or is contained by),
and what layers it has. Right now there won't be anything
in the contains list, and only one layer, "Parrot".
</p>
</ol>
<p/><p><bold><big>Adding properties</big></bold></p>
<p>
To add public properties to your object, you must first
define a public property. To do this:
</p>
<ol>
<li>
Select the <bold>"Add new property"</bold> menu
item under <bold>"Property defs"</bold>.
</li>
<li>
In the "Property definition" page, type in
a <bold>"Name"</bold> for your property, such
as <bold>"FlyingSpeed"</bold>.
</li>
<p>
<bold>Note:</bold> If you leave the property
value <bold>blank</bold> then the property
will <bold>not</bold> be added to the object. However,
if the property is inhereted from a superclass (discussed
later) then that property will be used.
</p>
<li>
You can also type in <bold>descriptions</bold> and help
categories so that other authors will know what the
"FlyingSpeed" property means.
</li>
</ol>
<p>
Now that you have created the public property definition,
you can add the public property to your Parrot object:
</p>
<ol>
<li>
Select the <bold>"Parrot"</bold> menu item
from the <bold>"Objects"</bold> menu.
</li>
<li>
Click on the <bold>"Properties"</bold> tab.
</li>
<li>
Under the "Property name" column, select
the <bold>"FlyingSpeed"</bold> property from the drop-down
list box.
</li>
<p>
Once you have selected the property the description will
automatically be filled in. You cannot modify the description
of a public property from the properties page.
</p>
<li>
To have the property automatically default to a value, type
one in under <bold>"Initial value"</bold>. In C++ you would set the value
in the object's constructor. You can do the same under MIFL,
but setting it in the GUI is easier and better when you're
trying to save the object to disk (since MIFL then knows if
the property has changed from its initial value; MIFL doesn't
save the property unless it has changed).
</li>
<li>
You can test the property out by doing a <bold>Compile</bold> and
then running <bold>Test compiled code</bold>. You can either
see the property in the right-hand column, or
bold type <bold>Parrot.FlyingSpeed</bold> in the watch list.
</li>
<p>
Tip: If you change the <bold>"Scope"</bold> of the watch list to
"Parrot" then you only need to type in "FlyingSpeed".
</p>
</ol>
<p>
You can add a private property in a similar manner:
</p>
<ol>
<li>
Select <bold>"Parrot"</bold> from the <bold>"Objects"</bold> menu.
</li>
<li>
Switch to the <bold>"Properties"</bold> tab.
</li>
<li>
Under the "Properties, private" section, type in
a <bold>"Name"</bold> for your private property. (Remember,
for a public property you selected from the public property
list.)
</li>
<li>
Set a <bold>"Initial value"</bold> just like the public property.
</li>
<li>
Type in a <bold>"Description"</bold> of the private property.
This doesn't appear in help, but it's always good to document
what things mean.
</li>
<li>
You can view the private property the same as a public
property. Be aware that "Lantern.YourPrivateProperty"
will <bold>not</bold> work unless you set the watch
"Scope" to "Lantern". Private properties can only be accessed
from within the object/class.
</li>
</ol>
<p/><p><bold><big>Adding methods</big></bold></p>
<p>
To add a public method to your object, you must first
define a public method. To do this:
</p>
<ol>
<li>
Select the <bold>"Add new method"</bold> menu
item under <bold>"Method defs"</bold>.
</li>
<li>
In the "Modify method definition" page, type in
a <bold>"Name"</bold> for your property, such
as <bold>"Fly"</bold>.
</li>
<li>
You can also type in <bold>descriptions</bold> and help
categories so that other authors will know what the
"Fly" method does.
</li>
<li>
The "Method method definition" page is just like the
"Modify function" page that you familiarized yourself
with when you created HelloWorld(). The only difference
is that it <bold>doesn't</bold> provide a tab for
modifying the code, since a method definition does <bold>not</bold> include
code; that's specific to an object.
</li>
</ol>
<p>
Now that you have created the public method definition,
you can add the public method to your Parrot object:
</p>
<ol>
<li>
Select the <bold>"Parrot"</bold> menu item
from the <bold>"Objects"</bold> menu.
</li>
<li>
Click on the <bold>"Methods"</bold> tab.
</li>
<li>
At the "Methods, public" section, select
the <bold>"Fly"</bold> method from the drop-down list box.
</li>
<li>
Press the <bold>"Add a new public method"</bold> button just
above it. This will add the public method that you have selected
from the list box.
</li>
<p>
This switches to the "Modify method" page. Again it's
just like the "Modify function" page, except that
while you can modify the code, you cannot change the
method's name, description, or parameters since these are
set by the method definition.
</p>
<li>
Switch to the <bold>"Code"</bold> tab and type in some
code to run so you can see the method working:
</li>
<p align=center><table><tr><td><font face=courier>
Trace ("Fly() method called");<br/>
return FlyingSpeed;
</font></td></tr></table></p>
<li>
You can test the property out by doing a <bold>Compile</bold> and
then running <bold>Test compiled code</bold>.
</li>
<li>
Type <bold>"Parrot.Fly()"</bold> in the edit field and
press <bold>"Run this"</bold> to execute the code.
</li>
<p>
You'll see the Trace() in the Debug Trace display, along
with the value returned from Parrot.Fly(), which should be
the value of FlyingSpeed.
</p>
</ol>
<p>
You can add a private property in a similar manner:
</p>
<ol>
<li>
Select <bold>"Parrot"</bold> from the <bold>"Objects"</bold> menu.
</li>
<li>
Switch to the <bold>"Methods"</bold> tab.
</li>
<li>
Under the "Methods, private" section, press
the <bold>"Add a new private method"</bold> button.
</li>
<p>
This switches to the "Modify method" page. Again it's
just like the "Modify function" page. Unlike the public
method modification, you can change everything, including
the name, decription, parameters, and code.
</p>
<li>
In the <bold>"Description"</bold> page type in
a unique <bold>"Name"</bold> for the method.
</li>
<li>
Switch to the <bold>"Code"</bold> tab and type in some
code to run so you can see the method working.
</li>
<li>
You can <bold>verify that the private method works</bold> the same
way you tested the public method. However, you will
need to set the watch variable's "Scope" to "Parrot"
in order to access the private method.
</li>
</ol>
<p/><p><bold><big>Dynamically creating and deleting objects</big></bold></p>
<p>
You can also dynamically create and destroy objects:
</p>
<ol>
<li>
<bold>Compile</bold> the project and switch
to the <bold>Test compiled code</bold> page.
</li>
<li>
In the top edit field, type <bold>"MyGlobal = new Parrot"</bold> and
press <bold>"Run this"</bold>.
</li>
<p>
Note: This assumes that you kept your test global variable,
"MyGlobal", in the code. If not, you'll need to add it.
</p>
<li>
A second parrot will have been added. You can see it
by clicking on <bold>"Objects..."</bold> in the right
side of the "Test compiled code" page. The objects list will
have two parrots, one named "Parrot" and another named
"Parrot" with lots of numbers next to it.
</li>
<p>
The parrot with all the numbers next to its name is the one
you created. The numbers are the new parrot object's ID.
Objects that are automatically created (such as the original
Parrot) do not show this number.
</p>
<li>
To delete the newly created parrot object, type <bold>"delete MyGlobal"</bold> and
press <bold>"Run this"</bold>.
</li>
<p>
Note: You're not deleting the global variable "MyGlobal", but
the object referenced by the variable. There are ways to delete
global variables, but they won't be covered in the tutorial.
</p>
</ol>
How to create superclasses and subclasses.
<p>
Objects can inherit methods and properties from other classes/objects.
As stated previously, this is useful for the example Parrot object
that can inherit methods and properties from the soon-to-be-created
"Bird" class, which in turn could inherit from an "Animal" class, etc.
</p>
<p>
Each class provides a way of providing common methods and
properties for a group of like individuals.
A "superclass" is a class (or object) with methods or properties
that are inherited by other classes. A "subclass" is a class
that inherits from a superclass.
</p>
<p/><p><bold><big>Creating a superclass</big></bold></p>
<p>
To create the "Bird" superclass:
</p>
<ol>
<li>
<bold>Create an object/class</bold> called "Bird", using the
same general actions as those used to create "Parrot".
</li>
<li>
Unlike Parrot, the <bold>"Automatically create as an object"</bold>,
found under <bold>"Super-classes"</bold> in
the "Modify object" page, should <bold>not</bold> be checked.
(It could be checked, but then you'd end up with both a Parrot
object and a Bird object.)
</li>
<li>
As before, <bold>add the "FlightSpeed" public property</bold> to
the Bird class, and type in an initial value, like 10.
</li>
<li>
<bold>Add the "Fly" public method</bold> to the Bird class and provide
that outputs a Trace("This is Fly from the bird class") so you
can tell that it was called.
</li>
</ol>
<p>
That's it; you've created a bird class.
(Of course, this is a very simple example so you can understand
the process.)
</p>
<p/><p><bold><big>Using a superclass</big></bold></p>
<p>
To get the Parrot object to use the Bird class:
</p>
<ol>
<li>
<bold>Switch to the</bold> "Modify object" page for the Parrot
object, and press the <bold>"Super-classes"</bold> tab if
it isn't already visible.
</li>
<li>
Press the <bold>"Add class"</bold> button.
</li>
<p>
The "Add class(es)" page will appear. It lists all of the
classes in the project.
</p>
<li>
<bold>Check</bold> the "Bird" class.
</li>
<li>
<bold>Return</bold> to the "Modify object" page for the
Parrot object. You'll see that "Bird" is now listed as
one of the superclasses.
</li>
<p>
If you compiled now though, the Parrot class would be unchanged
because it already supports the "FlightSpeed" property and "Fly"
method. Because of inheritance rules, if a sub-class (aka: the
Parrot object) supports a method or property from a super-class (aka:
the Bird class) then the sub-class's properties or methods
have priority.
</p>
<p>
Therefore, you need to remove both the "FlightSpeed" property
and "Fly" method from the Parrot object (not the Bird class).
</p>
<li>
Switch to the <bold>Properties</bold> tab and <bold>change</bold> the
property drop-down for "FlightSpeed" to "blank", effectively
deleting the property.
</li>
<li>
Switch to the <bold>Methods</bold> tab, <bold>hold
the control key down</bold>, and click on
the <bold>"Fly"</bold> method button. This will delete the
method.
</li>
<p>
Since Parrot no longer supports the properties and methods
from the Bird class, they will be inherited directly.
</p>
<li>
To test your changes, <bold>compile</bold> and
run the <bold>Test compiled code</bold>. The FlightSpeed
property will be the value initialized in the Bird class,
and the Fly method will call the code from the Bird class.
</li>
</ol>
<p/><p><bold><big>Why use superclass?</big></bold></p>
<p>
So what? That didn't really do much. Now that you have a
Bird class that defined flying, here's how you can use it:
</p>
<ul>
<li>
<bold>Override a property's inheritance</bold> - If you add the
"FlightSpeed" property back into the Parrot class but use
a new initial value, you can make Parrots fly slower or
faster than the "typical" bird.
</li>
<li>
<bold>Override a method's inheritance</bold> - If you add the
"Fly" method back into the Parrot class with different code
you can make parrot's fly differentlu than "typical" birds.
</li>
<li>
<bold>Other subclasses</bold> - Since most types of birds (swans,
ducks, hawks, etc.) are really just small variations on the
basic "bird" concept, you can quickly create many different
types of birds (sub-classes) based on the "Bird" super-class.
Where the type of bird (swan, duck, etc.) varies from the
super-class "Bird", you provide new properties and/or methods.
</li>
<li>
<bold>Multiple inheritance</bold> - As stated in the last
tutorial, you can have the Parrot object inherit not only
from the Bird class, but also from a "TalkingCreature" class.
Parrots, humans, aliens, and even robots would be
members of the "TalkingCreature" class, sharing properties
and code specific to creature that talk.
</li>
</ul>
<p>
I won't lead you through all these examples step-by-step
because they're all variations on a theme.
</p>
<p/><p><bold><big>Superclass order</big></bold></p>
<p>
If an object is a sub-class to two or more super-classes
you may need to prioritize the super-classes so that any
overrides (for properties or methods) are correct. Such
situations will arise if two or more superclasses support
the same properties or methods.
</p>
<p>
The <bold>"Super-classes"</bold> tab in the "Modify object"
lets you control the priority of all the object's
superclasses.
</p>
<p/><p><bold><big>Method inheritance</big></bold></p>
<p>
Earlier I said that if a class (the Parrot object) provides
code for a method (the "Fly" method),
any code from the superclass (the "Fly" method in the Bird class)
will be ignored.
</p>
<p>
I lied. This isn't always the case.
</p>
<p>
Some method definitions allow the methods to coexist.
You can have a call
to a method first call the method in the lowest sub-class (the
Parrot), then the next sub-class (the Bird class), and the
next one (the Animal class), etc. You can reverse the order
and call the methods from the highest super-class first, down
to the lowest sub-class. You can also allow this chain to
be stopped based on the returns of the methods. This creates
four possibilities, in addition to the "just override inheritance"
that's the default.
</p>
<p>
You might want to do this to accomplish:
</p>
<p>
<bold>Cumulative effects</bold> - If you wanted to allow the
bird with an egg inside it to fly slower, because it weighs more,
you could temporarily decrease the "FlightSpeed" property and
then try and remember to increase it when the egg was laid.
</p>
<p>
Alternatively, you could create a "FlightSpeedGet" method that
just did the following:
</p>
<p align=center><table><tr><td><font face=courier>
return (parameter[0] = FlightSpeed);
</font></td></tr></table></p>
<p>
This would return the bird's flying speed, and would be
the "official" way of getting the value; you could even make
the property private.
</p>
<p>
The variable, parameter, is automatically defined to be the
list of parameters passed into the method. Since the
FlightSpeedGet() call takes no parameters, the list will
initially be empty. Setting paramter[0], however, changes this.
Normally, this would have no effect... however:
</p>
<p>
You could also create a super-class called "Pregnant". It would
support the FlightSpeedGet() method too, but its code would be:
</p>
<p align=center><table><tr><td><font face=courier>
return (parameter[0] *= 0.75);
</font></td></tr></table></p>
<p>
If you had the FlightSpeedGet() method call the super-class
first, parameter[0] would be set to the original flight
speed. Once that returned, the next class down (Pregnant) would
be called; this would retrieve the value "stashed away" in
parameter[0], decrease it to account for the egg's extra weight,
and return that.
</p>
<p>
Thus, to make a pregnant Parrot you'd include the Pregnant
class as a super-class. You could also add and remove the
pregnancy later using layers (discussed later).
</p>
<p>
This idea could be extended further by making an "Unhealthy"
super-class that likewise reduces flying speed. If a Parrot
is both Pregnant and Unhealthy it will fly even slower.
</p>
<p>
<bold>Sometimes capture, sometimes pass it on</bold> - Alternatively,
you could tell the method definition to call all the methods
from sub-to-super-class or super-to-sub-class, repeating until
one of them comes up knows how to deal with the situation.
</p>
<p>
Perhaps you might need a public method, LikeFood (FoodName), that returns
true if the object (aka: Parrot or Bird) likes a food item (passed
in as the parameter "FoodName") or false if it doesn't.
</p>
<p>
The generic bird would return true for (FoodName == "birdseed") ||
(FoodName == "peanut"), but
false for (FoodName == "flower").
</p>
<p align=center><table><tr><td><font face=courier>
return (FoodName == "birdseed") || (FoodName == "peanut");
</font></td></tr></table></p>
<p>
Some parrots like flowers, in addition to to birdseed. You could
provide have the LikeFood() method override, cut-and-paste the
code from the Bird's LikeFood() method, and then modify it.
Or, you could have specify (in the method definition) that it
should call from the highest priority methods (the Parrot's)
to the lowest priority methods (the Bird's), trying
LikeFood() for each one. If any returns a value which is not
undefined, then that return is used. Otherwise, the next in
line is called.
</p>
<p>
The Parrot's LikeFood() method would then be:
</p>
<p align=center><table><tr><td><font face=courier>
if (FoodName == "flower")<br/>
&tab;return true;<br/>
else<br/>
&tab;return undefined; // pass it on
</font></td></tr></table></p>
<p>
This makes for a very elegant solution, especially if new
foods are added later on.
</p>
<p>
Both the "cumulative effects" and "sometimes capture, sometimes
pass on" approaches to methods are supported by
MIFL. To have a method definition use either of these:
</p>
<ol>
<li>
Switch to the <bold>"Misc."</bold> tab in the "Modify
method definition" page for the method you wish to
change.
</li>
<li>
Change the <bold>"Overrides"</bold> setting to the method
you wish to use. The default is "Call only the highest
priority method", but the other four possibilities are
also listed.
</li>
<li>
Make sure to <bold>document</bold> this behavior so that
anyone using that method definition will know what to expect.
</li>
</ol>
<p/><p><bold><big>Recommended properties and methods</big></bold></p>
<p>
Realistically, the flying speed for every bird is different,
and while a few birds will fly at the FlightSpeed (property)
as initialized by the Bird superclass, most birds will fly
at different speeds.
</p>
<p>
To make sure that anyone creating a new type of bird will
override the default FlightSpeed property for the bird, or
consciously decide not to override it, you can can add
a setting to the "Bird" class so that if any other class
is derived from it (such as a "Robin"), an entry for
the FlightSpeed property will automaically be added to the
Robin class's list of public properties.
</p>
<p>
To do this:
</p>
<ol>
<li>
Switch to the <bold>"Misc."</bold> tab in the "Modify object"
page for the "Bird" class.
</li>
<li>
Press the <bold>"Recommend properties and methods for
sub-classes"</bold> button.
</li>
<p>
The "Recommended" page will appear, displaying the properties
and methods that are automatically added to any sub-classes
created from the Bird class.
</p>
<li>
<bold>Check</bold> the "FlightSpeed" property.
</li>
</ol>
<p>
Once the "FlightSpeed" property is checked, anytime the Bird
class is included as a super-class of another object (or class),
the "FlightSpeed" property will automatically be added to
the object's list of public properties.
</p>
Describes what layers are and how to use them.
<p>
MIFL includes an object concept called <bold>"layers"</bold>.
They allow an program to add new super-classes to an object
at run-time. They also allow new methods and property get/set
calls to be added at run-time.
</p>
<p>
The primary use for layers is to allow objects to dynamically
change their beahaviors (methods) and properties in an
easy-to-program way. As an example from the last tutorial,
layers could be used to add a "Pregnant" or "Unhealthy" superclass
to a "Parrot" object. The layer allows all the methods
associated with being Pregnant or Unhealthy to be added
at run-time, and removed when the state no longer applied.
</p>
<p>
To see how this works, first create a superclass that can be layered.
</p>
<ol>
<li>
<bold>Create</bold> an "Unhealthy" super-class.
</li>
<li>
<bold>Add the public method,</bold> "Fly", to the Unhealthy super-class.
The code should be different than before:
</li>
</ol>
<p align=center><table><tr><td><font face=courier>
Trace ("An unhealthy bird can't fly.");<br/>
return false;
</font></td></tr></table></p>
<p>
That's all you need to test out the concept. You'll know
if the unhealthy superclass has been added to the Parrot
object because calling Fly() will output different text.
</p>
<p>
To test the Unhealthy superclass:
</p>
<ol>
<li>
<bold>Compile</bold> the project and
then run the <bold>Test compile code</bold> menu item.
</li>
<li>
Type <bold>"Parrot.Fly()"</bold> and press <bold>"Run this"</bold>.
</li>
<p>
From the debug trace you'll see that the Fly() code for the Parrot
comes from the Bird class. The parrot is still healthy.
</p>
<li>
To add the Unhealthy class to the parrot object,
type <bold>"Parrot.LayerAdd ("SickLayer", "Unhealthy", 1);"</bold> and
press <bold>"Run this"</bold>.
</li>
<p>
The "SickLayer" parameter is a name that you given to the layer
so you can later idenfity which layer it is. (After all, you might
have several layers.) The "Unhealthy" parameter is the name of the
class. And 1 is the priority of the class; higher numbers have
higher priority over other classes. The class used to create
an object always has priorty 0, and is usually the lowest
priority.
</p>
<li>
Now, type <bold>"Parrot.Fly()"</bold> and press <bold>"Run this"</bold>.
</li>
<p>
The Fly() code from the Unhealthy class will be run instead.
The debug trace will indicate that the parrot is too sick to fly.
</p>
<li>
You can also see the layer by using the right column
of the test display. Click on <bold>"Objects..."</bold>,
then <bold>"Parrot"</bold>, and finally, hover the mouse
over the <bold>"Layers"</bold> entry.
</li>
<li>
To remove the layer,
type <bold>"Parrot.LayerRemove (0);"</bold> and
press <bold>"Run this"</bold>.
</li>
<p>
The 0 parameter is the index into the layer. If you used 1
instead, the "Parrot" class would be removed from the object,
making it an unhealthy nothing. Under normal circumstances
you'd look through all the layers in an object to find
the one named "SickLayer" and then delete that one. This
is just a tutorial though.
</p>
</ol>
<p>
There are quite a few layer methods you can call. Some
of them are:
</p>
<ul>
<li>
<bold>LayerAdd</bold> - Adds a new layer to an object.
</li>
<li>
<bold>LayerGet</bold> - Gets information about a layer
</li>
<li>
<bold>LayerNumber</bold> - Returns the number of layers in an object.
</li>
<li>
<bold>LayerRemove</bold> - Removes a layer from an object.
</li>
</ul>
<p/><p><bold><big>Adding individual methods</big></bold></p>
<p>
You can also add individual methods to a layer (although
adding classes (aka: groups of methods) is recommended).
To add an individual method:
</p>
<ol>
<li>
In the "Test compiled code" page,
type <bold>"Parrot.LayerMethodAdd (0, "Speak", Trace)"</bold> and
then press <bold>"Run this"</bold>.
</li>
<p>
The 0 parameter causes the method to be added to the layer
at index location 0. "Speak" is the name that the new method
will use. And, Trace, is the function (or method) to call when
"Speak" is called.
</p>
<p>
At the moment, you cannot compile code on-the-fly, so the
only code you can add is that which has already been compiled,
either in a function, this object, or another. (I can add
the ability to code on-the-fly, if necessary.)
</p>
<li>
Because "Speak" is not a compiled token, you can't
just type "Parrot.Speak ()". However, you can call
the method using, <bold>"Parrot.MethodCall ("Speak", "Polly wants
a cracker."</bold> and pressing <bold>"Run this"</bold>.
</li>
<p>
The "Speak" parameter is the name of the method to call; it
could also be "Fly", or other methods supported by the Parrot.
"Polly wants a cracker." is the parameter passed into the "Speak"
method, which just ends up calling Trace().
</p>
<p>
Therefore, you should see "Polly wants a cracker." appear
on your debug trace.
</p>
</ol>
<p/><p><bold><big>Layers and properties</big></bold></p>
<p>
When you include a super-class in your object, it automatically
inherits alls the properties from the superclass.
This is <bold>not</bold> the case for layers. If you add
a layer with a new class, the method definitions are incorporated
into the object, but <bold>properties are not.</bold>
</p>
<p>
MIFL specifically does not add properties when a layer is added
since doing so might unpredictably (for the programmer) cause
hundreds of properties to suddenly change.
</p>
<p>
Of course, there's an easy work around for this: Just set the
properties immediately before or after the layer. Or, provide
a function in the layered class that initialzes any necessary
properties.
</p>
<p>
One aspect of the properties is inherited when you layer on
a super-class though. Properties can include code that is called
whenever the property is accessed, through a get() or set().
Some properties need to trap access calls so they can verify the
value they're being changed to is acceptable.
</p>
<p>
If a layered super-class includes code for get() and set() of
its properties, this get() and set() code will be incorporated
into the object while the layer exists.
</p>
<p>
Furthermore, just as there's a way to add individual methods
to a layer, there is also a way of adding get() and set() code
for properties to a layer. This won't be discussed in the
tutorial though.
</p>
Tutorial about how one object can contain
another.
<p>
MIFL objects understand containership. They can contain other
objects and be contained by an object. This is an extremely
useful tool for interactive fiction, which has all sorts
of containers, like rooms containing objects, characters
containing inventory objects, and backpacks containing
objects.
</p>
<p>
To have an object initially created within another object:
</p>
<ol>
<li>
First create an object that can be a container. Using
everything you've learned so far, <bold>create
a "Birdcage"</bold> object. It doesn't need any properties
or methods. Make sure it has the <bold>"Automatically
create as an object"</bold> option checked.
</li>
<li>
Modify the Parrot object so it starts out contained within
the birdcage. Bring up the Parrot's "Modify object" page
and switch to the <bold>"Super-classes"</bold> tab.
</li>
<li>
Underneath the "Automatically create as an object" checkbox
is a drop-down listbox. Open it up and select <bold>"Birdcage"</bold> from
the listbox.
</li>
<li>
<bold>Compile</bold> the code and go to <bold>Test
compiled code</bold>.
</li>
<li>
You can use the right column to see the containership;
click on <bold>"Objects..."</bold>, then <bold>"Birdcage"</bold>,
and hover the mouse over the <bold>"Contains"</bold> item.
A popup will appear showing that the birdcage contains
the parrot.
</li>
<li>
If you <bold>switch to</bold> the Parrot object you'll see
that it is contained by the birdcage.
</li>
</ol>
<p>
That's all you need to do.
</p>
<p>
You can change containership at run-time using the following
methods:
</p>
<ul>
<li>
<bold>ContainedInGet</bold> - Returns the object that contains
this object.
</li>
<li>
<bold>ContainedInSet</bold> - Moves the object into another
object.
</li>
<li>
<bold>ContainsEnum</bold> - Returns a list of objects
that this object contains.
</li>
</ul>
Tutorial about the use of timers.
<p>
In MIFL, all timer events are associated with objects.
This means that to enumerate all the timers, you need to enumerate each
object and discover what times it supports. It also
causes times to be deleted when the object is deleted, or
saved when the object is saved.
</p>
<p>
Creating a timer is easy:
</p>
<ol>
<li>
Switch to the <bold>Test compiled code</bold> window.
</li>
<li>
Type <bold>"Parrot.TimerAdd ("SpeakTimer", true,
1.0, Trace, "Polly wants a cracker.")</bold> and
press <bold>"Run this"</bold>.
</li>
<p>
The "SpeakTimer" is a name you can use to identify the
timer. True indicates that the timer keeps repeating; if you
passed in false it would only call itself once and then stop.
1.0 is the number of seconds before the timer goes off.
Trace is the function (or method) to call, and
"Polly wants a cracker." is the parameter to pass into the
function (or method).
</p>
<p>
In other words, this timer causes the parrot to speak,
"Polly wants a cracker." over and over again.
</p>
<li>
To see the effects, press the <bold>"Refresh"</bold> button
in the "Debug trace" section.
</li>
<li>
You can also see the timer by clicking, <bold>"Timers (active)..."</bold> in
the right-hand table.
</li>
</ol>
<p>
There are three ways to stop the timer:
</p>
<ul>
<li>
Run <bold>"Parrot.TimerRemove ("SpeakTimer")"</bold>. This
deletes the timer.
</li>
<li>
Run <bold>"Parrot.TimerSuspendedSet (0.0)"</bold>. This
suspends <bold>all</bold> of the timers in the parrot object.
You can reactivate the timers by calling this function and
passing false in as the parameter.
</li>
<li>
Delete the parrot by running <bold>"delete Parrot"</bold>.
</li>
</ul>
<p>
That's the basics. Here's a complete list of timer methods:
</p>
<ul>
<li>
<bold>TimerAdd</bold> - Adds a new timer to the object.
</li>
<li>
<bold>TimerEnum</bold> - Enumerates all the timers in an object.
</li>
<li>
<bold>TimerQuery</bold> - Returns information about a timer.
</li>
<li>
<bold>TimerRemove</bold> - Deletes a timer from an object.
</li>
<li>
<bold>TimerSuspendedGet</bold> - Returns TRUE if the object's
timers are suspended.
</li>
<li>
<bold>TimerSuspendedSet</bold> - Suspends or resumes an
object's timers.
</li>
</ul>
Tutorial about the use of timers.
<p>
MIFL includes two special entities, string tables and resources.
The are both extremely useful for writing an application.
</p>
<p/><p><bold><big>String table</big></bold></p>
<p>
The <bold>"String table"</bold> is a list of strings with
a name attached to each. For example: One entry in the string
table might be "Hello world!" with the name, SHelloWorld (the S
prefix indicating it's a string).
</p>
<p>
When you have entered a string into the string table, you
can refer to the string by it's name. Therefore,
calling Trace(SHelloWorld); will print out "Hello world!".
</p>
<p>
The advantages of this follow:
</p>
<ul>
<li>
<bold>Centralized</bold> - If someone needs to change a string
that's displayed to the user, they can look for it in one (centralized)
location, instead of scanning through all the code.
</li>
<li>
<bold>Multiple references</bold> - If a string is used over and over
again, you only need to keep one copy of it in the string table.
This not only saves memory, but it makes it easy to change
all occurances of the string at once.
</li>
<li>
<bold>Faster code</bold> - A reference to a string table (SHelloWorld)
is stored internally as a number, making it very fast to pass
around. The string table reference is only converted to
a string, "Hello world!", at the last moment.
</li>
<li>
<bold>Localization</bold> - This is the most important aspect of
the string table. If a program only references SHelloWorld then
translating the program to spanish only requires that
the string table for SHelloWorld be changed from "Hello world!"
to "Hola el mundo!".
</li>
<p>
Here's the really cool part... the string table can contain the
string for both "Hello world!" and "Hola el mundo!". The version
it uses depends upon the user's language. This allows your application
to be written with English, Spanish, Japanese, etc. strings already
translated. If someone in an English speaking country runs it,
your program will use English. If they run it from Japan (or otherwise
indicate they're Japanese), they get Japanese from the program.
</p>
<p>
Furthermore, if MIFL is used for an online interactive fiction server, then
users in the US will see English prompts, and users from
Japan will see Japanese prompts, even though it's the same
program running. (Their chat text won't be translated though.)
</p>
</ul>
<p>
Before creating a string (in the string table), you should
first identify what languages your project will be
translated into:
</p>
<ol>
<li>
Select the <bold>"Project settings"</bold> menu item
under the <bold>"Misc.</bold> menu.
</li>
<p>
In the "Project settings" page that appears, you'll find
a section for "Languages". This lists all the languages
supported by your project.
</p>
<li>
To add a new language, <bold>select</bold> the language
underneath "Add language" and then
press <bold>"Add language"</bold>. You will see the
language added in the list to the right.
</li>
</ol>
<p>
To add a string to the string table, just:
</p>
<ol>
<li>
Select the <bold>"Add new string"</bold> menu item
under the <bold>"Misc.</bold> menu.
</li>
<p>
The "Modify string" page will appear.
</p>
<li>
Type in the <bold>"Name"</bold>, such as "SHelloWorld".
</li>
<li>
<bold>Type in the string</bold> for each of the languages.
You might end up leaving some languages blank because you
don't know them and are relying on a translator.
</li>
<p>
If a language's string is blank when the program is run,
the first non-blank string will be used, even though it
isn't for the right language.
</p>
<li>
Revisit any code you wrote and <bold>replace</bold> all occurances
of "Hello world!" with SHelloWorld.
</li>
<li>
<bold>Compile</bold> your code and use the <bold>Test
compiled code</bold> page to test that the strings still
display.
</li>
</ol>
<p>
Even if you added Japanese translation of "Hello world!", when
your HelloWorld() function was called, it probably printed
out "Hello world!". This is because it thinks you're in
an English speaking area. (Although theoretically if you're
in Japan it will automatically default to Japanese, and instead,
refuse to display the English version.)
</p>
<p>
To tell MIFL what language to use:
</p>
<ol>
<li>
In the "Test compiled code" page,
type <bold>"LanguageSet(1041)"</bold> and
press <bold>"Run this"</bold>.
</li>
<p>
The 1041 parameter is an ID for Japanese. English is 1033.
The language IDs conform to the standard LANGID #defines in
winnt.h. If you don't have this but wish to use the translations,
please contact me.
</p>
<li>
Calling <bold>HelloWorld()</bold> will now say "Hello world!" in
Japanese, assuming that you had entered a Japanese translation
for the string.
</li>
<li>
To find out what language MIFL is using
type <bold>"LanguageGet()"</bold> and
press <bold>"Run this"</bold>. The return value is the
current language ID.
</li>
</ol>
<p>
<bold>Warning:</bold> - Using a string table with multiple
languages can introduce some difficult-to-find bugs. For
example, look at this code:
</p>
<p align=center><table><tr><td><font face=courier>
MyGlobal = SHelloWorld;<br/>
Trace (MyGlobal);
</font></td></tr></table></p>
<p>
This will write "Hello world!" to the debug trace if the
language is set to English, "Hola el mundo!" for Spanish, etc.
No problems here. Lets complicate matters:
</p>
<p align=center><table><tr><td><font face=courier>
MyGlobal = SHelloWorld + " " + SHelloWorld;<br/>
Trace (MyGlobal);
</font></td></tr></table></p>
<p>
This will write "Hello world! Hello world!" to the debug trace if the
language is set to English, "Hola el mundo! Hola el mundo!" for Spanish, etc.
No problems here. Now for the problem:
</p>
<p align=center><table><tr><td><font face=courier>
LanguageSet (1033);<br/>
MyGlobal = SHelloWorld + " " + SHelloWorld;<br/>
LanguageSet (1034);<br/>
Compare = SHelloWorld + " " + SHelloWorld;<br/>
Trace (MyGlobal == Compare);
</font></td></tr></table></p>
<p>
The trace output is <bold>false</bold>. One might expect
it to be true since the exact same sequence of operations
is used to produce MyGlobal as well as Compare. Why is this?
</p>
<p>
"MyGlobal = SHelloWorld + " " + SHelloWorld;" ends up converting
SHelloWorld into a string. Since the current language is English,
the English version of SHelloWorld will be used from the string table.
MyGlobal ends up with the string "Hello world! Hello world!".
</p>
<p>
However, "LanguageSet (1034);" changes the language to
Spanish. When "Compare = SHelloWorld + " " + SHelloWorld;" is called,
the Spanish versions of the string are used.
Compare ends up being "Hola el mundo! Hola el mundo!".
</p>
<p>
The two strings are compared (as strings). To the program
they're obviously not the same.
</p>
<p/><p><bold><big>Resources</big></bold></p>
<p>
<bold>Resources</bold> are desgigned to hold data that isn't
a string and isn't code. This could include images,
sounds, etc.
</p>
<p>
Unforuntately, the type of resources a MIFL
project contains depends upon its context; different types
of resources will be available for interactive fiction than
for 3D modelling. Since this tutorial is written to be
context independent, it won't be very specific about the
types resources.
</p>
<p>
To create a resource:
</p>
<ol>
<li>
Select the <bold>"Add new resource"</bold> menu item
under the <bold>"Misc."</bold> menu.
</li>
<li>
A new page, "Add a resource", will appear. <bold>Click on</bold> one
of the listed resource types. Unfortunately, I can't
be detailed here.
</li>
<p>
When you click on a resource, a new page will appear, "Modify
resource". It lets you change the name of the resource as well
adding resources for specific languages.
</p>
<li>
Type in a <bold>"Name"</bold>, such as "RMyResource". The
"R" prefix indictes it's a resource.
</li>
<li>
Type in a <bold>"Description"</bold> so that when you see
the list of resources you'll know what it's for.
</li>
<li>
Press <bold>"Add"</bold> for one of the languages. This
will bring up a dialog that's specific to modifying the
resource you have selected.
</li>
<p>
If you have specified multiple languages for your project,
you'll notice that the resource can contain slots for each
of the languages. This lets the resource automatically
adjust to the user's language, just as string tables did.
If this were an audio resource with a recording of someone speaking,
you could include several recordings, one for each
supported language.
</p>
<p>
And, just as with string tables, if you only fill in one
resource entry, that one will be used, despite the user's
choice of lanugages. You probably don't need localized versions
of ever image, for example, although signs and other letters
within the images might cause a problem.
</p>
</ol>
<p>
Resource usage is context dependent, so I can't show that
here. As a general rule, the content (such as interactive fiction)
will provide a library specific to the context. It will
contain functions for using the resources the context supports.
</p>
Describes how constructors and destructors work
in MIFL.
<p>
MIFL supprots constructors and destructors for objects.
A constructor is called when an object is created, and
a destructor is called when an object is deleted (usually).
Classes and objects typically use constructors
to initialize variables and start timers. Destructors are usually
to unravel relationships the object has with other objects.
</p>
<p>
The use of a constructor and/or destructor for an object
is option. To use one, the object needs to support
the public method <bold>Constructor()</bold> and/or
the public method <bold>Destructor()</bold>.
It may also want to support <bold>Constructor2()</bold>.
</p>
<p>
Constructor() and Desctructor() are called under the following
circumstances:
</p>
<ul>
<li>
When a program is first run, <bold>all of</bold> the automatically
created objects are created. Then, <bold>Constructor()</bold> is
called for all the objects.
</li>
<li>
If a running program has been saved to disk, and is reloaded,
the <bold>Constructor2()</bold> method will be called for all
the loaded objects instead of Constructor().
</li>
<li>
When an object is created using
"new", <bold>Constructor()</bold> will be called for the object.
</li>
<li>
When an object is deleted using
"delete", <bold>Destructor()</bold> will be called for the object.
The destructor will be called <bold>before</bold> all the timers
have been deleted.
</li>
<li>
If a program is shut down even though objects are still
around, then Destructor() will <bold>not</bold> be called.
</li>
</ul>
Describes the different data types.
<p>
MIFL supports the following data types:
</p>
<p/><p><bold><big>Boolean</big></bold></p>
<p>
A boolean can either be <bold>true</bold> or <bold>false</bold>.
Both true and false are keywords built into the language.
</p>
<p align=center><table bgcolor=#ffffff>
<tr><td bgcolor=#80c0ff><bold>Convert data-type to boolean</bold></td></tr>
<tr><td bgcolor=#80c0ff><bold>Data-type</bold></td><td bgcolor=#80c0ff><bold>Conversion</bold></td></tr>
<tr>
<td>Boolean</td>
<td>NA</td>
</tr>
<tr>
<td>Character</td>
<td>if '\0' then false, else true</td>
</tr>
<tr>
<td>Function</td>
<td>true</td>
</tr>
<tr>
<td>List</td>
<td>true</td>
</tr>
<tr>
<td>List.Method</td>
<td>true</td>
</tr>
<tr>
<td>Null</td>
<td>false</td>
</tr>
<tr>
<td>Number</td>
<td>if 0 then false, else true</td>
</tr>
<tr>
<td>Method</td>
<td>true</td>
</tr>
<tr>
<td>Object</td>
<td>true</td>
</tr>
<tr>
<td>Object.Method</td>
<td>true</td>
</tr>
<tr>
<td>Resource</td>
<td>true</td>
</tr>
<tr>
<td>String</td>
<td>if "" then false, else true</td>
</tr>
<tr>
<td>String.Method</td>
<td>true</td>
</tr>
<tr>
<td>String table</td>
<td>if "" then false, else true</td>
</tr>
<tr>
<td>Undefined</td>
<td>false</td>
</tr>
</table></p>
<p/><p><bold><big>Character</big></bold></p>
<p>
A character is a unicode character, from 0 to 65535.
Characters are specific by using single-quotes around a single
character, such as <bold>'x'</bold> or <bold>'?'</bold>.
The same escape sequences that strings use can appear in
a character, such as <bold>'\n'</bold>.
</p>
<p align=center><table bgcolor=#ffffff>
<tr><td bgcolor=#80c0ff><bold>Convert data-type to character</bold></td></tr>
<tr><td bgcolor=#80c0ff><bold>Data-type</bold></td><td bgcolor=#80c0ff><bold>Conversion</bold></td></tr>
<tr>
<td>Boolean</td>
<td>'t' if true, 'f' if false</td>
</tr>
<tr>
<td>Character</td>
<td>NA</td>
</tr>
<tr>
<td>Function</td>
<td></td>
</tr>
<tr>
<td>List</td>
<td>'\0'</td>
</tr>
<tr>
<td>List.Method</td>
<td>'\0'</td>
</tr>
<tr>
<td>Null</td>
<td>'\0'</td>
</tr>
<tr>
<td>Number</td>
<td>convert to Unicode character</td>
</tr>
<tr>
<td>Method</td>
<td>'\0'</td>
</tr>
<tr>
<td>Object</td>
<td>'\0'</td>
</tr>
<tr>
<td>Object.Method</td>
<td>'\0'</td>
</tr>
<tr>
<td>Resource</td>
<td>'\0'</td>
</tr>
<tr>
<td>String</td>
<td>first letter of the string</td>
</tr>
<tr>
<td>String.Method</td>
<td>'\0'</td>
</tr>
<tr>
<td>String table</td>
<td>first letter of the string</td>
</tr>
<tr>
<td>Undefined</td>
<td>'\0'</td>
</tr>
</table></p>
<p/><p><bold><big>Function</big></bold></p>
<p>
This is a reference to a function. Function names are
tokens generated at compile time.
</p>
<p align=center><table bgcolor=#ffffff>
<tr><td bgcolor=#80c0ff><bold>Convert data-type to function</bold></td></tr>
<tr><td bgcolor=#80c0ff><bold>Data-type</bold></td><td bgcolor=#80c0ff><bold>Conversion</bold></td></tr>
<tr>
<td>Boolean</td>
<td>undefined</td>
</tr>
<tr>
<td>Character</td>
<td>undefined</td>
</tr>
<tr>
<td>Function</td>
<td>NA</td>
</tr>
<tr>
<td>List</td>
<td>undefined</td>
</tr>
<tr>
<td>List.Method</td>
<td>undefined</td>
</tr>
<tr>
<td>Null</td>
<td>undefined</td>
</tr>
<tr>
<td>Number</td>
<td>undefined</td>
</tr>
<tr>
<td>Method</td>
<td>undefined</td>
</tr>
<tr>
<td>Object</td>
<td>undefined</td>
</tr>
<tr>
<td>Object.Method</td>
<td>undefined</td>
</tr>
<tr>
<td>Resource</td>
<td>undefined</td>
</tr>
<tr>
<td>String</td>
<td>searches through the function list for the name</td>
</tr>
<tr>
<td>String.Method</td>
<td>undefined</td>
</tr>
<tr>
<td>String table</td>
<td>searches through the function list for the name</td>
</tr>
<tr>
<td>Undefined</td>
<td>undefined</td>
</tr>
</table></p>
<p/><p><bold><big>List</big></bold></p>
<p>
A list is an array of values (which can be any of the datatypes,
including other lists). For more information on lists, see
the tutorial about them.
</p>
<p align=center><table bgcolor=#ffffff>
<tr><td bgcolor=#80c0ff><bold>Convert data-type to list</bold></td></tr>
<tr><td>No conversions possible.</td></tr>
</table></p>
<p/><p><bold><big>List.Method</big></bold></p>
<p>
A "List.Method" is a combination of a list and a method assocaited
with that list. Although this is a dataype, programmers won't
usually think of it as one since it's an intermediate step
towards accessing the list's methods, such as List.ListAdd();
</p>
<p align=center><table bgcolor=#ffffff>
<tr><td bgcolor=#80c0ff><bold>Convert data-type to list</bold></td></tr>
<tr><td>No conversions possible.</td></tr>
</table></p>
<p/><p><bold><big>Null</big></bold></p>
<p>
The null type has no values.
</p>
<p align=center><table bgcolor=#ffffff>
<tr><td bgcolor=#80c0ff><bold>Convert data-type to list</bold></td></tr>
<tr><td>No conversions possible.</td></tr>
</table></p>
<p/><p><bold><big>Number</big></bold></p>
<p>
A number is a floating point value (in C/C++ terms it's
an 8-byte double) that has 15 digits of precision and ranges
from (approx) -1e300 to 1e300.
</p>
<p>
A number can be written in the following forms:
</p>
<ul>
<li>
<bold>Integer</bold> - A series of digits. A negative in front
is optional.
</li>
<li>
<bold>Decimal</bold> - An optional negative, with (optional) digits followed
by a '.' and more digits.
</li>
<li>
<bold>Exponential</bold> - A decimal number immediately followed by
'e' and then an integer.
</li>
<li>
<bold>Hexadecimal</bold> - '0x' followed by any number of hexadecimal
digits ('0'..'9', 'a'..'f').
</li>
</ul>
<p align=center><table bgcolor=#ffffff>
<tr><td bgcolor=#80c0ff><bold>Convert data-type to number</bold></td></tr>
<tr><td bgcolor=#80c0ff><bold>Data-type</bold></td><td bgcolor=#80c0ff><bold>Conversion</bold></td></tr>
<tr>
<td>Boolean</td>
<td>0 if false, 1 if true</td>
</tr>
<tr>
<td>Character</td>
<td>Unciode value of the character</td>
</tr>
<tr>
<td>Function</td>
<td>0</td>
</tr>
<tr>
<td>List</td>
<td>0</td>
</tr>
<tr>
<td>List.Method</td>
<td>0</td>
</tr>
<tr>
<td>Null</td>
<td>0</td>
</tr>
<tr>
<td>Number</td>
<td>NA</td>
</tr>
<tr>
<td>Method</td>
<td>0</td>
</tr>
<tr>
<td>Object</td>
<td>0</td>
</tr>
<tr>
<td>Object.Method</td>
<td>0</td>
</tr>
<tr>
<td>Resource</td>
<td>0</td>
</tr>
<tr>
<td>String</td>
<td>convert the first characters of the string to a number</td>
</tr>
<tr>
<td>String.Method</td>
<td>0</td>
</tr>
<tr>
<td>String table</td>
<td>convert the first characters of the string to a number</td>
</tr>
<tr>
<td>Undefined</td>
<td>0</td>
</tr>
</table></p>
<p/><p><bold><big>Method</big></bold></p>
<p>
This is a reference to a method. Method names are
tokens generated at compile time.
</p>
<p align=center><table bgcolor=#ffffff>
<tr><td bgcolor=#80c0ff><bold>Convert data-type to method</bold></td></tr>
<tr><td bgcolor=#80c0ff><bold>Data-type</bold></td><td bgcolor=#80c0ff><bold>Conversion</bold></td></tr>
<tr>
<td>Boolean</td>
<td>undefined</td>
</tr>
<tr>
<td>Character</td>
<td>undefined</td>
</tr>
<tr>
<td>Function</td>
<td>undefined</td>
</tr>
<tr>
<td>List</td>
<td>undefined</td>
</tr>
<tr>
<td>List.Method</td>
<td>keep only the method</td>
</tr>
<tr>
<td>Null</td>
<td>undefined</td>
</tr>
<tr>
<td>Number</td>
<td>undefined</td>
</tr>
<tr>
<td>Method</td>
<td>NA</td>
</tr>
<tr>
<td>Object</td>
<td>undefined</td>
</tr>
<tr>
<td>Object.Method</td>
<td>keep only the method</td>
</tr>
<tr>
<td>Resource</td>
<td>undefined</td>
</tr>
<tr>
<td>String</td>
<td>searches through the public method list for the name</td>
</tr>
<tr>
<td>String.Method</td>
<td>keep only the method</td>
</tr>
<tr>
<td>String table</td>
<td>searches through the public method list for the name</td>
</tr>
<tr>
<td>Undefined</td>
<td>undefined</td>
</tr>
</table></p>
<p/><p><bold><big>Object</big></bold></p>
<p>
This is a reference to an object.
</p>
<p align=center><table bgcolor=#ffffff>
<tr><td bgcolor=#80c0ff><bold>Convert data-type to list</bold></td></tr>
<tr><td>No conversions possible.</td></tr>
</table></p>
<p/><p><bold><big>Object.Method</big></bold></p>
<p>
An "ObjectMethod" is a combination of an object and a method
assocaited
with that list. Although this is a dataype, programmers won't
usually think of it as one since it's an intermediate step
towards accessing the object's methods, such as Object.MyMethod();
It is a useful construct for timers and other callbacks since
it remembers both the method to call and object to which it
belongs.
</p>
<p align=center><table bgcolor=#ffffff>
<tr><td bgcolor=#80c0ff><bold>Convert data-type to object.method</bold></td></tr>
<tr><td>No conversions possible.</td></tr>
</table></p>
<p/><p><bold><big>Resource</big></bold></p>
<p>
A resource is a reference to a resource (in the project).
Resource names are generated at compile time.
</p>
<p align=center><table bgcolor=#ffffff>
<tr><td bgcolor=#80c0ff><bold>Convert data-type to resource</bold></td></tr>
<tr><td>No conversions possible.</td></tr>
</table></p>
<p/><p><bold><big>String</big></bold></p>
<p>
A string is a reference to memory containing a Unicode string.
For more information on strings see the tutorial.
</p>
<p align=center><table bgcolor=#ffffff>
<tr><td bgcolor=#80c0ff><bold>Convert data-type to string</bold></td></tr>
<tr><td bgcolor=#80c0ff><bold>Data-type</bold></td><td bgcolor=#80c0ff><bold>Conversion</bold></td></tr>
<tr>
<td>Boolean</td>
<td>"false" if false, "true" if true</td>
</tr>
<tr>
<td>Character</td>
<td>string with the one character</td>
</tr>
<tr>
<td>Function</td>
<td>function name</td>
</tr>
<tr>
<td>List</td>
<td>list elements, separated by commas</td>
</tr>
<tr>
<td>List.Method</td>
<td>"list." with the method name appended</td>
</tr>
<tr>
<td>Null</td>
<td>null</td>
</tr>
<tr>
<td>Number</td>
<td>decimal form, or an exponential form if it's too large</td>
</tr>
<tr>
<td>Method</td>
<td>method name</td>
</tr>
<tr>
<td>Object</td>
<td>the return from Object.Name(), or the object's class</td>
</tr>
<tr>
<td>Object.Method</td>
<td>the return from Object.Name(), or the object's class,
followed by "." and the method's name</td>
</tr>
<tr>
<td>Resource</td>
<td>convert to string</td>
</tr>
<tr>
<td>String</td>
<td>NA</td>
</tr>
<tr>
<td>String.Method</td>
<td>"string." followed by the method's name</td>
</tr>
<tr>
<td>String table</td>
<td>the string designated for the current language</td>
</tr>
<tr>
<td>Undefined</td>
<td>""</td>
</tr>
</table></p>
<p/><p><bold><big>String.Method</big></bold></p>
<p>
A "String.Method" is a combination of a string and a method associated
with that string. Although this is a dataype, programmers won't
usually think of it as one since it's an intermediate step
towards accessing the string's methods, such as String.StringSlice();
</p>
<p align=center><table bgcolor=#ffffff>
<tr><td bgcolor=#80c0ff><bold>Convert data-type to string.method</bold></td></tr>
<tr><td>No conversions possible.</td></tr>
</table></p>
<p/><p><bold><big>String table</big></bold></p>
<p>
A string table is a reference to a string table (in the project).
String table names are generated at compile time.
</p>
<p align=center><table bgcolor=#ffffff>
<tr><td bgcolor=#80c0ff><bold>Convert data-type to string table</bold></td></tr>
<tr><td>No conversions possible.</td></tr>
</table></p>
<p/><p><bold><big>Undefined</big></bold></p>
<p>
The undefined type has no values.
</p>
<p align=center><table bgcolor=#ffffff>
<tr><td bgcolor=#80c0ff><bold>Convert data-type to undefined</bold></td></tr>
<tr><td>No conversions possible.</td></tr>
</table></p>
Describes how to intercept get() and set() calls
to global variables and properties.
<p>
Typically, when a global variable or an object's property is
created, the variable's contents can be accessed directly.
Any function or method can change the variable (or method)
to any value.
</p>
<p>
You may wish to protect variables (or methods) so that only
valid values can be written. Alternatively, you might
need the accessing of a variable to run some code. (For example:
The "weight" property of a backback might sum of the weight
of its contents and its own weight.)
</p>
<p>
You can do such interceptions by providing "get and set" code
for the variable or property. There are two ways to do this:
</p>
<ul>
<li>Check the "Use get/set code" in the variable's or property's definition</li>
<li>Use some run-time calls to change the get/set code for variables or properties</li>
</ul>
<p/><p><bold><big>"Use get/set code" checkbox</big></bold></p>
<p>
Providing your own get/set code for a global variable or
an object's property is easy. If you are modifying a global
variable you will find a checkbox for "Use get/set code" and
one button each for "Get" and "Set. If you are modifying an
object's properties, you'll find a similar checkbox and buttons.
</p>
<p>
To provide your own get/set code:
</p>
<ol>
<li>
Check the <bold>"Use get/set code"</bold> checkbox.
</li>
<li>
Press the <bold>"Get"</bold> button to modify the code to
get the variable.
</li>
<li>
Press the <bold>"Set"</bold> button to modify the code to
set the variable.
</li>
</ol>
<p>
Note: When you attach get/set code to a variable or method,
any reads from or writes to the variable will access the
get/set code. Except, from within the code for handling get
and set itself; this will be able to access the variable directly.
</p>
<p/><p><bold><big>Run-time calls</big></bold></p>
<p>
Modifying the get/set code for a global variable or
object's property at run-time is slight more difficult.
</p>
<p>
To modify a global variable's get/set code you need
to call <bold>GlobalGetSet()</bold>. See the function's definition
for more information.
</p>
<p>
To modify an object's property's get/set code you need to
call <bold>LayerPropGetSetAdd()</bold>. See the
method's definition for more information.
</p>
Describes how to loading/saving objects from/to
disk works.
<p>
MIFL lets you save one or more (or all) objects to disk, and
then reload them at a later point. The exact function needed
to do this depends upon the context, so I can't go into
details here.
</p>
<p>
However, I can explain the basic process:
</p>
<p/><p><bold><big>Saving</big></bold></p>
<p>
When one or more objects are saved, the following steps are taken
for each object:
</p>
<ol>
<li>
<bold>Identify the initial properties in a virgin object</bold> - A "virgin"
form of the object is created using the object's class (from the
lowest-priority layer). The virgin object has its properties
initialzed according to the "initialized to" values for
the object's methods.
</li>
<li>
<bold>Save only property changes</bold> - The to-be-saved
object is compared to its "virgin" version. Only those properties
that have been changed (or deleted) will be saved.
</li>
<p>
This is a very useful feature since it not only reduces the
space necessary to save objects, it makes it safer for authors
to save all the objects, change some properties, and then
reload the previous objects (which were created with old code).
Since only changed (or deleted) properties were saved, the
realoded objects will contain all the properties as prescribed
by the new code, except for the changes.
</p>
<p>
Changing the code between a save and load isn't 100% safe
though, and problems can arise. For example: If a saved
object relied on the class "Unhealthy", but when it reloaded
discovered that no "Unhealthy" class exists, it will ignore
the "Unhealthy" class.
</p>
<li>
<bold>Save all layers</bold> - All the layers of the object
are saved, including any single-instance methods and get/set
code.
</li>
<li>
<bold>Save contained in</bold> - The object reference of the
object's container will be saved. The contained objects'
references are not, however, because this information
can be reconstructed from the containers.
</li>
<li>
<bold>Save all timers</bold> - All the object's timers are
saved. The timer's suspended state is also saved.
</li>
</ol>
<p>
In addition, if all the objects are saved, the following
information is also saved:
</p>
<ol>
<li>
<bold>Global variable changes</bold> - Just as only changed
(and deleted) properties are saved, so too with global variables.
Any unchanged global variable is ignored.
</li>
</ol>
<p>
<bold>A note about numerical accuracy:</bold> - Because of the
wave data save is currently implimented, numbers may be rounded
off to the nearest 0.00001. This is not a problem for most
numbers, but might cause problems under some circumstances.
</p>
<p/><p><bold><big>Loading</big></bold></p>
<p>
If all the objects were saved, the following
information is loaded:
</p>
<ol>
<li>
<bold>Global variable changes</bold> - All the global variables
are created and set the the "initialized to" value from compile.
Then, any changes are loaded from the saved version.
If any globals were deleted prior to saving then they're
deleted.
</li>
</ol>
<p>
When the saved objects are reloaded, the following steps happen
for every object:
</p>
<ol>
<li>
<bold>Check for object ID conflict</bold> - If there is a conflict
in the object's ID with an object that already exists, then
a new ID is created. The object's ID is changed to the new ID,
and any saved object (or data) that references the old ID gets
updated.
</li>
<li>
<bold>Create layers</bold> - The loaded object has all the
proper layers setup.
</li>
<li>
<bold>Create properties</bold> - All the properties (from the
lowest priority layer) are added using the compiled
"initialized to" values.
</li>
<li>
<bold>Load property changes</bold> - Any changes to the
properties are loaded. This also includes property deletions.
</li>
<li>
<bold>Load contained in</bold> - The contained-in reference
is loaded and the loaded object is placed in the container.
If the container no longer exists then the loaded object
is not placed in any container.
</li>
<li>
<bold>Load all timers</bold> - All the object's timers are
load. The timer's suspended state is also loaded.
</li>
<li>
<bold>Call Constructor2</bold> - If the object supports
a Constructor2() method, that is called so the object knows
it has been loaded from a saved state.
</li>
</ol>
Talks about limits to loops and stack depth.
<p>
MIFL performs the following checks to prevent infinite loops:
</p>
<ul>
<li>
<bold>Loop counts</bold> - For most contexts, MIFL will
abort a loop if it is more than a million iterations.
This protects against infinite loops.
</li>
<li>
<bold>Call-stack depth</bold> - For most contexts, MIFL will
abort a function or method call if it is more than 1000
function/method calls deep. This protects against infinite
recursion.
</li>
</ul>
The database functions (DatabaseXXX) can be used to store large numbers of objects to disk.
<p><bold><big>Database functions</big></bold></p>
<p>
The Circumreality server provides a set of database functions you can
use to save objects (from your virtual machine) onto disk
when they're not used. This saves memory and processing power.
</p>
<p>
For IF, you can use the database if your world is too large
to fit in memory, or you have objects that aren't needed
all the time. For example: You may wish to save information
about your players and their characters in separate databases,
only loading in the information when the player has logged on.
When the player logs off, the information is saved back to disk.
</p>
<p>
The database is also useful because it saves to disk, which
is good protection in case the server crashes. You can even
have the database make daily backups to other directories
on disk, or even other servers connected through a LAN.
</p>
<p><bold><big>Database categories</big></bold></p>
<p>
The first issue you need to tackle when dealing with the
database functions is to determine what database "categories"
you'll need. Each category represents a separate list of
objects stored in its own file.
</p>
<p>
For IF, you'll probably want the following database categories:
"Players", "PlayerCharacters", "SaveToDatabase", and "Objects".
You may also want categories for "EMails", and "NewsgroupPosts"
so that users can send message to one another.
</p>
<p>
Database categories are automatically created when the category
is referenced. You don't need to call any funtions.
</p>
<p><bold><big>Cached properties</big></bold></p>
<p>
Each database category stores a list of objects. Each
stored object retains its class and properties.
When you wish to retrieve one or more items from a database
you'll need to search the database (do a query) for objects
whose properties match certain qualifications.
</p>
<p>
For example: When a user types in their player name, your
application will query the "Players" database for all objects
whose "Name" matches the name typed in by the user.
Likewise, when the user wishes to select a character, you'll
need to query the "PlayerCharacters" database for all objects
whose "Player" matches the player name.
</p>
<p>
To make queries as fast as possible, the database "caches"
one or more properties from the objects. The cached properties
are usually those used for queries. While you could conceivably
cache every property in all the objects, you shouldn't because
that would make the cache information very large, defeating
the memory-saving ability of the database.
</p>
<p>
For example: From the "Players" database you would definitely
cache "Name". You might also cache some properties useful for
administration, like "TotalTimeLoggedOn". The "PlayerCharacters"
database would cache "Name" and "Player". You might also
cache "Level", "Race", "Class", etc. so you can quickly generate a list
of all player characters along with their race and level.
</p>
<p>
To add a property to be cached call DatabasePropertyAdd() with
the database name and cached property as parameters.
(Example: DatabasePropertyAdd ("Players", "Name");)
Repeated calls add more properties. (If a property is already
on the cached list then the call will just return TRUE
for success, since the property is already cached.)
</p>
<p>
One important thing to remember about cached properties is that
if a property is added AFTER the database has objects, whenever
that property it accessed for the pre-existing objects it will
return Undefined. As the objects are checked out (accessed), however,
the cache list will be updated with their correct values.
What this means is that you should define what properties
are to be cached before your databases are finalized.
</p>
<p><bold><big>Adding an object to the database</big></bold></p>
<p>
To add an object to the database:
</p>
<ol>
<li>
Create it as normal, using "MyObject = new cMyClass" (or whatever).
</li>
<li>
Call "DatabaseObjectAdd ();" with the database name and the
object. Example: "DatabaseObjectAdd ("Players", MyObject);
</li>
</ol>
<p>
At this point the object will be saved to the database and
immediately "checked out". An object that is checked out
can only be accessed by the application that has it checked
out (preventing another application from using the same player).
Furthermore, an object that is checked out must be
"checked in" for the changes to be saved.
</p>
<p>
Once an object is added, use it as normal until your
program is ready to delete the object. At that point
the program needs to check in the object.
</p>
<p><bold><big>Checking in an object</big></bold></p>
<p>
You can think of the whole process of checking out and checking
in an object like a visit to the library... to find a book
you first visit the card catalog (the database query). Once
you know what book you want, you get it from the shelves and
check it out from the librarian. From then on, only you have
access to it. When you're finished with it, check it back in,
and it goes back on the shelves and is available for other
visitors.
</p>
<p>
When your application is finished with a checked-out object,
it should call "DatabaseObjectCheckIn()", passing in the
database category and the object. This will save the object
to disk and DELETE it from memory (just like
calling "delete MyObject"). If you don't want the object
deleted from memory then you can pass a parameter into
DatabaseObjectCheckIn() that will cancel the deletion.
</p>
<p>
If your object contains other objects (such as a player character
holding possessions) then you MUST check in the contained
objects first before checking in the container object.
Conversely, when you check out, your MUST check out the container
first, followed by the contained objects. If you do check out/in
in the wrong order MIFL will not properly remember which object
is contained within which other object.
</p>
<p>
If you merely want to save your object to disk without checking
it in, call "DatabaseObjectSave()" instead. You may wish to
do this for two reasons: 1) If the computer crashes, all the
information about an object will be lost unless it's saved (or checked
in). 2) When an object is saved, any properties from the
will be updated in the cache, allowing for more up-to-date queries.
</p>
<p><bold><big>Checking out an object</big></bold></p>
<p>
You can check an object out of the database by
calling "DatabaseObjectCheckOut()", using the database
category name and the object.
Checking an object out will load it off disk and create
a version of the object in memory. (In many ways, the
functionality is similar to a call to "new cMyClass" except
that the newly created object has all the properties and
timers of the checked in class.)
</p>
<p>
Once an object is checked out you can use it as normal.
When you're finished with it, check it back in.
</p>
<p>
To check an object out, however, you need to know which
object you want. This involves a trip to the card catalog (aka:
a database query.)
</p>
<p><bold><big>Database queries</big></bold></p>
<p>
To get a list of all the objects in the players database, just
call: DatabaseQuery ("Players", NULL, '|');
This will return a list of all the objects in the database.
You can then use DatabaseObjectCheckOut() to get each
object in turn, examinine it, and check it back in.
</p>
<p>
If you wished to find the player object belonging to the
player that's logging in, checking out and then checking in
every single player object in the database would be very
slow.
</p>
<p>
There is a better way to do this: Call
"DatabaseQuery ("Players", ["==c", "|Name", "Tim Jones"], '|');
instead. This call will return a list of objects whose
name (property) is "Time Jones".
</p>
<p>
Here is an explanation of the paramters:
</p>
<ol>
<li>
The "Players" entry informs the query to look in the "Players"
database.
</li>
<li>
"["==c", "|Name", "Tim Jones"]" tells the query to find
all objects whose "Name" (property) is equal ("==c") to "Tim Jones".
The '|' in front of "Name" identifies it as a properties, as
opposed to a string. ("Tim Jones" does NOT have a '|' in front of it.)
The "==c" is a case-insensative comparison.
</li>
<li>
The '|' parameter identifies what character is used to indicate
that a string is actually a property name, as opposed to a string
to compare against.
</li>
</ol>
<p>
The "==c" is called an operator. It specifies what kind of
comparison to use when comparing the "Name" property to "Tim Jones".
There are a couple dozen different operators (see the
DatabaseQuery() documentation). For example: "!=c" would find
all players whose name is NOT "Time Jones", while "containsc" would
find all players whose name contains the string, "Tim Jones", so
"Tim Jones III" and "Hittim Jones" would be returned from the
query.
</p>
<p>
You can also compare several properties (as explained in
the DatabaseQuery() documentation). For example: To find
all players named "Tim Jones" or whose name contains "Tim", pass
in "["||", ["==c", "|Name", "Tim Jones"], ["containsc", "|Name", "Tim"]]".
The "||" is a logical for the next two operands.
</p>
<p><bold><big>Avoiding checking out and checking in</big></bold></p>
<p>
Sometimes your application will just need to get a few
properties from an object (which may be cached) and doesn't
really need to check out the object.
</p>
<p>
For example: You may have done a query for all the
objects whose name contains "Tim" and now want to display
their names and ages on the screen. You could go through
each object and check it out, get the property values, and
then check the object back in. This would be slow.
It also has a problem: If the player object was checked out
elsewhere you wouldn't be able to access it.
</p>
<p>
You could also call "DatabasePropertyGet()" to get the
properties directly.
</p>
<p>
Example:
</p>
<ol>
<li>
Call "ListOfTims = DatabaseQuery ("Players", ["containsc", "|Name",
"Tim"], '|');" to fill "ListOfTims" with a list of all players
named Tim.
</li>
<li>
Call "ToDisplay = DatabasePropertyGet ("Players", ListOfTims, "Name");"
to get the "Name" property from all the objects in "List of tims".
This might return ["Tim Jones", "Tim Jones III", "Hittim"].
</li>
</ol>
<p>
If you wished to get both the "Name" and "Age" properties from
the players in the list, you could always call DatabasePropertyGet()
twice. Or, you wrap the property names into a list
and call: "DatabasePropertyGet ("Players", ListOfTims,
["Name", "Age"]);" This will return a list of lists. The
first level of the list will correspond to all the objects.
The second level (lists within the main list) will be
the values for the "Name" and "Age" properties respectively.
</p>
<p>
If you just wished the name for the first Tim, calling
"DatabasePropertyGet ("Players", ListOfTims[0], "Name");" would
return "Tim Jones". (Notice the [0] index onto ListOfTims.)
</p>
<p>
Be aware that while DatabasePropertyGet() will get properties
for objects that are checked out, the property value may not
be up-to-date. If an object is checked out and its "Name"
property change, calls to DatabasePropertyGet() will return
the old name. At least until the object is checked in, or
the object is saved.
</p>
<p><bold><big>Avoiding checking out and checking in, part 2</big></bold></p>
<p>
You can use a sister function, "DatabasePropertySet()" to change
one or more properties without actually checking out (and then
checking in) the object.
</p>
<p>
NOTE: DatabasePropertySet() will NOT be able to modify an
object that is checked out. It can only modify objects
that are checked in. (DatabasePropertyGet() can access properties
from objects that are checked out or in.)
</p>
<p>
Example: To change a player's name without checking it out,
call "DatabasePropertySet ("Players", ListOfTims[0], "Name",
"Tim's new name");"
</p>
<p>
Just as DatabasePropertyGet() can accept a list of objects
to get (as opposed to just one object), or a list of
properties to get (as opposed to just one property), so
to can DatabasePropertySet(). For more information see
the DatabasePropertySet() documentation.
</p>
<p><bold><big>Deleting checked-out objects</big></bold></p>
<p>
If you <bold>delete</bold> a checked-out object using
"delete XXX", or "XXX.DeleteWithContents()", or
"DeleteGroup()", then if the object is checked out, it
will automatically be deleted from the database. If
it is <bold>not</bold> checked out, then its database
entry will be unaffected.
</p>
The SavedGameXXX() functions can be used to save a game, or group of objects.
<section><p><bold><big>Saved games</big></bold></p></section>
<p>
You can use the SavedGameXXX() functions to load or save
a game (for a single player game), or to create instanced
areas of the game.
</p>
<p>
To save a game:
</p>
<ol>
<li>
If you have values of <bold>global variables</bold> that
you wish to save, you need to put the global variables
into a named object. Or better yet, design your IF title
so it doesn't use global variables to store information
that might change across game saves.
</li>
<li>
<bold>Determine every object</bold> that needs to be
saved. You don't need
to worry about child objects since they can be handled
automatically.
</li>
<p>
If most of the objects need to be saved (such as for saving
an entire game), then determine which objects <bold>don't
need to be saved</bold>. For example: You won't wish to
save the connection objects because if you do, reloading the
saved game will lose connection information.
</p>
<li>
Call <bold>SavedGameSave()</bold>, passing in a <bold>game
name</bold> and <bold>the list of objects</bold> you wish
to save (or exclude from saving). The function accepts
a flag indicating whether or not you wish to save all objects
and exclude from the list, or save only in the list.
It also accepts a flag which causes all the children (and
their children, recursive) to be saved.
</li>
</ol>
<p>
That's it. To reload the game:
</p>
<ol>
<li>
Call <bold>SavedGameLoad()</bold> with the name of the game
and a flag indicating if you wish to remap objects.
</li>
<p>
If remapping is set to TRUE then any objects you load that
already exist will be given new IDs (and the old ones will
be kept around).
</p>
<p>
If remapping is FALSE then loaded objects
will replace any old ones.
If the game was saved with the SaveAll flag
set to TRUE then any deleted objects will also be deleted.
</p>
</ol>
<p>
You can also call <bold>SavedGameEnum()</bold> to list
saved games, or <bold>SavedGameRemove()</bold> to delete
saved games.
</p>
<br/><section><p><bold><big>Advanced saved games</big></bold></p></section>
<p>
When you save a game, you need to specify both a <bold>file name</bold>, and
a <bold>sub-file name</bold>.
</p>
<p>
The file name corresponds to an actual
file written to disk, in a sub-directory of where the databases are stored.
For example: The "MySaves" filename would be translated
into "[DatabaseDir]\SavedGames\MySaves.msg".
</p>
<p>
The sub-file is written within the main .msg file.
</p>
<p>
The advantage of this system is that instances (see below) for players
on a multiuser game can be easily saved. Each character can be assigned
a main file name (probably based on their character's object ID). All
of the instanced maps are saved as sub-files within the character's
save-game file.
</p>
<br/><section><p><bold><big>Creating an instanced dungeon</big></bold></p></section>
<p>
<bold>NOTE:</bold> Instances are handled by the Basic Interactive Fiction
library. If you want to use instances, you should use the functions and
methods provided there. However, you may find this section interesting
because (a) it explains the basic principles, and (b) it lets you rewrite
the code in the Basic Interactive Fiction library.
</p>
<p>
An instanced dungeon (or area of the world) is a copy of the
world that can only be accessed by select PCs (usually friends).
A section of the world may be instanced several times over
so that many groups can play in multiple copies of the same area.
</p>
<p>
To create an instanced dungeon:
</p>
<ol>
<li>
<bold>Build the section of your world</bold> that will
be instanced, and note what rooms are in it. Keep
the <bold>list of rooms in a global</bold>.
</li>
<li>
Make sure there are <bold>no direct entrances</bold> into
the instanced area so that players and monsters cannot
just walk in.
</li>
<li>
Provide a <bold>door with custom code</bold> that will redirect
each player to the right instance of the world. When the
PC walks through the door, it figures out which instanced
world should be used and calls ActorMove() to move the player
to that world.
</li>
<li>
The rooms should always be kept <bold>sterile</bold>, which means no
player characters or NPCs from outside can enter, and nothing from within
can leave. They reason they're sterile is because they act as a template
for all the instances.
</li>
<li>
When a player enters an instanced set of rooms, and the rooms need to be
created, call <bold>SavedGameEnum()</bold> to see if the player already
has entered the instanced rooms.
</li>
<p>
If this is the first time the player is entering the instance,
call <bold>ObjectClone()</bold> to clone all the rooms and their
contents. The player will be allowed to enter the copies of the
cloned rooms.
</p>
<p>
If the player has entered, the call <bold>SavedGameLoad()</bold> to load
in the instanced rooms. This will loaded in the instanced rooms that,
at some point, were created using ObjectClone().
</p>
<li>
When a player needs to be moved into the instance, <bold>find
the room</bold> to move them into using the remapping list,
searching for the old room and returning the new one.
</li>
<li>
That's all you need to do, except when <bold>all the players</bold> leave
the instanced dungeon and are finished with it,
call <bold>SavedGameSave()</bold> and delete all of the
instanced rooms and their contents.
</li>
</ol>
Describes the online help system.
<section><p><bold><big>Online help</big></bold></p></section>
<p>
Creating online help for a Circumreality title is easy. All you need
to do is create a number of "Help" resources; they will
automagically be placed into a table of contents and indexed.
</p>
<p>
To create a help resource:
</p>
<ol>
<li>
Select the <bold>Add new resource</bold> option from
the <bold>Misc</bold> menu,
</li>
<li>
In the "Add a resource" page that appears,
press the <bold>"Help"</bold> button.
</li>
<li>
In the "Modify resource" page, type in
the help topic name, like <bold>rHelpGetCommand</bold>. The
resource name should be unique for each help topic.
</li>
<li>
Press <bold>"Add"</bold> to add a resource for a specific
language.
</li>
<li>
In the "Help resource" page that appears, type
in a <bold>Name</bold> that will be visible to the players.
This name must also be unqiue for each help topic.
</li>
<li>
Type in the <bold>Category</bold> in which the help topic
will be placed (for the table of contents). To place
the help topic at the top of the TOC, leave this blank.
</li>
<p>
The TOC is like a directory tree. If you wish to place
the help topic several sub-directories down, just use a
forward slash ('/') to separate them. For example:
"Plants/Trees/Evergreen", will create a "Plants" category
at the top of the TOC. If the user clicks on that they
will see the "Trees" category. Underneath that is the
"Evergreen" category, and that contains the article
for pine trees.
</p>
<li>
You can place the article in a <bold>secondary category</bold> if
you wish, or leave the entry blank.
</li>
<li>
Enter a <bold>short description</bold> for the category like,
"An article about pine trees".
</li>
<li>
Next you need to enter the <bold>MML text</bold> for the category.
MML is a lot like HTML (used for web pages). If you have
never used HTML before, look up some reference manuals
on the Internet, or look at some other help topics to see
how the format works.
</li>
<p>
Press the <bold>Test MML</bold> button to make sure you don't
have any mistakes in your MML, and that it looks good.
</p>
</ol>
<p>
Those are the basics. When you next run your Circumreality title the
help article will automatically be added to the table of contents
and indexed.
</p>
<br/><section><p><bold><big>Advanced online help</big></bold></p></section>
<p>
At the bottom of the help-resource page are some options
that might come in useful:
</p>
<ul>
<li>
The <bold>Function</bold> and <bold>Function parameter</bold> fields
are useful for any help topics that should only be seen
by administrators or characters with specific skills.
</li>
<p>
If you fill in "Function" with a function name, the function
will be called if the user tries to access the help topic.
The first parameter for the function will be the actor searching
for help, and the second will be the "function parameter" (as
a string.) If your function returns TRUE, the player will be
allowed to see the help topic. Returning FALSE will hide the
help topic from the player.
</p>
<li>
The <bold>Book</bold> field allows you to divide help into
several books, and only show topics for a specific book.
While this functionality is not usually needed, it does come
in handy for role-playing knowledge.
</li>
<p>
For example: You could
write several help topics about Elvish culture that the player
would only have access to if they played an Elf character (or
learned some sort of skill). Then, you could create a new
command that mimiced the "Help" command, but was designed
for role-playing knowledge, like a "Do I know" command.
If the user had Elvish role-playing knowledge they would
be given access to the help in the "Elvish" book.
</p>
</ul>
<br/><section><p><bold><big>Online help functions</big></bold></p></section>
<p>
Some useful online help functions are:
</p>
<ul>
<li>
<bold>HelpArticle()</bold> searches for a specific help
article and returns the MML resource (that can be sent to
the player's computer), along with the categories the
help topic is in.
</li>
<li>
<bold>HelpArticleMML()</bold> just returns the MML
resource. It automatically includes links at the end of the
page so the player can see the TOC where the article is
placed.
</li>
<li>
<bold>HelpContents()</bold> returns a list of all the
articles and sub-directories given a directory in the
table of contents.
</li>
<li>
<bold>HelpContentsMML()</bold> returns a MML resource
with all the contents information from HelpContents().
</li>
<li>
<bold>HelpSearch()</bold> performs a keyword search.
</li>
<li>
<bold>HelpSearchMML()</bold> returns a MML resource from
a keyword search.
</li>
</ul>
A little bit of information about automatically downloaded data and files.
<section><p><bold><big>Automatically downloaded data and files</big></bold></p></section>
<p>
When Circumreality produces a .crf file, it automatically scans all the
resources for file references, such as .wav files, and includes them
in the .crf file. That way, any data file referenced in a resource
will be included in the .crf file for distribution.
</p>
<p>
If the IF title is run multiuser over the Internet, then any
file in the .crf file will be transferred over as its needed.
</p>
<p>
There are some exceptions to this... the Circumreality client's install
will install a few files that are commonly used in all Circumreality
titles. These include MikeRozak.tts (text-to-speech
voice), EnglishInstalled.mlx (lexicon for text-to-speech),
and LibraryInstalled.me3 (basic 3d objects).
The Circumreality client will <bold>not download</bold> these files from
the server.
</p>
<p>
Furthermore, if a 3rd party application copies a .tts or
or .mlx file into the client application's directory then it
will use those rather than installing them.
</p>
<p>
What this means:
</p>
<ul>
<li>
If you have a more recent (or older) version of the .tts or
.mlx files listed, you cannot guarantee that your versions
will be used. (The LibraryInstalled.me3 won't be any problem
though; it will update properly.) Usually, this is not
a problem.
</li>
<li>
The user may have installed a high-quality TTS voice over
the default voices, resulting in better sounding TTS.
A high quality TTS voice is 50+ megabytes, and not something
most people want to download.
</li>
</ul>
<p>
See also the "Binary database", since files in the binary database
are automatically downloaded.
</p>
Shows how to use the binary database to store images that players upload.
<section><p><bold><big>Binary database</big></bold></p></section>
<p>
Players may wish to upload images of their characters so that whenever
another player sees their character, the image will automatically
be downloaded. Likewise, sounds could be customized by the players.
</p>
<p>
The <bold>binary database</bold> lets you accomplish this. The database
just stores binary data accessed by a file name, such as "test file.jpg".
Various functions are provided to add/remove data to the database,
see below. Once data is in the database, <bold>any automatic download
requests from the client</bold> will access the database, as well
as precompiled binary information. Thus, any reference to "test file.jpg"
on the client will automatically pull the data from the binary database.
</p>
<p>
The binary database supports the following methods:
</p>
<ul>
<li>
<bold>BinaryDatabaseEnum()</bold> - Enumerates all entries in the
database, or all entries beginning with a specific prefix.
</li>
<li>
<bold>BinaryDatabaseGetNum()</bold> - Gets the name of a database
entry based on the index into the database.
</li>
<li>
<bold>BinaryDatabaseLoads()</bold> - Loads binary data from the
database.
</li>
<li>
<bold>BinaryDatabaseNum()</bold> - Returns the number of entries
in the database.
</li>
<li>
<bold>BinaryDatabaseQuery()</bold> - Returns the size and modification
dates for the data.
</li>
<li>
<bold>BinaryDatabaseRefresh()</bold> - Sends a message to all the connected
clients that a file in the database has been changed, and that they should
reload it.
</li>
<li>
<bold>BinaryDatabaseRemove()</bold> - Deletes an entry from the binary
database.
</li>
<li>
<bold>BinaryDatabaseRename()</bold> - Renames a database entry.
</li>
<li>
<bold>BinaryDatabaseSave()</bold> - Saves binary data into the database.
</li>
</ul>
Describes how to log text into the text log.
<section><p><bold><big>Text/event logging</big></bold></p></section>
<p>
Circumreality has a "text log" that can be used to store gigabytes of
log information, such as when users log on, log off, what actions
they take, etc. Logging is vitally important for finding bugs as
well as detecting players that cheat.
</p>
<br/><section><p><bold><big>Log everything</big></bold></p></section>
<p>
As a general rule, log everything, or at least, provide the ability
to log everything.
</p>
<p>
Categorize each event into one of four levels:
</p>
<ul>
<li>
<bold>Very important (Category 1)</bold> - These are events that you
always want to log, such as when your code realizes that its data
is corrupt, or even that a user has logged on/off.
</li>
<li>
<bold>Important (Category 2)</bold> - Category 2 events will always
be logged too, but they're not as critical. For example: Log commands
that the user types.
</li>
<li>
<bold>Nice to know (Category 3)</bold> - Category 3 events are only logged
if you are suspicious about a specific user, or you set the system
to an elevated state of alert. For example: A character picks up
or drops an item.
</li>
<li>
<bold>Unimportant (Category 4)</bold> - These events are only logged
if a user is marked as being very suspicious or the system is set
to a very high state of alert. For example: Storing all the MML sent
from the server to the clients.
</li>
</ul>
<p>
In you code, you should check
either <bold>gTextLogUser[EVENTLEVEL]</bold> or <bold>gTextLogSystem[EVENTLEVEL]</bold> before
logging an event. If the value is TRUE then log the event.
Use gTextLogUser[] if the event pertains to an action from the specific
user. gTextLogSystem[] is for events that are independent of the user,
such as something a NPC does.
</p>
<p>
There are three ways to log an event:
</p>
<ul>
<li>
<bold>TextLog()</bold> - This logs a string, as well as an optional object.
The current user, the user's character, and the location of the user's character
are also logged. You will probably use TextLog() more often than the other
two logging functions.
</li>
<li>
<bold>TextLogNoAuto()</bold> - Like TextLog(), this logs a string, but
it lets you specify which user object, character object, room object,
and object are associated with the log. Use TextLogNoAuto() if, for example,
a NPC acts.
</li>
<li>
<bold>Trace()</bold> - Calling Trace() will only log the text line. You
won't be able to store additional information in the log, such as
the user or room.
</li>
</ul>
<p>
Combining, these two together, a sample line of logging code might
look like:
</p>
<p align=center><table width=80%>
<tr><td>
if (gTextLogUser[3])<br/>
&tab;TextLog ("Pick up", vObjectThatPickedUp);
</td></tr>
</table></p>
<br/><section><p><bold><big>Some more details</big></bold></p></section>
<p>
The globals, gTextLogUser and gTextLogSystem, are intializes by
calls to <bold>TextLogPriorityAdjust()</bold>. TextLogPriorityAdjust() is
called in the connection object when a message arrived from a user.
It does the following:
</p>
<ol>
<li>
Adjusts <bold>gTextLogUser and gTextLogSystem</bold>. These
values are affected by <bold>gTextLogAlert</bold>, which controls the
overall system alert, and ther user's <bold>pUserLogAlert</bold> which
controls what priority messages are to be logged for a specific user.
Increase gTextLogAlert to record lower priority (higher value) events
for all users. Increase pUserLogAlert to record lower priority (higher
value) events for a specific user, such as one that might be cheating.
</li>
<li>
The function also calls <bold>TextLogAutoSet()</bold> to tell
the text log what user, room, and player character to automatically
use when TextLog() is called.
</li>
</ol>
<p>
It is unlikely that you will need to call TextLogPriorityAdjust() directly,
but knowing of its existence helps you understand what's going on.
</p>
<br/><section><p><bold><big>Accessing the text log</big></bold></p></section>
<p>
Circumreality provides several functions that are useful for accessing the
text log database:
</p>
<ul>
<li>
<bold>TextLogAutoGet() and TextLogAutoSet()</bold> - Let you set the
user, character, and room that are used by TextLog().
</li>
<li>
<bold>TextLogDelete()</bold> - Deletes one of the text logs files.
Circumreality creates one new file every hour. Circumreality includes code that automatically
deletes logs older than a few days, affected
by <bold>gTextLogDeleteOld</bold>.
</li>
<li>
<bold>TextLogEnableGet() and TextLogEnableSet()</bold> - Completely disable
text logging. These are used when Circumreality is running in an offline, single-player
mode, and logging isn't warranted.
</li>
<li>
<bold>TextLogEnum()</bold> - Lists all of the log files, one per
hour that the world has been running.
</li>
<li>
<bold>TextLogNumLines()</bold> - Returns the number of events logged
in specific text log file.
</li>
<li>
<bold>TextLogRead()</bold> - Reads in a line/event from a specific
text log file.
</li>
<li>
<bold>TextLogSearch()</bold> - Searches through the text log files
for all events that occur within a specific date/time range, for
a specific user, character, room, or object, and with a specific
sub-string.
</li>
</ul>
Provides basic interactive fiction definitions, classes, objects, and functions.
The Basic Interactive Fiction library provides definitions, classes, objects, and functions that are necessary for interactive fiction to work. Using this library is highly recommended, since without it, you'll have to write your own.
Describes how a command typed in by a user is processed.
<section><p><bold><big>How command parsing works</big></bold></p></section>
<p>
This tutorial describes how a command, like "pick up
the sword", is received by the
server, processed, and then acted upon.
</p>
<section><p><bold><big>From the server</big></bold></p></section>
<p>
Commands from the client are sent to the appropraite connection
object. (Usually a cConnection.) The ConnectionMessage()
method is called with the command message.
</p>
<p>
Assuming that the user isn't in a specific state (like logging
in), the ConnectionMessage() function eventually passed
the message down to a handler that calls two functions:
</p>
<ol>
<li>
<bold>CommandParse()</bold> - This parses the command and
determines which parse makes the most sense, based upon
where the player's character and other inff.
</li>
<li>
<bold>CommandAct()</bold> - Once the best choice is determined,
the command is acted upon.
</li>
</ol>
<p>
Simple... except that a lot is done in these two functions.
</p>
<section><p><bold><big>NLPParse()</big></bold></p></section>
<p>
When the oParserVerb() object (in the Basic IF Library) initializes,
it loads a few commonly used NLPRuleSets into the "Commands"
parser. These are rParserVerbRuleSet and rNLPCommon. Let me explain.
</p>
<p>
Circumreality comes with some built in utility functions for helping
parse commands and speech passed to a chatterbot. The
built in functions are all accessed from the NLPParserXXX()
and NLPRuleSetXXX() functions. You may wish to look
through their documentation for complete instructions,
but here's an overview:
</p>
<p>
The built-in parser functions accept a string from the
user, such as "pick up the lantern", breaks it into
individual words ("pick", "up", "the", "lantern") and
then uses some NLP rules to figure out what the
command means.
</p>
<p>
The NLP rules are actually very simple (and primitive).
All they do is match a series of words and convert them
to a new series of words. In the case of parsing "pick up the lantern",
there is a rule someplace in rParserVerbRuleSet that
converts "pick up" to "`get". (Note the '`' in front
of "get". It tells code later-on down the pipe
that "`get" is a langauge-indepent result, and is <bold>not</bold> a
word typed in by the user.)
A temporary rule (see below) will convert "the lantern"
into "|0123456789abcdef0123456789abcdef" (or some other GUID),
indicating the exact object that's referenced.
</p>
<p>
The parser ends up producing a list of hypothesis.
One entry is the original, "pick up the lantern", with a score of
1.0. One entry is the correct one, "`get |GUID" (where GUID is
that really long number that identifies the object).
There are also other, less conclusive parses, like "pick up |GUID"
and "`get the lantern", which will be discarded later.
</p>
<p>
The function doing the parsing is NLPParse(). It returns
all the hypothesis in a list.
</p>
<section><p><bold><big>CommandParse() call</big></bold></p></section>
<p>
The CommandParse() call eventually calls into NLPParse(). However,
it first does some other work...
</p>
<p>
CommandParse() enumerates all the objects that are near the
user, using ObjectsEnumNearby(). oParserVerb is added onto
this list even though it technically isn't nearby.
</p>
<p>
Each nearby object is asked for any one-off rules that need
to be added to the list of rules passed into NLPParse().
This is done by calling NLPRuleSetTemp(), which
in turns calls NLPRuleSetTempName() and NLPRuleSetTempVerbs().
</p>
<p>
NLPRuleSetTempName() will return a rule that converts the
object's name, such as "the lantern" or "lantern" or even
"the lamp" into the "|GUID" string. Since every nearby
object is queried for a name, the call ensures that
the parser will handle the names.
</p>
<p>
Some objects will also support NLPRuleSetTempVerbs(), which
identifies verb-rules that are only available when the
object is nearby. In the example below about moving
a chess piece on a chessboard object, the rule
might be to convert "move" to "`MOVECHESSPIECE".
</p>
<p>
Finally, temporary rules are added for pronouns, so that
"it" will refer to the last object referenced, etc.
See NLPPronounGet() for more information.
</p>
<p>
The user's command ("pick up the lantern"), along
with the temporary rules, are passed into NLPParse(),
and a list of hypothesis is returned. Each hypothesis
has the possible meaning (such as "`get |GUID") and
a score.
</p>
<p>
Each nearby object is asked if it supports its own command
parser. If it does, it will return either 1 or 2 from
the NLPCommandParseQuery() call. Most objects <bold>will not</bold> support
their own parser. The reason they would is if having the
nearby allowed for some new commands. For example: If a chessboard
were nearby, a user could type "Move CHESSPIECENAME to LOCATION".
The normal verb parser in oParserVerb doesn't know about
chess board moving, but because the object is around, suddenly
the parser understands chessboard commands.
</p>
<p>
oParserVerb returns 2 from a NLPCommandParseQuery() to indicate
that it wants to parse commands.
</p>
<p>
Once CommandParse() knows all of the nearby objects that want
to parse commands. It then procedes to send <bold>all</bold> of
the hypothesis to <bold>all</bold> of the flagged objects and
asks them if they can make heads or tail of the command.
This call is made through NLPCommandParse().
</p>
<p>
If a parser can understand the command, it will return a
confidence score along with a callback that will take action
on the parse, and parameters to be passed into the callback.
</p>
<p>
The returned confidence score is multiplied by the score for
the hypothesis. The final hypothesis chosen is the one
with the highest total score resulting from the multiplication.
This way, the confidence score allows one NLPCommandParse()
call to make a higher "bid" than others, ensuring that it
gets to act upon the command.
</p>
<p>
The winner (highest score) is returned from CommandParse()
and then passed into CommandAct(). The information
about the winner includes the callback and parameters
supplied by NLPCommandParse().
</p>
<section><p><bold><big>CommandAct() call</big></bold></p></section>
<p>
CommandAct() checks to see if there was a winner from
CommandParse(). If there wasn't one, it sends a message back
to the player saying that the command wasn't understood.
</p>
<p>
If there is a winning parse, its callback is called, and the
parameters it requested are passed into it. The callback
then does whatever action is deems necessary.
</p>
<section><p><bold><big>oParserVerb</big></bold></p></section>
<p>
While any object can providing a parser, most of the
parsing is done in the oParserVerb object. This section
will go into detail about how it works.
</p>
<p>
When NLPCommandParse() is called for a hypothesis,
such as "`get |GUID", the oParserVerb object will remove
the '`' character from the first token (in this case "`get")
and then prepend "Parse_". Therefore, "`get" is turned
into "Parse_get", "`look" into "Parse_Look", etc.
</p>
<p>
If the oParserVerb object supports the Parse_Get (or Parse_Look, etc.)
method then
that is called, passing in exactly the same parameters
as were passed into NLPCommandParse(). If Parse_Get is
not supported then NLPCommandParse() returns without
setting a callback.
</p>
<p>
Tip: If you wish to add new verbs to the command parser,
just add them the oParserVerb object. First, produce rules
in a NLPRuleSet resource that convert the verb into a token.
(For example: To allow jumping, you'd make a rule that converts
"Jump" or "Leap" to "`jump".) You'll need to call NLPRuleSetAdd()
with your resource whenever the IF session starts.
Then, add a new public method to oParserVerb called "Parse_Jump".
Have it parse the command (see below), and return
information about the parse. That's all.
</p>
<p>
Back to the call into Parse_Get()...
</p>
<p>
Parse_Get() knows that the first token is "`get". All it has
to do is look at whatever follows and determine what should
happen if this command is actually acted upon.
</p>
<p>
First, lets assume a valid command was passed in: It
would be "`get" followed by an object (from "|GUID").
</p>
<p>
Parse_Get() would first figure out a score. If the object
was visible to the actor (see ObjectCanSee()), and
accessible (sse ObjectCanAccess()), and not too heavy to
pick up, then the score would be 1.0. However, if any of
these conditions was not met, the score would be lower, such as
0.1.
</p>
<p>
This way, if there are two lanterns, only one of
which can be picked up, and the user
types "get lantern", then the lantern which can be picked up
will have a score of 1.0, while the one that can't will have
a score of 0.1. The higher score will be chosen for the command,
so the lantern which can be picked up will be taken. However,
if the only lantern around can't be picked up, its callback
will be run, and will tell the user that he can't reach the lantern.
</p>
<p>
Sometimes the hypothesis already has an acceptable parse.
If Parse_Get() has produced a lower score than what's already
there then it just returns. If its score is higher, Parse_Get()
will overwrite the existing parse with its own.
</p>
<p>
Next, Parse_Get() needs to provide a callback and parameters
to pass to it. For convention's sake, the callback will
be Act_Get(), and the parameters depend upon whether or
not the checks to see if the lantern could be reached succeded.
</p>
<p>
If the player can pick up the lantern, then the parameter might
just be the lantern object. If the player can't pick it
up (because it's on a high shelf, or is nailed to the ground)
then the parameters would be the object and some sort of value
indicating that it couldn't be picked up and why.
</p>
<p>
ParseGet() returns its score, callback, and callback parameters.
It then waits for the callback to be called. (The callback might
not be, since there may be better parses.)
</p>
<p>
When/if the callback is called, it can look at the parse parameters
and act accordingly. In the case of Act_Get(), it would
move the object to the player's possession and alter the user,
or maybe inform the user that the object couldn't be moved.
</p>
<p>
NOTE: Act_Get() needs to call NLPPronounSet() to ensure that
the pronouns such as "it" and "he" are modified to the
most-recently referenced objects.
</p>
<section><p><bold><big>oParserVerb - Tips and tricks</big></bold></p></section>
<p>
If you want to occasionally override a verb handled
by oParserVerb, but only for certain types of objects,
there are two options:
</p>
<ol>
<li>
You can write a custom parser for each object that has
the special get code. This was explained above.
</li>
<li>
You can create a new method in oParserVerb to handle
the extra verb. See below...
</li>
</ol>
<p>
To handle the alternate form of the verb by modifying
oParserVerb:
</p>
<ol>
<li>
Create a NLPRuleSet resource that converts the tokenized
form of the verb, such as "`get" to "`MySpecialGet" with
a probability of 99. (As close to 100% as you can get.)
</li>
<li>
Make sure to load this rule set into the "Commands" parser
when the program stars up. The easiest way to do this
it to have an automatically-created object whose Constructor()
and Constructor2() load it in.
</li>
<li>
Add Parse_MySpecialGet() to oParserVerb. If it does successfully
parse it will need to return a score higher than 1.0 to
ensure that its parse it used instead of the one
from Parse_Get().
</li>
</ol>
Describes useful functions for dealing with noun cases and noun/verb agreement.
<section><p><bold><big>Noun cases and noun-verb agreement</big></bold></p></section>
<p>
Online fiction generates a lot of text (to be displayed or spoken)
by splicing together two strings. For example: When a PC picks
up and object, such as a lantern, the Circumreality code will
need to splice together "You" + "pick up" + "a lantern" + ".".
However, at the same time, Circumreality may need to display the string
to other players in the room, in which case it becomes
"Fred Smith" + "picks up" + "a lantern" + ".". Notice how
not only does the name change from "You" to "Fred Smith", but
the verb changes from "pick" to "picks". This is the crux
of the problem... it's called noun cases (going from "You" to
"Fred smith") and noun/verb agreement ("pick" to "picks").
</p>
<br/><section><p><bold><big>Getting an object's name</big></bold></p></section>
<p>
So if you have an oLantern object, how do you get its name,
"the shiny lantern"?
</p>
<p>
The easiest way to get an object's name is just to concatenate
the object to a string, such as "You pick up " + oLantern;
However, this doesn't give the system much to work with. For example,
it's not sure if you want "the" or "a" before the lantern's
name, or if you want the full (long) name "shiny lantern" or
a the short one ("lantern"). As a result, getting an object's
name using this method will work, but you won't be able to control
the results.
</p>
<p>
Similarly, you could call oLantern.Name(); it would return
the same string as was appended above.
</p>
<p>
The correct way to get an object's name is to use the
NLPName() method. It allows you to specify some paramters
that enable a better name display:
</p>
<p>
The first (optional) parameter is the actor's name. The actor
is the one looking at the object. Most of the time this won't
make any difference. However, if the actor is the same as the
object, then NLPName() will automatically display the
correct pronoun. Thus oLantern.NLPName(oLantern, ...) will
automatically return "you", or whatever is appropriate for
the current language.
</p>
<p>
The second (optional) parameter is a bit-field that lets you
control what form of the object's name should be displayed;
it is "a shiny lantern", "the lantern", or "10 lanterns".
The flags are global variables of the form gNC_XXX_YYY.
You "or" them together, like: (gNC_Art_Definite | gNC_Count_Many
| gNC_Caps_Upper).
Some ones which you may find useful:
</p>
<ol>
<li>
<bold>gNC_Art_Definite</bold> causes "the" to be
displayed. <bold>gNC_Art_Inefinite</bold> causes "a" or "an"
to be displayed. <bold>gNC_Art_None</bold> shows the
name without any articles.
</li>
<li>
<bold>gNC_Caps_Upper</bold> will capitalize the first
character in the same, useful for the start of a sentence.
</li>
<li>
<bold>gNC_Case_Subjective</bold> causes pronouns to use
the subjective case ("I", "he", "she"),
while <bold>gNC_Case_Objective</bold> causes pronouns to
be objective ("me", "him", "her"). You can
display the possessive form ("The lantern's")
using <bold>gNC_Case_Possessive</bold>. A whole
host of gNC_Case_YYY flags are available for langauges
other than English.
</li>
<li>
<bold>gNC_Case_Single</bold> causes the singular version
of the object to be displayed, such as
"lantern". <bold>gNC_Case_Few</bold> and <bold>gNC_Case_Many</bold> will
show the plural form, "lanterns". Some languages have
two definitions of plural, "few" (either 2 or 3) and "many" (4+).
That's why there are two plural cases. If your interactive
fiction will only every be English then use either one.
</li>
<li>
<bold>gNC_Gender_Male</bold>, <bold>gNC_Gender_Female</bold>,
and <bold>gNC_Gender_Nuter</bold> will control the gender
of the object's string. In English, this usually only affects
pronouns such as "he" and "she".
</li>
<li>
<bold>gNC_Verbose_Long</bold> and <bold>gNC_Verbose_Short</bold> let
you specify the use of the object's short string ("lantern") or
long string ("shiny lantern"). Not all objects will differentiate though.
</li>
</ol>
<p>
You only need to use a flag if the default result isn't
right. Usually the defaults will be correct.
</p>
<p>
The third parameter (also optional) is a boolean that specifies
whether the "noun-case" string is to be appended. If this
is TRUE or not set, a number surrounded by brackets will be
appended to the object's name. This number stores the noun-case
information (such as plural or singular), and is useful later
on for noun/verb agreement. If you will eventually pass this
string on to NLPStringFormat() to do noun/verb agreement,
then you'll want the noun-case string added.
</p>
<p>
If you wish to display an object being possessed by another
object, such as "Mike's lantern", then use the NLPNamePossessed()
call. It not only saves you some work, but it makes localization
to other languages easier. For example: Spanish does not use
's to identify possession, but instead uses "the lantern of Mike".
The NLPNamePossessed() function will automatically handle such
changes.
</p>
<br/><section><p><bold><big>Creating objects that display the right name</big></bold></p></section>
<p>
If you create an object in MIFL and then call the NLPName()
method, you'll be sorely disappointed. First of all, it will
display the object's name, such as "oLantern" as the actual
string. Second, if your object's name is in any way
irregular, such as "person" vs. "people" (singular and plural forms)
then NLPName() won't work properly.
</p>
<p>
To make NLPName() produce the right string, you may need to change
some of your object's properties, and maybe even an occasional
method:
</p>
<p>
First off, you will need to modify <bold>pNLPNounName</bold> to
your object's name, such as "lantern". (Note: Keep the first
character lower case unless it's a proper name and you always
want it capitalized.) Be aware that pNLPNounName is
different than <bold>pNLPParseName</bold>, which is used to
identify the object in a command; see "How command parsing
works" for a description of pNLPParseName.
</p>
<p>
If the name has a long vs. short form, you'll need to use
some special tags within pNLPNounName. In this example, to
create a long name of "shiny lantern", you'll
need to set pNLPNounName to "(long?shiny )lantern".
</p>
<p>
The parenthsis in the string identify that the noun-case needs
to be tested. The "long" part before the '?' chacter indicates
that the gNC_Verbose_Long setting is to be tested. If it
is set then the "shiny " string is placed before "lantern".
</p>
<p>
You could also make the short version be "lamp" by
changing the string to "(long?shiny lantern:lamp)".
The characters after the colon (and before the last parenthesis)
are used it gNC_Verbose_Long is not set.
</p>
<p>
The string, "(lon?shiny lantern:lamp") would also work.
Notice that "long" has been shortened to "lon". You can
abbreviate the noun case to two to four letters depending upon
the name. It cannot be so short that it's indistinguishable
from another name. For example: There is also a "short" test to
see if the short form of the noun is desired (which is the opposite
of the "long" test) and a "single" test to see if the noun
is sinugular. You can't abbreviate "short" down to just "s" because
then the computer wouldn't know if you meant "short" or "single".
</p>
<p>
You can use a similar technique if the noun has irregular
plurals. For example: To show "hippopotumus" when one hippo
is around, and "hippopotumi" when several around about,
you would set the object's pNLPNounName to
"hippopotum(plur?i:us)". The "plur" test checks for a plural
noun case. If pNLPNounName handles the plural case,
set <bold>pNLPNounNoAutoPlural</bold> to TRUE.
</p>
<p>
But that's not all...
</p>
<ol>
<li>
Some languages, like French, differentiate between animate and
inanimate objects. To indicate that your object is
animate, change the <bold>pNLPNounAnimate</bold> property.
</li>
<li>
To indicate that your noun is plural by default,
change the <bold>pNLPNounCount</bold> property. For example:
You'd use this object was "pants".
</li>
<li>
To change the gender of your object,
use <bold>pGender</bold>. This is particularly
useful for pronouns. Objects default to the
neuter gender.
</li>
<li>
If you never want to prepend an article ("a", "an", or "the")
in front of your object's name,
set <bold>pNLPNounNoAutoArticle</bold>. You'll want to do
this for proper names, since "the Mike" or "a Mike" is not
standard English. You can also use this, along with
the noun-case tests in pNLPNounName to create
custom aritcles, such as "(indef?lots of:(defi?the)) gold".
</li>
<li>
Possessive forms of an object automatically have "'s" or "s'"
added. If you would rather handle the possessive case
in pNLPNounName, then set <bold>pNLPNounNoAutoPossessive</bold> to
TRUE.
</li>
<li>
To always display a quantity in front of the noun,
set <bold>pNLPNounQuantity</bold> to TRUE. For example, if
you have a stack-of-gold object, you may wish to have it
always display the number of coins in it. You may
also wish to write your own NLPNounQuantity() method for
the object.
</li>
</ol>
<br/><section><p><bold><big>Noun/verb agreement</big></bold></p></section>
<p>
As shown at the beginning of this tutorial, your will encounter
situations where a noun string and a verb string are concatenated
together, and you need to conjugate the verb to match the noun...
"I am...", "You are...", "He is...", "We are...", "You(pl) are...",
and "They are...".
</p>
<p>
Circumreality provides some functions that make this easy...
</p>
<p>
You've already seen the first set using the NLPName() method.
It automatically appends a number surrounded in brackets. This
number indicates the number and person (1st, 2nd, or 3rd) of
the noun. For example: oLantern.NLPName() might return
"The shiny lantern{3543434}".
</p>
<p>
When you use a verb that relies upon a concatenated noun
string (which will have the "{number}" appended), you
must write out the three verb forms in parenthesis
and separated by a '/'. For example: "(am/are/is) in good shape.".
</p>
<p>
The first form is used for the 1st person singular ("I").
The second is used by the 2nd person singular and all plural forms
("you", "we", "they"). The third entry is for
the 3rd person sinular ("he" or "she").
</p>
<p>
Then the noun string and verb string are concatenated they will
look something like this: "The shiny lantern{3543434} (am/are/is)
in good shape."
</p>
<p>
If you pass this string to NLPVerbForm(), it will return
"The shiny lantern is in good shape.". It uses the number in
curly braces to determine which form the verb should take.
</p>
<p>
An even better way of displaying the string is to use
the NLPStringFormat() function, which is a combination
of the StringFormat() method and NLPVerbForm(). It
works by replacing "%1", "%2", etc. in the string to the given
argument, and then passing the concatenated string to
NLPVerbForm().
</p>
<p>
Example: NLPStringFormat("%1 (am/are/is) in good shape.",
oLantern.NLPName(Actor, gNC_Caps_Upper | gNC_Art_Definite));
Will produce the same results.
</p>
<p>
NLPStringFormat() is the recommended solution for concatenating
nouns and verbs since it also makes localizing easier.
Not all languages use the same word order, so the "%1", "%2", etc.
allow the order of insertion to be flipped depending upon the
language.
</p>
<br/><section><p><bold><big>Advanced</big></bold></p></section>
<ul>
<li>
You might wish to look at the <bold>NLPPronoun()</bold> function. This will
return a pronoun string, such as "he", "she", or "it" based
upon the noun-case information passed in.
</li>
<li>
There is more to an object's name than just pNLPNounName and
pNLPNParseName, especially when dealing with characters.
</li>
<p>
You may wish to set <bold>pNLPNameReal</bold> and <bold>pNLPNameRealParse</bold> with
the character's name, such as "Bill Smith". Or, you might
even with to write your own code for <bold>NameReal()</bold>.
</p>
<p>
If your character has an obvious profession, such as "innkeeper" or
"guard", then provide
a <bold>pNLPNameProfession</bold> and <bold>pNLPNameProfessionParse</bold>,
or write your own <bold>NameProfession</bold>.
</p>
<p>
If you have races, such as cRaceElf, then you will need to
write your own <bold>NameRace()</bold>.
</p>
<p>
In fact, if your character has a NameRace(), you don't even need
to provide a pNLPNameReal or pNLPParseName!
</p>
</ul>
Covers the cObject, a generic object class that's a superclass of all other MIF objects.
<section><p><bold><big>cObject - Generic objects</big></bold></p></section>
<p>
The cObject class is at the heart of all the interactive
fiction objects, such as lanterns, monsters, player characters,
doors, and rooms. Every other interactive fiction object
is based directly or indirectly from cObject.
</p>
<p>
This tutorial will give an overview about how to use cObject.
You should read this before learning how to use cRoom for rooms,
cCharacter or cMonster for monsters, NPCs, and player characters, cDoor
for making doors, cContainer for containers, etc. since they're
all based on cObject.
</p>
<br/><section><p><bold><big>Making a new object</big></bold></p></section>
<p>
To make a new object for your world, such as a lantern:
</p>
<ol>
<li>
Create a new object using the <bold>"Objects" menu, and
selecting "Add a new object"</bold>.
</li>
<li>
<bold>Type in the name for the object;</bold> if it's an object in
the world (such as a specific lantern) then prefix the
name with a "o" so it's easy to identify it as such.
If it's a class, such as a "light emitting class" then
prefix it with "c".
</li>
<li>
Under the object's <bold>"Superclass" tab</bold>, press
the <bold>"Add class"</bold> button.
</li>
<li>
In the "Add class(es)" page, check the <bold>"cObject"</bold> class.
This tells MIFL that your lantern object is based off
the generic object.
</li>
<li>
Press <bold>"Back"</bold> to return to the classes page;
you'll see the "cObject" class listed.
</li>
<li>
Check the <bold>"Automatically create an an object"</bold> button;
this is what makes it an object and <bold>not</bold> a class.
</li>
<li>
Below that, <bold>select</bold> the room that the lantern will
appear in. If it starts out held or contained by another
object then select that object.
</li>
<li>
<bold>Don't bother</bold> changing the object's GUID since
a unique one has automatically been generated.
</li>
<li>
Switch to the <bold>properties</bold> tab.
</li>
<p>
When you selected the cObject class, some properties were
automatically added. These include pNLPParseName, pNLPNounName,
and pVisual.
</p>
<li>
Type in <bold>pNLPParseName</bold> and <bold>pNLPNounName</bold>.
These are the names used to identify the object when its name
is typed in a command, and how to diplay the name to the
user. For more information on these,
see the <bold>How command parsing works</bold> and <bold>Noun
cases and noun-verb agreement</bold> tutorials.
</li>
<p>
Some commands, particularly in conversations, allow the use of
possessives, like as "Bill Smith's". Circumreality already contains code
to create possessives in NLPMakePossessive(), but if this
ends up making a mistake then you may need
to provide your own <bold>pNLPParseNamePossessive</bold>.
</p>
<li>
For <bold>pVisual</bold> you'll need a resource that
is to be used to "draw" a picture of the object. It
can either be an Image, ThreeDScene, ThreeDObject, Title,
or Text resource. If you don't have one now, leave this
blank and a default visual will be provided.
</li>
<li>
You should set <bold>pWeightSelf</bold> to the weight of
the object, in kilograms. <bold>pVolumeSelf</bold> is
the area that the object takes up, in liters. If the object
is roughly as dense as water, use the same values for both.
However, if the object is light but large, like a pillow,
you'll need a high volume.
</li>
<li>
Since you want your lantern to provide light,
add the <bold>pProvidesLight</bold> property and set
it to <bold>TRUE</bold>. Of course, this means that
your lantern is always on, but it's good enough
for the purpose of this tutorial.
</li>
<li>
If you want your lantern to be described the first
time a player sees it, set <bold>pDescribed</bold>.
</li>
</ol>
<p>
Assuming you had a room to place the latern in, you should
be able to compile the project and visit the room with
your character. The lantern will be there and providing light.
</p>
<br/><section><p><bold><big>Properties and methods</big></bold></p></section>
<p>
The default cObject supports numerous properties and methods.
A typical object will only need to override some of the
properties and methods. For example: The lantern object
needs to change <bold>pProvidesLight</bold> and the methods
dealing with turning the object on or off.
</p>
<p>
For more details on the supported properties and methods,
look around the help files.
</p>
How to make rooms and connect them.
<section><p><bold><big>cRoom - Room objects</big></bold></p></section>
<p>
In MIFL, rooms are just objects (based on cObject) that players
can walk around in. If you want, you could even design backpacks
that players could walk around in; it's all the same. A room
is basically a container that's large enough to hold player
characters. (Although the room class is <bold>not</bold> based
on the cContainer class.)
</p>
<br/><section><p><bold><big>Creating a new room</big></bold></p></section>
<p>
Creating a room is easy:
</p>
<ol>
<li>
Just as described in the "cObject" tutorial, <bold>create
a new object</bold>, but <bold>instead</bold> of deriving
your room off cObject, derive it from <bold>cRoom</bold>.
(cRoom is a subclass of cObject, so by deriving from cRoom
you are also deriving from cObject.)
</li>
<p>
If you want to make a <bold>grid of rooms</bold>, such as 16 x 16 rooms
for a section of wilderness, then name your rooms like,
"oRoomWildernessXXxYY". Replace XX with the X (east/west) location in the
16 x 16 grid, and YY with the Y (north/south) location.
However, offset your numbers so that the room in the center of the
3D model (.m3d file) is at 50 x 50. Thus, the center room would
be oRoomWilderness50x50. The room to the west of it would be
oRoomWilderness49x50.
</p>
<p>
If all of the rooms in the grid are basically
the same, you can save yourself time by creating the room in the
north-west corner first, oRoomWilderness42x42, and then copying the
same object. Everytime you copy a room, the last number, Y, will be
automatically incremented by one, to "oRoomWilderness42x43", then
"oRoomWilderness42x44". One you've created 16 rooms at 42xYY, you'll
need to manually enter a 43 for the X location.
</p>
<li>
Make sure the <bold>Automatically create as an object</bold> button
is checked.
</li>
<li>
Rooms <bold>do not need</bold> to start off contained in
other objects, although it's possible.
</li>
<li>
In the object's <bold>properties</bold> tab, you'll find a
number of properties that were automatically added when
you selected the "cRoom" class. You'll need to fill these in.
</li>
<li>
<bold>pNLPParseName</bold> and <bold>pNLPNounName</bold> will
need to be filled in. See <bold>How command parsing
works</bold> and <bold>Noun cases and noun-verb agreement</bold>.
</li>
<li>
<bold>pAutoMapMap</bold> should be filled in the the
map that the room is in. For now, use oMapDefault for
the value. I'll discuss maps, regions, and zones later.
</li>
<li>
<bold>pLocation</bold> controls
where the center of the room is in the map. This is a list
of [EW, NS, UD], where EW is the location in meters east (positive)
or west (negative). NS is north (positive) or south (negative).
UD is up (positive) or down (negative).
</li>
<p>
You <bold>don't have to set pLocation</bold> if your room is
part of a grid of rooms. The X and Y locations will automatically
be converted into a pLocation, using pAutoMapMap.pMapRoomSeparation
to determine the room spacing. The default spacing is 10 meters.
</p>
<li>
<bold>pDimensions</bold> specifies the size of the room.
It is a list with [EW, NS, UD], where EW is the east/west
size in meters, NS is north/south, and UD is the
height. You might
also want to change some of the other automap settings (see
the help) to control the room's shape or orientation.
</li>
<p>
You <bold>don't have to set pDimensions</bold>. If you don't,
the dimensions will be guessed based on the sizes of the
surrounding rooms.
</p>
<li>
<bold>pVisual</bold> and <bold>pVisualDark</bold> provide
the image that the user sees when he/she enters the room.
You should probably use a ThreeDScene resource with a 360
degree view for a room. If you haven't produced a 3D model
yet, leave pVisual and pVisualDark blank. (<bold>pVisualDark is
optional</bold>; it is used to draw the room when there's no light,
but will be automatically simulated by the lighting models.)
</li>
<p>
Alternatively, you can <bold>leave the room's pVisual blank</bold> and
create a <bold>pVisual for the room's pAutoMapMap</bold>. In
this case, the room's visual will automatically be based on
the map, and the camera location will be set to the
room's <bold>pLocation</bold>. An empty pVisual is particularly
useful for a grid of rooms.
</p>
<p>
You'll ususally use a <bold>ThreeDScene</bold> resource with
a 360-degree camera. Unless you
change <bold>pRoomAutoCameraHeight</bold>, the camera height
that you set in the ThreeDScene resource will be ignored,
and it
will automatically be adjusted based on the terrain or building (block)
that the character is in. This is desirable most of the time,
but may cause some confusion once in awhile when the camera
height suddenly changes.
</p>
<li>
If you want your room to be described the first
time a player enters it, set <bold>pDescribed</bold>.
</li>
</ol>
<br/><section><p><bold><big>Adding exits to rooms</big></bold></p></section>
<p>
Once you have built your room you need to add some exits (and
hence entrances.)
</p>
<ol>
<li>
In the "Properties" tab, add a <bold>pExitXXX</bold> property,
like pExitNorth,
where XXX is "North", "South", "East", etc. There are twelve
different directions to choose from.
</li>
<p>
If your room is part of a <bold>grid of rooms</bold> then exits
to other rooms in the grid will be <bold>automatically generated</bold>.
You will still have to set up exits to non-grid rooms. And, if
you <bold>don't want an automatic exit in a grid room</bold> then
set pExitXXX to NULL.
</p>
<p>
Room exits will automatically be created as hotspots in the
360-degree room image <bold>unless</bold> you
change <bold>pRoomAutoHotSpots</bold>. The size and location
of the hotspots are automatically calculated, although you
can override this by changing <bold>pExitNorthHotSpot</bold> (or
pExitXXXHotSpot), or writing
your own <bold>RoomExitHotSpot()</bold> method.
</p>
<li>
In the property's value, <bold>type in the name of the room</bold> you
wish to connect it to, such as <bold>oRoomCircular</bold>.
</li>
<li>
You will also need to modify oRoomCircular and provide an
exit that connects to your new room.
</li>
</ol>
<p>
That's all. Easy, eh? If you wish to add doors,
read the <bold>cDoor - Door objects</bold> tutorials.
</p>
<br/><section><p><bold><big>Spawning objects</big></bold></p></section>
<p>
This is a bit of an advanced topic, but I'll cover it now since
I'm covering rooms.
</p>
<p>
Online virtual worlds (and some single player games) requires
that monsters and items "spawn" from time to time. This means
that new copies of monsters and items are automatically created
a few minutes after the old ones are killed or taken. This
is necessary for muliplayer games to ensure that there are
still monsters and loot around even after previous players have been through.
</p>
<p>
To have a room spawn monsters (or items):
</p>
<ol>
<li>
Set the <bold>pSpawnClass</bold> property to a string with
the spawned monster's class, such as "cOrc". Or, to spawn
magic mushrooms, use "cMushroomMagic". You can also use
a list, to ensure that several orcs appear in the
room, like ["cOrc", "cOrc", "cOrc"].
</li>
<li>
Set the <bold>pSpawnTime</bold> to the average number
of <bold>minutes</bold> between spawns. If you want
an exact time, or want to control how the time varies,
then use a list with [min time, max time].
If you don't set the value, the default pSpawnTime for
rooms is 10 minutes.
</li>
<p>
See also <bold>pSpawnTimeRange</bold> to limit the times.
</p>
<li>
If you wish the objects to spawn in an area (as opposed
to always appearing in this room), or you wish them
to appear in a container (such as a letter appearing in
a mailbox), then change <bold>pSpawnLocation</bold>. Look
at its documentation for details.
</li>
</ol>
<br/><section><p><bold><big>Other properties and methods</big></bold></p></section>
<p>
Rooms support many other properties and methods, such as properties
for controlling ambient sounds. To find out more, look
in the documentation.
</p>
<ul>
<li>
If you want to play a special cutscene when a player enters the room,
then set <bold>pRoomEnterDisplay</bold> or write
your own <bold>RoomEnterDisplay()</bold> method. This is particularly
useful when a player enters a room and you wan't to narrate a special
entrance, and have the NPC provide a special greeting.
</li>
</ul>
<br/><section><p><bold><big>Maps, regions, and zones</big></bold></p></section>
<p>
A large virtual world can contain tens of thousands of rooms.
Because this many rooms is too conceptually difficult to fit
onto one map, the rooms in a virtual world are grouped together.
First, a collection of rooms is grouped into a "map", such as
a map of a house, a map of a city, or a map of a dungeon.
A collection of nearby maps is a "region". Regions might
include counties, with multiple cities and towns.
A collection of
nearby regions is a "zone". Zones could include entire
countries, containing multiple counties.
Thus, every map is in a region, and
every region in a zone.
</p>
<p>
The IF library includes a default map, region, and zone
object: oMapDefault, oRegionDefault, oZoneDefault. You can
use these objects if you only want one map, one region, or
one zone in your world. However, if you want more you'll
need to create new objects.
</p>
<p>
To make a map object:
</p>
<ol>
<li>
<bold>Create</bold> a new object,
</li>
<li>
Set its super-class to <bold>cMap</bold>.
</li>
<li>
Make sure the <bold>Automatically create as an object</bold> button
is checked.
</li>
<li>
In the object's properties page, fill
in <bold>pNLPNounName</bold> and <bold>pNLPParseName</bold> with
the map's name, such as "New York City".
</li>
<li>
Fill in <bold>pAutoMapRegion</bold> with the region where
the map is, such as oRegionDefault, or whatever regions you
create.
</li>
<li>
Fill in <bold>pLocation</bold> with [EW, NS, UD].
These are <bold>offsets</bold> for
the center of the map, compared to the center of the region.
</li>
<p>
The same properties in a room control the room's location in the
map. Likewise, in a map object, they control the map's location
within the region. You <bold>must</bold> fill these in properly
or the artificial intelligence routines will have difficulty
walking from rooms on one map to another.
</p>
<li>
If your map has waterfalls, or other objects that can be
heard several rooms away, you might wish
to set <bold>pMapAmbient</bold>.
</li>
</ol>
<p>
To create a <bold>region object</bold>, follow basically the
same actions as a map object, but base the object off
the <bold>cRegion</bold> class, and fill in <bold>pAutoMapZone</bold> instead
of pAutoMapRegion.
</p>
<p>
A <bold>zone object</bold> can be created in the same way,
but is based off <bold>cZone</bold> and doesn't need a
pAutoMapZone or pAutoMapRegion.
</p>
Tells you how to make doors and place them in rooms.
<section><p><bold><big>cDoor - Door objects</big></bold></p></section>
<p>
The cDoor class is used to create doors that connect rooms.
You don't need to connect rooms with doors, but if you
want an obstacle (such as a locked door)
between the rooms, you'll need to add a cDoor.
</p>
<p>
If you haven't read the tutorial on <bold>cRoom - Room
objects</bold> then you should do that now.
</p>
<br/><section><p><bold><big>Creating a new door</big></bold></p></section>
<p>
Here's how you create a door:
</p>
<ol>
<li>
Just as described in the "cObject" tutorial, <bold>create
a new object</bold>, but <bold>instead</bold> of deriving
your room off cObject, derive it from <bold>cDoor</bold>.
(cDoor is a subclass of cObject, so by deriving from cRoom
you are also deriving from cObject.)
</li>
<li>
Make sure the <bold>Automatically create as an object</bold> button
is checked.
</li>
<li>
Underneath the checkbox, <bold>select the room</bold> that
the door will be in. (A door is usually "in" two rooms at
once, since it straddles the two. I'll get to this later. For
now just pick one of the rooms.)
</li>
<li>
In the object's <bold>properties</bold> tab, you'll find a
number of properties that were automatically added when
you selected the "cDoor" class. You'll need to fill these in.
</li>
<li>
<bold>pNLPParseName</bold> and <bold>pNLPNounName</bold> will
need to be filled in. See <bold>How command parsing
works</bold> and <bold>Noun cases and noun-verb agreement</bold>.
Generally, pNLPParseName will be something like "[east] door",
and pNLPNounName "east door". You can provide more exciting
names, of course, but most people will expect doors to be called
"door".
</li>
<p>
Note: <bold>You don't have to</bold> fill in pNLPParseName
and pNLPNounName for doors since a name will automatically
be creasted by the door's exit within the room, such
as "northeast door".
</p>
<li>
<bold>pDoorCounterpart</bold> will be filed in later. I'll
explain below.
</li>
<li>
Doors do not usually have a <bold>pVisual</bold> property
because they aren't evey drawn (as a separate object in the
room) since they usually appear in the 3D model of the room.
If you want your door to be drawn as an object separate from
the room you'll need to provide a pVisual and
set <bold>pDontListInRoom</bold> to FALSE.
</li>
<li>
Now, <bold>switch to the room object</bold> that contains
the door.
</li>
<li>
Add a <bold>pExitXXXDoor</bold> property, where XXX is the
wall that the door is on, such as pExitEastDoor. Type
in the door's name, such as oDoorEast.
</li>
</ol>
<p>
You're not done though. You have only created half a door.
Doors provide an unusual dilemma since they're an object that
must appear in two rooms at once, which isn't possible
with MIFL. Therefore, you need to create two door objects,
one for each room its in.
</p>
<p>
You have already created the first door object. Now you need to
create the second:
</p>
<ol>
<li>
<bold>Return</bold> to the door object and
switch to the <bold>Misc</bold>.
</li>
<li>
Press the <bold>Duplicate this object</bold> button.
This does exactly what it says.
</li>
<li>
Modify the door's duplicate, chaning the <bold>room</bold> it
appears in, and the <bold>ID</bold>... you don't need to make
an entirely new number. Just change the rightmost digit.
</li>
<li>
In the "Properties" tab, change the
door's <bold>pNLPNounName</bold> and <bold>pNLPParseName</bold>,
usually changing "east" to "west", etc.
</li>
<li>
In <bold>both</bold> the doors, add
the <bold>pDoorCounterpart</bold> property. The value for
each should be the door's opposite. Setting this property
ensures that when one (half of the) door is opened, so is
the other (half of the) door.
</li>
<li>
Visit the object definition for the <bold>other room</bold>,
and set the <bold>pExitXXXDoor</bold> to the duplicate door.
</li>
</ol>
<p>
That's it. You now have two doors that appear as one.
</p>
<p>
Normally you'd leave the duplication to the last moment, after
you had set and extra door properties, such as the ability
to lock it. By duplicating after all the lock properties have
been added you save yourself some work.
</p>
<br/><section><p><bold><big>Locked doors</big></bold></p></section>
<p>
To create a locked door:
</p>
<ol>
<li>
In the door's property tab, add the <bold>pLock</bold> property.
Set it to <bold>TRUE</bold>. This defaults to the door being
locked... which makes sense from a game point-of-view.
</li>
<li>
Also add the <bold>pLockShape</bold> tab, and type in
a string the identifies the shape of key that will
open the door, such as <bold>"SkeletonKey12"</bold>. Some
keys may fit to several shapes, allowing them to open
several types of locks, just as master keys do. Conversely,
you can have a door accept several key shapes by
setting pLockShape to a list, like <bold>["SkeletonKey12",
"SkeletonKey54"]</bold>.
</li>
<li>
If you want your door to automatically lock itself after
it has been unlocked, set <bold>pLockAutoLock</bold> to
the number of seconds delay before it locks. This feature
is extremely handy for multi-user interactive fiction,
causing the door puzzle to reset. If you use automatic
locking you may also wish to set <bold>pOpenAutoClose</bold> to
have the door automatically close itself... a locked
door does not good if it's left open.
</li>
<li>
You'll need to make the <bold>exact same changes</bold> to
the door's other half, unless you want different behavior
for each side.
</li>
<li>
You'll need to create a key object that
supports <bold>pKeyShape</bold> with <bold>"SkeletonKey12"</bold>.
See the tutorial, <bold>cObject - Generic objects</bold> for
information on general-purpose objects.
</li>
</ol>
<p>
That's it. Now, players (and NPCs) will have to unlock the
door before walking through it.
</p>
<br/><section><p><bold><big>Other properties and methods</big></bold></p></section>
<p>
Doors support some other properties and methods, such
as ways to make the doors see-through. For more information
see the documentation.
</p>
Collects are objects that are grouped together, such as gold, sand, and liquids.
<section><p><bold><big>cCollection - Collection objects</big></bold></p></section>
<p>
The cCollection class lets you create objects that are
groups of objects, such as gold, water, sand, and a deck of
cards. The collection object allows you to combine collections
(such as two piles of gold), split collections (one pile
of gold into two), and it displays a quantity in front of
the object (such as "52 gold pieces" or "5 liters of water").
</p>
<p>
If you haven't read the tutorial on <bold>cObject - Basic
objects</bold> then you should do that now because
cCollection objects are based off cDoor objects.
</p>
<br/><section><p><bold><big>Creating a collection object</big></bold></p></section>
<p>
Here's how you create a collection object:
</p>
<ol>
<li>
Just as described in the "cObject" tutorial, <bold>create
a new object</bold>, but <bold>instead</bold> of deriving
your room off cObject, derive it from <bold>cCollection</bold>.
(cCollection is a subclass of cObject, so by deriving from cCollection
you are also deriving from cObject.)
</li>
<li>
Make sure the <bold>Automatically create as an object</bold> button
is checked. (Note: If you are going to have more than one pile
of gold, you may want to make a cCollectionGold class, and then
derive each individual pile from that. This tutorial assumes
you only have one pile of gold though.)
</li>
<li>
Underneath the checkbox, <bold>select the room</bold> that
the collection will be in.
</li>
<li>
In the object's <bold>properties</bold> tab, you'll find a
number of properties that were automatically added when
you selected the "cCollection" class. You'll need to fill these in.
</li>
<li>
Set the <bold>pVisual</bold> property, of course.
</li>
<li>
<bold>pNLPParseName</bold> and <bold>pNLPNounName</bold> will
need to be filled in. See <bold>How command parsing
works</bold> and <bold>Noun cases and noun-verb agreement</bold>.
Generally, pNLPParseName will be something like "[(pile|piece|pieces) [of]] gold [piece|pieces]",
and pNLPNounName "gold piece". Unless you change pNLPNounQuantity,
a number will always be displayed in front of "gold piece".
</li>
<li>
Fill <bold>pQuantityUnits</bold> in with a character indicating
what type of units are associated with the collection.
Use 'm' if the object is grouped in meters (like a rope),
'2' if it's square meters (like cloth), 'l' if it's in liters
(liquids), 'f' if it's a fractional unit (like modern currency),
or 'i' if it's an integer unit (like pieces of gold).
</li>
<li>
If you have any type of units (besides the integer units) then
set <bold>pQuantityMin</bold> to indicate the smallest size
the object can be divided into. If you don't, some user
will divide 100 liters of water into 100,000 objects, each
with 1 ml.
</li>
<li>
Set <bold>pQuantity</bold> to the number of such units.
In the case of gold, it will be the number of gold coins.
</li>
<li>
Set <bold>pCollectionType</bold> to a string that uniquely
identifies the class of object that this collection holds.
In this case, it would be "goldpieces". If you had
a water object you'd use "water". If you have healing potion
it would be "healingpotion". This string is necessary to make
sure that only collections of the same type can be merged
together.
</li>
</ol>
<p>
That's it. If you play with your new object you'll notice the
following feature:
</p>
<ul>
<li>
The name always displays the quantity in front of it.
</li>
<li>
You can split the object into smaller parts, using
a command like "divide gold into 3 parts".
</li>
<li>
You can combine piles of gold togehter with
a command, "combine all gold", or "combine 3 gold pieces with
5 gold pieces" (assuming you have one pile with 3 gold peices,
and another with 5 gold pieces).
</li>
</ul>
Describes how to make containers, like backpacks and chests.
<section><p><bold><big>cContainer - Container objects</big></bold></p></section>
<p>
The cContainer object lets you easily make containers, such
as bags, backpacks, chests, hat racks, book cases, etc.
</p>
<p>
If you haven't read the tutorial on <bold>cObject - Basic
objects</bold> then you should do that now since
containers are based off basic objects.
</p>
<br/><section><p><bold><big>Creating a container</big></bold></p></section>
<p>
Here's how you create a container:
</p>
<ol>
<li>
Just as described in the "cObject" tutorial, <bold>create
a new object</bold>, but <bold>instead</bold> of deriving
your room off cObject, derive it from <bold>cContainer</bold>.
(cContinaer is a subclass of cObject, so by deriving from cContainer
you are also deriving from cObject.)
</li>
<li>
Make sure the <bold>Automatically create as an object</bold> button
is checked. (Note: If you are going to have more than one container
of the same type, you may want to make a cContainerBackpack class, and then
derive each individual backpack that. This tutorial assumes
you only have one backpack though.)
</li>
<li>
Underneath the checkbox, <bold>select the room or object</bold> that
the container will be in.
</li>
<li>
In the object's <bold>properties</bold> tab, you'll find a
number of properties that were automatically added when
you selected the "cContainer" class. You'll need to fill these in.
</li>
<li>
Set the <bold>pVisual</bold> property, of course.
</li>
<li>
<bold>pNLPParseName</bold> and <bold>pNLPNounName</bold> will
need to be filled in. See <bold>How command parsing
works</bold> and <bold>Noun cases and noun-verb agreement</bold>.
</li>
<li>
Fill <bold>pContainerVolumeMax</bold> with the maximum volume
that the container can hold, in liters.
The <bold>pContainerVolumeScale</bold> should be filled in with
a number from 0 to 1, indicating how much larger the container
gets when you shove more objects in it. For example: A canvas
bag will use 1.0, since the volume of the bag is dependent upon
its contents. A chest would be 0.0 since shoving more stuff
into a chest does <bold>not</bold> increase its volume.
</li>
<li>
Fill <bold>pContainerWeightMax</bold> with the maximum weight
the container can hold; a canvas bag cannot hold as much
weight as a chest. You can add
the <bold>pContainerWeightScale</bold> property to turn
the container into a magic item; the default scale is 1.0,
which means that if the container holds 10 kg of weight, it's
10 kg heavier. However, if the scale is reduced, to 0.5, then
the 10 kg of contents will only add 5 kg to the weight
of the container.
</li>
<li>
Set <bold>pVolumeSelf</bold> and <bold>pWeightSelf</bold> for
the volume and weight of the container, without contents.
</li>
<li>
If you wish your container to be openable,
set <bold>pOpen</bold> to <bold>FALSE</bold>, causing the
container to initially be closed. It can be set
to <bold>TRUE</bold> if you wish it to start out opened.
</li>
</ol>
<p>
That's it. You now have a container that you can put stuff in.
</p>
<br/><section><p><bold><big>Locked containers</big></bold></p></section>
<p>
To create a locked container:
</p>
<ol>
<li>
In the container's property tab, add the <bold>pLock</bold> property.
Set it to <bold>TRUE</bold>. This defaults to the container being
locked... which makes sense from a game point-of-view.
</li>
<li>
Also add the <bold>pLockShape</bold> tab, and type in
a string the identifies the shape of key that will
open the container, such as <bold>"SkeletonKey12"</bold>. Some
keys may fit to several shapes, allowing them to open
several types of locks, just as master keys do. Conversely,
you can have a container accept several key shapes by
setting pLockShape to a list, like <bold>["SkeletonKey12",
"SkeletonKey54"]</bold>.
</li>
<li>
If you want your container to automatically lock itself after
it has been unlocked, set <bold>pLockAutoLock</bold> to
the number of seconds delay before it locks. This feature
is extremely handy for multi-user interactive fiction,
causing a container puzzle to reset. If you use automatic
locking you may also wish to set <bold>pOpenAutoClose</bold> to
have the container automatically close itself... a locked
container does not good if it's left open.
</li>
<li>
You'll need to create a key object that
supports <bold>pKeyShape</bold> with <bold>"SkeletonKey12"</bold>.
See the tutorial, <bold>cObject - Generic objects</bold> for
information on general-purpose objects.
</li>
</ol>
<p>
That's it. Now, players (and NPCs) will have to unlock the
container before accessing its contents.
</p>
<br/><section><p><bold><big>Other properties and methods</big></bold></p></section>
<p>
Containers support some other properties and methods, such
as ways to make the containers
see-through, using <bold>pCanSeeContents</bold>. For more information
see the documentation.
</p>
Discusses how communication between the server and client works.
<section><p><bold><big>Talking with the client</big></bold></p></section>
<p>
The Circumreality server application communicates with the client software
(on the player's computer) over the internet. All communications
are done using a MML string (basically XML).
The Basic IF library supplies several functions useful for
sending messages over the Internet to the client.
</p>
<p>
Most of the functions have two flavors, one that sends the
message immediately, although it will be queued up on the
client. The other returns a MML string; several of these
strings can be appended and then sent all at once to the
client. Functions that return the MML string are generally
prepended with "MML", such as MMLAudioPlay(), which returns
a MML string, while MMLAudioPlay() sends the string immediately.
</p>
<br/><section><p><bold><big>Basic functions</big></bold></p></section>
<p>
The most commonly used communication functions are:
</p>
<ul>
<li>
<bold>ActorLanguageSet()</bold> - Calling this function
sets the current language (through LanguageSet()), to whatever
language the actor has chosen as their preferred language.
Before sending any message to an actor, you should call
ActorLanguageSet() to ensure they will receive the message
in the proper language.
</li>
<li>
<bold>AudioPlay()</bold> - Plays a wave or MIDI (music) file
on the user's computer.
</li>
<li>
<bold>AutoCommand()</bold> - Has the user's computer send
a command back to the server, such as "go north". At first
glance this function may seem pointless, but it is useful
since the message will be sent <bold>only</bold> when the
client's playback queue gets to this point.
</li>
<p>
AutoCommand() is particularly useful for story segments
since a small portion of audio and animation can be queued
up on the user's system. When all that is played, the
AutoCommand() will cause a message to be send back to the
server, which in turn sends a bit more of the story.
</p>
<li>
<bold>ObjectDisplayUpdate()</bold> - Whenever an object
is changed (such as turned on or opened) its visuals may
change. Calling ObjectDisplayUpdate() will update the
object's visuals to one of the players.
</li>
<li>
<bold>ObjectDisplayUpdateAll()</bold> - This is just
like ObjectDisplayUpdate() except that all of the players
in the room will get the visual update.
</li>
<li>
<bold>RoomDisplayUpdate()</bold> - When the contents of
a room has changed, calling this will update the visuals
(room display, contents, inventory) for one of the
players.
</li>
<li>
<bold>RoomDisplayUpdateAll()</bold> - Just like
RoomDisplayUpdate() except all players in the room are
notified.
</li>
<li>
<bold>Silence()</bold> - Calling this will add silence to
the queue, creating a pause between actions.
</li>
<li>
<bold>SpeakNarrator()</bold> - Causes the text-to-speech
to be played on the player's computer. The voice used
will be the narrator's.
</li>
<li>
<bold>SpeakObject()</bold> - Causes the text-to-speech
to be played on the player's computer. The voice used
will be the object's.
</li>
<li>
<bold>StringCleanup()</bold> - This utility function
removes potentially unsafe characters from names (or other strings)
that come from from the user's command.
</li>
</ul>
<p>
Some less commonly used functions are:
</p>
<ul>
<li>
<bold>AmbientLoopVar()</bold> - Some ambient sounds that
include loops have the ability to loop in different ways
depending upon the value of "ambient loop variables". This
function lets the server set the value of the variable.
</li>
<li>
<bold>AmbientUpdate()</bold> - This updates the ambient
sounds played on a player's computer. The ambient sounds
are a set of sounds based on the player's room, and sometimes
even items carried by the player.
</li>
<li>
<bold>ChatDisplayUpdate()</bold> - Update's the actor's
chat window.
</li>
<li>
<bold>ChatVerbUpdate()</bold> - Update's the actor's
chat window.
</li>
<li>
<bold>CommandLineShow()</bold> - Shows or hides the command
line. If the command line is hidden the player must click
on hotspots or menus, and cannot type in commands directly.
</li>
<li>
<bold>Position360()</bold> - Changes the player's facing
for the 360 degree images.
</li>
<li>
<bold>VerbWindowShow()</bold> - Shows or hides the player's
verb window.
</li>
</ul>
<br/><section><p><bold><big>MML-string functions</big></bold></p></section>
<p>
The MML string functions don't actually send a message to
the player's computer. Instead, they return a string with the
message. The message can then be sent at a later date, combined
with other messages, or passed into other MMLxxx() functions.
</p>
<ul>
<li>
<bold>MMLAmbientLoopVar()</bold> - Like AmbientLoopVar()
except it returns the string.
</li>
<li>
<bold>MMLAmbientSounds()</bold> - This function combines
several "<Ambient>" resources into a single "<AmbientSounds>..</AmbientSounds>"
message that can be sent to the client.
</li>
<li>
<bold>MMLAudioPlay()</bold> - Like AudioPlay() except
it returns the string.
</li>
<li>
<bold>MMLAutoCommand()</bold> - Like AutoCommand() except
it returns the string.
</li>
<li>
<bold>MMLCommandLineShow()</bold> - Like CommandLineShow() except
it returns the string.
</li>
<li>
<bold>MMLDelay()</bold> - See the "Queues and delays" section
for a complete description.
</li>
<li>
<bold>MMLDisplayWindow()</bold> - Calling this will create
a new window on the client's computer and display the provided
object within the window. If the window already exists then
it will be updated.
</li>
<li>
<bold>MMLDisplayWindowMain()</bold> - This function combined
MMLObjectDisplay() with MMLDislpayWindowMain() to make
it very easy to update the image shown in the main window.
</li>
<li>
<bold>MMLGroup()</bold> - MMLGroup() is used to wrap up groups
of related object images (See MMLObjectDisplay() or the
method VisualAndMenuGet()). The groups of images are then combined
using MMLIconWindow().
</li>
<li>
<bold>MMLIconWindow()</bold> - Creates a new window on the
player's computer that displays a series of objects. If the
icon window already exists then it will be updated with
the new information. The objects
are arranged into groups. For example: The inventory window
has a group of items immediately held by the player character,
followed by groups for items contained within other items.
</li>
<li>
<bold>MMLObjectDisplay()</bold> - The MMLObjectDisplay()
function creates an "<ObjectDisplay>" MML command, which
is used to name an object, provide a visual for it, and
perhaps a menu. You may also wish to look at the
VisualAndMenuGet() method documentation.
</li>
<p>
The <ObjectDisplay> MML
command can be used in a few different ways: The MML
string can be passed to MMLGroup() or MMLDisplayWindow() to
specify what objects are displayed in an icon or display window.
Alternatively, the <ObjectDisplay> MML can be sent directly
to the client using ConnectionSend(); doing this will cause
any visuals for the object to be updated to whatever is
in the new MML.
</p>
<li>
<bold>MMLObjectDisplayGroup()</bold> - This function
is used to get the <ObjectDisplay> MML for the contents
of an object.
</li>
<li>
<bold>MMLPosition360()</bold> - Like Position360() except
this returns the MML.
</li>
<li>
<bold>MMLQueue()</bold> - See "Queues and delays" for more
information.
</li>
<li>
<bold>MMLQueueSimultaneous()</bold> - See "Queues and delays"
for more information.
</li>
<li>
<bold>MMLSilence()</bold> - Like Silence() except this returns
the MML.
</li>
<li>
<bold>MMLSpeakNarrator()</bold> - Like SpeakNarrator() except
this returns the MML string.
</li>
<li>
<bold>MMLSpeakObject()</bold> - Like SpeakObject() except
this returns the MML string.
</li>
</ul>
<br/><section><p><bold><big>Queues and Delays</big></bold></p></section>
<p>
Whenever a message is sent to the client (using ConnectionSend()),
there is an option for placing the message in the main queue
or acting on it right away. If the message is placed in the main
queue it will ignored until that point of the queue is reached.
</p>
<p>
The queue allows you to have the client play a sound (waiting
until the sound is finished), speak (waiting until the speech
is finished), display an image, speak again (waiting until
the speech is finished), etc.
</p>
<p>
If the message are marked for immediate use, then all of the
events will happen at the sime time... <bold>except</bold> the
speech. <bold>The text-to-speech system can only generate one voice
at a time</bold>, so even if you tell it to speak several sentences
at once, it will end up speaking them one at a time.
</p>
<p>
All of this can easily be done using ConnectionSend() and the
MMLxxx() functions above.
</p>
<p>
However, what if you want a sound to play concurrently with
the second audio (both being played after the image is
displayed)? Or what if you wanted the sound to play half
way through the speech?
</p>
<p>
To do this you would use <bold>MMLDelay()</bold>.
</p>
<p>
The <Delay> MML command causes another queue to be created
which runs at the same time as the original one. Therefore,
if you call add MMLDelay (0, rWaveToPlay) into the queue then
at the point in the queue where the message is processed, it
will create a new queue that playes "rWaveToPlay". When
rWaveToPlay is finished playing the second queue will
automatically shut down.
</p>
<p>
If you pass a time into delay, such as MMLDelay (5, rWaveToPlay)
then the wave won't play until 5 seconds of the next sound (or
speech) has been played. (For example: MMLDelay(5,rWaveToPlay) +
MMLNarratorSpeak(...) will play the sound 5 seconds after the
narrator begins to speak.)
</p>
<p>
Since the amount of time to speak will vary depending upon the
user's speed settings (or language selected), you can have the
delayed wave play a percentage of the way through the next
sound (or speech). Just use a negative number to indicate
the percentage. (For example: MMLDelay(-50,rWAveToPlay) +
MMLNarratorSpeak(...) will start the wave playing half way
through.)
</p>
<p>
But what if you want your secondary queue to play more than
one wave, or perform some other action?
</p>
<p>
That's where <bold>MMLQueue()</bold> comes in. It combines
a series of MML actions into a queue MML. This queue can then
be passed into MMLDelay(). After the delay occurs, the queue
will be processed one at a time.
(For example: MMLDelay(-50, MMLQueue(rWaveToPlay, rWaveOther)) +
MMLNarratorSpeak(...) will play rWavePlay half way through the
speech. When rWavePlay is finished, rWaveOther will be played,
sequentially after rWavePlay, but concurrently with MMLNarratorSpeak()).
</p>
<p>
You can also use MMLQueue() when calling ConnectionSend().
If you have the message queued up in the main queue then nothing
unusual happens. However, if ConnectionSend() is set to immediate
playback, then the queued events (in MMLQueue()) will start playing
in their own queue, but sequentially.
</p>
<p>
The Basic IF Library also includes a
function, <bold>MMLQueueSimultaneous</bold> that combines
MMLDelay(0, xxx) with MMLQueue(). It just makes programming
a bit easier.
</p>
<p>
Using the MMLQueue() and MMLDelay() functions you can create
complex timings on the user's computer. These are particularly
useful for the story segments, which often rely on simple
animations.
</p>
What story segments are and how to use them.
<section><p><bold><big>cStorySegment - Story segment objects</big></bold></p></section>
<p>
Sometimes you will need to show players a "cut scene", which is
basically a short animation (series of images) along with audio.
For example: If the player meets an important NPC, you may wish
to spend a minute having the NPC talk, showing the NPC interacting
with the player, etc. Cut scenes are fairly common in game,
although usually they're full motion video. (Circumreality doesn't allow
for FMV.)
</p>
<p>
You might even need to go one step futher, and have a cut
scene with a series of options at the end, such as how the
player can respond to the NPC's question. These responses
might result in further cut scenes.
Using this technique creates an experience similar to the
traditional "Choose Your Own Adventure" books.
</p>
<p>
Alternatively, you can string one cut scene after another,
creating a linear story.
</p>
<p>
All of these devices can be created using the cStorySegment
class.
</p>
<p>
The cStorySegment class allows you to display short (or long)
animations (as a series of stills) and audio on the player's
computer. When the animations have finished displaying, the
story segment either moves onto the next story segment (creating
a linear narrative) or displays a menu that lets the user
chose what's next.
</p>
<br/><section><p><bold><big>Creating a new story segment</big></bold></p></section>
<p>
Creating a story segment is easy:
</p>
<ol>
<li>
Just as described in the "cObject" tutorial, <bold>create
a new object</bold>, but <bold>instead</bold> of deriving
your room off cObject, derive it from <bold>cStorySegment</bold>.
(cStorySegment is a subclass of cObject, so by deriving from cStorySegment
you are also deriving from cObject.)
</li>
<li>
Make sure the <bold>Automatically create as an object</bold> button
is checked.
</li>
<li>
Story segments <bold>should not be</bold> contained in
other objects.
</li>
<li>
In the object's <bold>properties</bold> tab, you'll find a
number of properties that were automatically added when
you selected the "cStorySegment" class. You'll need to fill these in.
</li>
<li>
<bold>pNLPParseName</bold> and <bold>pNLPNounName</bold> can
be filled in with the segment's name. See <bold>How command parsing
works</bold> and <bold>Noun cases and noun-verb agreement</bold>.
</li>
<li>
<bold>pAmbient</bold> can be filled in with a resource for
the ambient sounds played during the story segment, or
left blank.
</li>
<li>
<bold>The menu</bold> properties need to be filled in; I
will discuss them later..
</li>
<li>
If the story segment's pStorySegmentHideUI is FALSE (which is
the default), then you'll need
to set <bold>pVisual</bold> to the final image of the story segment.
</li>
<li>
Fill <bold>pStorySegmentCutScene</bold> in with the cut scene to
be played for the story segment, along with <bold>pVisual</bold> with
the final visual to be displayed to the user, giving them menu
choices.
</li>
<p>
If you want something more complicated, then you'll
need to modify <bold>StorySegmentGet()</bold>. In
the object's <bold>methods</bold> tab, you'll find an
entry for the <bold>StorySegmentGet</bold> method. You
will need to write this. See below.
</p>
</ol>
<br/><section><p><bold><big>StorySegmentGet</big></bold></p></section>
<p>
Each story segment should provide
a <bold>pStorySegmentCutScene</bold> and <bold>pVisual</bold> property.
However, if these are too limiting, then you
may wish to write your own <bold>StorySegmentGet()</bold> method.
This method is used to control what images are displayed on the
user's computer, along with any sounds that are played.
</p>
<p>
StorySegmentGet() returns a MML string that is passed directly
to ConnectionSend(). What you place in the MML string is up
to you, but (by convention) it should set the visuals for the
main image using MMLDisplayWindowMain(). You may even
wish to update the main display window several times.
</p>
<p>
A typical StorySegmentGet() function will look something like this:
</p>
<p align=center><table width=80%>
<tr><td><font face="courier new">
return<br/>
&tab;MMLDisplayWindowMain (...) +<br/>
&tab;MMLSpeakNarrator (...) +<br/>
&tab;MMLDisplayWindowMain (...) +<br/>
&tab;MMLAudioPlay (...) +<br/>
&tab;MMLDisplayWindowMain (...);
</font></td></tr>
</table></p>
<p>
Each MMLxxx() function is a step in the animation or audio.
The "..." will depend upon the function, of course.
</p>
<p>
For more information about generating MML strings,
see the "Talking with the client" tutorial.
</p>
<p>
If a StorySegmentGet() isn't written, the story segment will
call MMLCutScene() and VisualGet(), and display the
pStorySegmentCutScene and pVisual properties instead.
</p>
<br/><section><p><bold><big>The story segment's menu</big></bold></p></section>
<p>
When the story segment is finished playing, it brings up a
menu, allowing the user to choose an option. There are several
ways of accomplishing this:
</p>
<p>
The easiest is to fill in <bold>pStorySegmentOptions</bold> with
a list of menu strings, such as ["Say hello to Fred", "Ask Fred
about the sword", "Leave"]. You then need to
fill in <bold>pStorySegmentActions</bold> with a list of story
segment or room objects
corresponding to the action taken. Example: [oStoryHelloToFred,
oStoryAskFredSword, oRoomInTheCity]. If the object is a story
segment then the new story segment will be run. If it's a room
the the player's character will be moved into the room.
</p>
<p>
If you do use the the <bold>pStorySegmentOptions</bold> property,
you can also fill <bold>pStorySegmentMenuTimeOut</bold> to the
number of seconds before the menu will time out. (If you leave
the property blank, the menu won't time out.) When the menu
times out, it will automatically chose the first item in the
menu and do that.
</p>
<p>
Alternatively, if you want more control over the menu, you
can create a GeneralMenu resource and
fill <bold>pStorySegmentMenu</bold> with the resource name.
If you use a menu resource, <bold>pStorySegmentOptions</bold> will
be ignored.
</p>
<p>
You may still wish to fill in <bold>pStorySegmentActions</bold> though.
The story segment object automatically handles user commands
in the form of "Option X", where X is a number from 1+.
If the menu item's command is "Option 1", then
the player will be moved to pStorySegmentActions[0]. "Option 2"
will move the player to pStorySegmentActions[1], etc.
</p>
<p>
If your menu returns commands that are not "Option X", such
as "Say hello to fred", then you will need to provide a
NLPCommandParse() and NLPCommandParseQuery() message for
the story segment, which is a lot more work.
</p>
<p>
If you don't wish to display a menu at all, but wish to have
the story segment automatically pass the player to another
story segment or another room then fill
in <bold>pStorySegmentAutoCommand</bold> with the command
text to run when the segment is finished with its in animation.
The easiest way to handle this is to set pStorySegmentAutoCommand
to "Option 1" and have pStorySegmentMenuActions contain
a list with one item, the next room or story segment.
</p>
<p>
And if that weren't enough options, you can even have hot
spots in the images displayed by the story segment's
StorySegmentGet() call. If the user clicks on a hot spot,
the specified command will be send. As with menu resources,
using "Option X" will make programming much easier.
</p>
<br/><section><p><bold><big>Mini-choices</big></bold></p></section>
<p>
Some story segments might include "mini choices" that (usually) reveal
information that helps players make a choice for the story segment,
but don't actually branch off into a new story segment.
</p>
<p>
For example: A story segment might have the player encounter a door,
and then decide to open the door or leave it alone. The player might
wish some more information, and might be provided with
the mini-choices of "Examine the door" or "Look through the keyhole".
</p>
<p>
Selecting a mini-choice will speak some text (or play a cutscene),
like "You look through a keyhole and see an orc waiting to
ambush you."
</p>
<p>
However, mini-choices have a cost. If they didn't, then players
would aways select them. The costs might incur the risk of something
else happening (branching to another story segment for a "wandering
monster" instead of the keyhole description being played), or
negatively affecting a NPC that the player is in conversation with,
or perhaps the player is only allowed to choice "one of the above".
</p>
<p>
To add mini choices:
</p>
<ol>
<li>
Add the choice description
to <bold>pStorySegmentMenuOptions</bold> as usual.
</li>
<li>
Under <bold>pStorySegmentMenuActions</bold>, use a sub-list
for the action instead of a room.
</li>
<p>
For example: In the "Look through the keyhole" option,
the sub-list might be ["speak", "You look through a keyhole and see an orc waiting to
ambush you."].
</p>
<li>
Describe the consequence by
filling in <bold>pStorySegmentMenuConsequences</bold>. If
you <bold>don't</bold> fill the consequence in then the
default is to use 1 action point, which effectively becomes
a "Choose 1 of the following."
</li>
<p>
NOTE: Once a mini-choice has been made, it cannot be made again if
the player re-enters the story segment.
</p>
</ol>
<p>
The default consequences deal in "<bold>action points</bold>".
The first time the player enters the story segment they
are given a limited number of action points; Always less
than the number of choices that require action points!
A limited supply of action points forces the player to
"Choose 1 of the following 2 choices", and turns
the story segment into a small sub-game of its own.
</p>
<p>
You can specify the number of action
points in <bold>pStorySegmentActions</bold>. If you don't,
then players will be given enough action points so
they can only choose half of the available action-point-based mini-choices.
</p>
<br/><section><p><bold><big>Moving a player into a story segments</big></bold></p></section>
<p>
Once the player is in a story segment, it's easy to for
the player to move to other story segments or rooms, but
how does the player get into a story segment in the first place?
</p>
<p>
There are two ways:
</p>
<ol>
<li>
You can provide a story segment as an exit from a room,
filling <bold>pExitXXX</bold> with the story segment
object instead of a room object.
</li>
<li>
Your code can call <bold>ActorMoveParty()</bold> to move the
character into a story segment.
</li>
</ol>
<br/><section><p><bold><big>Other properties and methods</big></bold></p></section>
<p>
Unlike other classes, there really isn't much more to a
story segment. cStorySegment is a fairly simple class. However,
some more properties exist:
</p>
<ul>
<li>
<bold>pStorySegmentEnterAsParty</bold> - This flag defaults to
TRUE, and forces all members of a party to enter (and leave) a story segment
together. This ensures that party members don't just wander around,
but that they don't wander into different paths of the story.
</li>
<li>
<bold>pStorySegmentHideUI</bold> - Setting this flag in a story segment
causes all the UI except for the main window to be hidden. This will prevent
players from typing in commands, viewing inventory, or even
talking to other players in the segment. Since most story segments
are entered as a party and may want to talk with
one another, pStorySegmentHideUI defaults to FALSE.
</li>
<p>
However, if pStorySegmentHideUI is FALSE, then strangers in the story
segment will also be able to see and talk to one another. This
may break the immersion of the game. To solve this problem, put the
story segments into their own "instance" (described later).
</p>
<li>
<bold>pStorySegmentMenuExtraText</bold> - Provides an extra description
to the command. If not specified, some "extra text"
values are <bold>automatically generated for mini-choices</bold>.
</li>
<li>
<bold>pStorySegmentIdentifySelf</bold> - Causes one or more NPCs
to introduce themselves to the PCs.
</li>
<li>
<bold>pStorySegmentKnowledgeAdd</bold> - Automatically calls KnowledgeAdd()
for one or more pieces of knowledge.
</li>
<li>
<bold>pStorySegmentQuestsAdd</bold> - Adds a quest to the PC's list.
</li>
<li>
<bold>pStorySegmentMystery</bold> - Adds a mystery to the player's list
by calling MysteriesAdd().
</li>
</ul>
Information about what storylines are and how to create them.
<section><p><bold><big>cStoryline - Storylines</big></bold></p></section>
<p>
Many MUDs and MMORPGs let players choose a character from a
good race or an evil race. Depending upon the goodness or
evilness, their character begins in a different location
in the virtual world.
</p>
<p>
This is an example of a storyline; the player can choose
the good storyline or the evil storyline. The MMORPG, Dark
Age of Camelot, includes three storylines, one for each
of three realms.
</p>
<p>
An interactive fiction title could use storylines in
different ways. Perhaps new players must first play a specific
race. Once they have completed all the quests for that race, they're
allowed a larger selection of races. This would create
a world with two storylines.
</p>
<p>
The basic IF library comes with a placeholder storyline
object called <bold>oStorylineGeneric</bold>. If you are just
playing around, or only have one storyline, then you can
just use this object.
</p>
<br/><section><p><bold><big>Creating a storyline</big></bold></p></section>
<p>
To create a storyline:
</p>
<ol>
<li>
<bold>Create an object</bold>, and base it off of <bold>cStoryline</bold>.
</li>
<li>
Set the object's <bold>pNLPNounName and pNLPParseName</bold> to the
storyline's name, such as "The good side". This name will
be displayed as once of the storyline choices when a character
is created. (If the world only provides one storyline then the
player won't be given a choice.)
</li>
<li>
Fill in <bold>pExamingeGeneral</bold> with a description about
the storyline, such as "Play an evil character and fight the
treacherous forces of good."
</li>
<li>
<bold>pStorylineRaces</bold> should be filled in with a list
of races that the user can choose from if they select the
storyline. For example, the races in a good storyline might
include humans, elves, dwarves, and halflings. The races in
an evil storyline might be humans, orcs, and trolls.
</li>
<li>
<bold>pStorylineRoomStart</bold> should be filled in with the
room where new characters should first appear. If there
is more than one room, then create a list of rooms.
</li>
<p>
You can also provide a <bold>StorylineRoomStart()</bold> method
and select the room based upon the character's race or gender.
The default behaviour for StorylineRoomStart() is to return
pStorylineRoomStart.
</p>
<li>
If you wish your storyline to be off-limits to players until
they have completed another storyline, or until they have
paid, then write a <bold>StorylineQuery()</bold> method
to check any properties assigned to the user. If the storyline
can always be played then ignore the method.
</li>
<li>
If you wish your storyline to provide the character with
special equipment or skills then provide
a <bold>StorylineSetup()</bold> method, which will be called
after the character has been created.
</li>
<li>
Finally, add the storyline object
to <bold>gStorylines</bold> so the user creation code
knows what storlines are available.
</li>
<p>
gStorylines is accessed through the
function, <bold>StorylinesEnum()</bold>, which begins with
the list in gStorylines but pairs it down based upon
SotyrlineQuery() calls.
</p>
</ol>
An easy way to save objects (such as NPCs) to a database.
<section><p><bold><big>cSaveToDatabase - Automatically saved objects</big></bold></p></section>
<p>
If your interactive fiction title is designed to be offline,
then the entire world setup (including all the objects
in the world) will be saved when the user saves a game; you don't
need to worry about this section.
</p>
<p>
However, if you are using Circumreality to create a MUD or MMORPG, when
a user quits, only their character information is saved.
If the server is shut down (or crashes) then all NPCs will
be deleted and started afresh the next time the server
is rebooted.
</p>
<p>
For a traditional MUD, this is no problem. However, NPCs in Circumreality
can remember quite a lot about players, information that
shouldn't be forgotten.
</p>
<p>
To make an object that automatically saves itself to the database
and then reloads when the IF title is restarted, just
base it off of <bold>cSaveToDatabase</bold>. That's all you need
to do.
</p>
<p>
The cSaveToDatabase class keeps a log of all the objects
belonging to it in gSaveToDatabase. When the IF title is
shut down, all these objects (namely NPCs) are saved to
the gDatabaseSaveTo ("SaveToDatabase") database. All the objects they
hold are likewise saved; this way NPCs will continue to hold
equipment.
</p>
<p>
Just to be paranoid, oDatabase sets up a timer to trickle-save
all the cSaveToDatabase objects. That way, if there is a crash,
most of them will have been saved recently.
</p>
<p>
When the IF title starts up, oDatabase sets a timer to occur
in 0-seconds that loads in all the objects that were saved
in gSaveToDatabse. Thus, all NPCs and their equipment will
be reloaded.
</p>
<p>
The reload process involves some gotchas that you should
be aware of:
</p>
<ul>
<li>
If an object saved by cSaveToDatabase is also <bold>based on
class X, but class X is deleted or renamed</bold>, then the object will
fail to load.
</li>
<li>
If a NPC/object based on cSaveToDatabase holds item Y when
it is saved, when the NPC/object is reloaded, <bold>it will automatically
move item Y into its possession.</bold> This might cause some
confusion if you thought you modified your code to place item Y
someplace else, but it keeps popping up in the NPC's hands.
</li>
<li>
<bold>If a player kills a NPC that's defined in your code, such
as oNPCFrank, then the next time you reboot oNPCFrank will
be recreated.</bold> This happens because when oNPCFrank is killed,
he is deleted from the oDatabaseSaveTo database. The next time
the IF title starts, it doesn't find an entry for oNPCFrank
in the database,
so it starts with a fresh one.
</li>
<li>
If you <bold>delete the "SaveToDatabase.mdb" file,</bold> all the information
saved by cSaveToDatabase will be forgotten and you can start
afresh.
</li>
</ul>
An optimization that automatically suspends timers.
<section><p><bold><big>Automatically suspended timers</big></bold></p></section>
<p>
In order to minimize CPU usage, timers for rooms and NPCs are <bold>automatically
suspended</bold> when players aren't in them.
</p>
<p>
The oDatabase object does the following once every second:
</p>
<ol>
<li>
<bold>Un-suspend all the timers</bold> in one
randomly selected room, calling room.RoomSleep (FALSE).
This will cause one room to wake up, even if there aren't any
players in it, allowing NPCs to act (albiet slowly) even when players
aren't in the room.
</li>
<li>
<bold>Suspend all the timers</bold> in two randomly select rooms
using room.RoomSleep(TRUE).
If players are in the room, timers are <bold>not</bold> suspended.
These suspensions ensure that if a player walks out of a room the room
is eventually suspended.
</li>
</ol>
<p>
Plus, whenever <bold>a player or NPC</bold> walks into a room, the
room ands its adjacent rooms are <bold>un-suspended</bold> so that NPCs
become active.
</p>
<p>
If you want a room whose timers don't get suspended then override
RoomSleep() for the room.
</p>
Created instanced spaces in your world.
<section><p><bold><big>Instances</big></bold></p></section>
<p>
Many multiplayer virtual worlds have "instances" that are private
regions of the world that only the player and his friends can
enter. For example: A player's house might be an instance. Or,
a dungeon that's guaranteed private will have an instance.
</p>
<p>
In MIFL, you can <bold>instance a map</bold> and all the rooms
it contains, along with the contents of the rooms. If you wish
a region or zone to be instanced, then just ensure that all the
maps within the region or zone are individually instanced.
You <bold>cannot</bold> instance individual rooms though,
unless you put them into their own map.
</p>
<p>
Making an instance is easy:
</p>
<ol>
<li>
<bold>Create</bold> an oMapMyInstance object based off of cMap,
just like you would create any map object.
</li>
<li>
Set <bold>pMapInstanced</bold> to TRUE.
</li>
<li>
You may wish an instance to be deleted after it hasn't been used
for a few days. To do so,
set <bold>pMapInstancedExpires</bold>.
</li>
<li>
Normally, if a player leaves an item in an instance, <bold>the
item will be deleted when the instance saves</bold>. This ensures
that players can't use instances to store equipment, instead of
storing equipment in the bank. However, if you wish players
to be able to store equipment in an instance, then
set <bold>pMapInstanceSaveAll</bold> to TRUE.
</li>
</ol>
<br/><section><p><bold><big>Some important points</big></bold></p></section>
<p>
When you mark a map as pMapInstanced, the map's rooms and all the objects
in the room are treated like a "template". When an instance of the
map are created, all the rooms and objects of the template are cloned.
Therefore, <bold>never</bold> allow player characters or non-template NPCs
to move into the template. This shouldn't be difficult since all the methods
and functions test for the instance and prevent movement into it.
</p>
<p>
NPCs that are checked out of the database (those
based on cSaveToDatabase) <bold>can't move into an instance</bold>. However,
NPCs not in the database can move freely between instances. This ensures that
a major NPC doesn't follow a player into an instance, and then the player shuts
down the instance, permenantly trapping the NPC.
</p>
<p>
Unless players form up into a party, they will go their separate ways
when they enter an instance, even if they're following one another. If
they form a party, they enter the leader's instance.
</p>
<p>
NPCs that are intentionally chasing players will be to enter the player's
instance.
</p>
<br/><section><p><bold><big>Behind the scenes</big></bold></p></section>
<p>
Here's how instancing works behind the scenes:
</p>
<ol>
<li>
Whenever a player character (or NPC) moves from room to
room, <bold>InstanceRoomRedirect()</bold> is called to see if the
player has entered a new instance. If the PC or NPC doesn't
switch instances then InstanceRoomRedirect() doesn't make any
changes to the movement.
</li>
<li>
If a PC or NPC <bold>enters a room with a different
instance,</bold> InstanceRoomRedirect() determines
if the instance has been loaded already. If it has, it just
remaps the new room to the instanced room.
</li>
<li>
If the instance hasn't been loaded, InstanceRoomRedirect() <bold>attempts
to load a saved instance.</bold> If that succedes, the saved instance
is used.
</li>
<li>
If a saved instance cannot be found, then
InstanceRoomRedirect() <bold>clones the template rooms and
objects</bold> in the map and uses those.
</li>
<li>
Once every 15 seconds (approximately), <bold>a timer tries to shut down one
of the running instances.</bold> If any players are in the instance,
the shutdown fails right away. If no players are there, the instance
is saved to disk, and all the instance rooms and objects are deleted.
</li>
<li>
The same background timer also <bold>deletes saved instanced that
haven't been used for awhile</bold> (as specificed by pMapInstancedExpires).
</li>
</ol>
Information about fractured reality, a mechanism that lets players change a multiplayer world.
<section><p><bold><big>Fractures</big></bold></p></section>
<p>
One of the "really big" problems of a multiplayer world is that
player's can't change the world in any meaninful way because
any changes would also be seen by other players.
</p>
<p>
For example: If a village is under attack by goblins, and the
player kills all the goblins, then the next time the player enters
the village, it should be a happy place. However, if the player
fails to kill the goblins, the village should remain a burnt-out
ghost-town for the rest of the game. Thus, there are three
versions of the village: During the goblin attack, successfully
defended, and burnt.
</p>
<p>
Once solution to this problem is to create a private instance for the
village. However, the village will only ever be private, and
players have zero chance of encountering other players when they enter.
</p>
<p>
"Fractured reality" provides another solution. Basically, it allows three
versions of the village to co-exist in reality. When the player enters
the village, the player's character is whisked to one of the three
versions of reality depending upon "fracture flags" set in the
character. The same goes for all players, so that it's possible and likely
that players will meet other players in whatever version of the village
they're routed to.
</p>
<br/><section><p><bold><big>Fracturing rooms</big></bold></p></section>
<p>
The first step to creating a fractured reality is to fracture some rooms.
You can either fracture all the rooms in the map that need fracturing,
or you can fracture the entrace/exit rooms to the map and have each
fracture redirect the player character to another map.
</p>
<p>
For each fracture, you need to:
</p>
<ol>
<li>
<bold>Create a standard room</bold> as you normally would. Since the
rooms are variations on the same room, you'll probably make several
copies of the original room, or base the fractured room objects off
the original room.
</li>
<li>
Come up with a lower-case string that will <bold>name</bold> the
fracture, such as "firevillage" or "happyvillage". The default (original)
version (which is being attacked by goblins) won't need a name.
</li>
<p>
Several rooms can share the same fracture string; there's no need
to have a "fireinn" and "happyinn" as separate from the village so
long as it's in the same village.
</p>
<li>
For each room, set <bold>pRoomFracture</bold> to list all the fractured
versions of the room, as well as what fracture string determines what
room the character is sent to.
</li>
<li>
If you have an especially tricky situation, you
may whish to override the <bold>RoomFracture()</bold> method
for the rooms.
</li>
</ol>
<br/><section><p><bold><big>Assigning characters to fractures</big></bold></p></section>
<p>
Once the player has defeated or lost to the goblins,
call <bold>cCharacter.FractureAdd()</bold>, passing in either the
"firevillage" or "happyvillage" string. You'll
need to also <bold>move</bold> the character (and anyone in his party)
to the new fracture. If you don't move the character, the changes won't
occur until after the character moves into another fractured room.
</p>
<p>
If the character is a member of a party, make sure to call
FractureAdd() for the <bold>leader</bold> of the party, since all
party members take their fracture flags from their party leader.
</p>
<p>
You can remove fracture flags by calling <bold>FractureRemove()</bold>.
Calls to <bold>FractureQuery()</bold> will test to see if a flag
has been set.
</p>
<p>
An easy way to test whether an object exists within the player's
fractured world is to call <bold>IsInDifferentFracture()</bold>.
</p>
<br/><section><p><bold><big>Objects and NPCs that only exist in a fracture</big></bold></p></section>
<p>
You may run into cases in a multiplayer game where a NPC needs to disappear in the eyes
of some players, but not others. For example: The player may complete a quest, one consequence
of which is that the NPC disappears.
</p>
<p>
To do this:
</p>
<ol>
<li>
Set oNPCThatDisappears.<bold>pDoesntExistForPlayer</bold> to TRUE. (You can set it
to a lower-case fracture string, but TRUE is usually easier since the fracture string
is automatically generated.)
</li>
<li>
When the quest is completed and the character should disappear,
display any relevent descriptions, and call oNPCThatDisappears.<bold>DoesntExistForPlayerSet</bold>().
</li>
<li>
To see if the NPC doesn't exist for a specific player,
call oNPCThatDisappears.<bold>DoesntExistForPlayer</bold>. Support for DoesntExistForPlayer() is
automatic in the game's code, and is handled in other methods such
as IsInvisible().
</li>
</ol>
<br/><section><p><bold><big>Killable NPCs in a multiplayer world</big></bold></p></section>
<p>
Traditional multiplayer worlds have a problem: Players aren't allowed to kill NPCs, because if they were,
other players would never get to meet the NPCs.
</p>
<p>
CircumReality has a solution for this. Players <bold>can kill NPCs</bold>. The NPCs will be
resurrected <bold>but</bold> after the NPCs are resurrected, they won't be visible
to the players that killed them... thanks to fractures.
</p>
<p>
To make a killable NPC:
</p>
<ol>
<li>
Make sure the NPC is based off of the class, <bold>cSaveToDatabase</bold>. Almost all
NPCs will be based off this anyway, since the class is necessary for them to remember
players.
</li>
<li>
Set the NPC's <bold>pDamageCanBeAttacked</bold> to TRUE, so the NPC can be attacked.
</li>
<li>
Set the NPC's <bold>pDoesntExistForPlayer</bold> to TRUE, so that if can be fractured.
</li>
<li>
<bold>Don't start the NPC with any directly created possessions of its own</bold>. There
should be no oFredsShield and oFredsSword for the character oFred (who can be killed). If
you do this, then only one player will be able to pull the object off the dead NPCs body.
</li>
<li>
Instead, fill in <bold>pLootEquip, pLootCreate, and pLootCreateOnDeath</bold> as
per LootCreate(). Make Fred's sword and shield part of his pLootEquip. That way, every time
Fred is killed, a new sword and shield will be created for him.
</li>
</ol>
<p>
Players will be able to kill the NPCs. Other players will witness the NPC being killed too.
When the NPC dies, its body can be looted. When the body disappear, the NPC is resurrected
in its starting room with new loot. Anything on its body before the NPC dies will disappear.
The player that killed the NPC, along with the player's party, won't be able to see the NPC
after that, thanks to DoesntExistForPlayerSet().
</p>
Exits that let characters move quickly and easily around the world.
<section><p><bold><big>Portals</big></bold></p></section>
<p>
Virtual worlds often include "portals", which are exits that
jump large distances, or allow players to bypass parts of the
world that are so uninteresting they haven't even been designed.
</p>
<p>
For example: A city could be constructed as a number of interesting
districts, such as the shopping mall, city hall, and the park.
The residential neighborhoods might be uninteresting. What players
really want is a way to get around between the three districts, as
well as a way to exit the city.
</p>
<p>
For example: Everquest II has free water transport. At the end of
the dock is a bell. If the player rings the bell, they have a choice
of several destinations where they can be transported to. The
destinations are all far away.
</p>
<p>
In more technical terms, <bold>a portal is an exit that lets a
character quickly travel to any (known) maps in the region</bold>.
</p>
<br/><section><p><bold><big>Making a portal exit</big></bold></p></section>
<p>
To make a portal exit:
</p>
<ol>
<li>
Set <bold>pRoomPortal</bold> in the room where the exit occurs. Use
a number from 0 through 11, indicating a direction numbers, such
as 0 for north, 1 for north-east, etc.
</li>
<li>
Since portal exits are probably entry points to, make sure
to <bold>include the room in the map's pMapEntries</bold>.
See below.
</li>
</ol>
<p>
Players (and NPCs) can use the portal to instantly move to any map
with known entry points.
See below.
</p>
<br/><section><p><bold><big>Entry points in maps</big></bold></p></section>
<p>
To allow players to portal to a map:
</p>
<ol>
<li>
Set the map's <bold>pMapEntries</bold> to all the rooms in the map
that can be entered. The rooms are usually the same as all the portal
rooms in the map, but not necessarily.
</li>
<p>
For example: If you have a cMapShoppingDistrict that's an east/west
street, then you will probably have exit portals at both the east and west
ends of the street. Likewise, pMapEntries will have two rooms,
one at the west end of the street, and one at the east end. Players
that "Travel to the shopping district" will automatically be
sent to the <bold>nearest</bold> room, east or west, depending upon where
they started from.
</p>
<li>
You may not want all maps to be instantly accessible by new players.
Many maps won't be accessible until the player character "learns" about
its existence from NPCs. To make a map that's unaccessible to new
players, include a factoid object in <bold>pMapEntries</bold>. When
the player character hears the factoid from a NPC, it should be
added to the player's knowledge list (KnowledgeAdd()). The next time
the player visits a portal, he will be able to access the new map.
</li>
<p>
For example: The city might also include "Mr. Feld's house". Since
the player character doesn't know of Mr. Feld's existence when the
begin play, "Mr. Feld's house" is never shown in any of the portals.
However, once a NPC gives the player a quest to rob Mr. Feld's house,
the player character's KnowledgeAdd() is called, and the map
can now be accessed through the portal.
</p>
</ol>
<br/><section><p><bold><big>Advanced</big></bold></p></section>
<p>
Some advanced portal features are:
</p>
<ul>
<li>
<bold>MapEntries()</bold> is the method that returns the pMapEntries
values. You might wish to write your own MapEntries() method for
special-case portals.
</li>
<p>
Example: You might want a one-off encounter to happen when a user
does, "Travel to distant city". There might be some bandits on the
road that are encountered once, but never again. To impliment this,
you'd write your own MapEntries() method for oMapBanditEncounter. If
the bandits have already been dealt with, this returns NULL.
If they haven't, it would return [[oRoomBanditEncounter, oMapDistantCity,
1.0]]. This would cause all travel to oMapDistantCity to be automatically
redirected to oRoomBanditEncounter.
</p>
</ul>
How to prevent characters from going through doors.
<section><p><bold><big>Guard NPCs - Blocking access to doors</big></bold></p></section>
<p>
Guards that prevent players from going through a specific door are a common
feature of IF. Basically, the player isn't allowed to pass until they provide
the right identification for the guard, speak a password (such as solving
the Sphinx's riddle), etc.
</p>
<p>
To make a guard object:
</p>
<ol>
<li>
Write a <bold>ActionBlock()</bold> method for the guard object that
traps the "move" action and returns TRUE if the players try to
go through the door before providing proof of identity.
Make sure to test that the guard is conscious (pDamageUnconscious) and
alive (pDamageDeath).
</li>
</ol>
Personal NPCs that are loaded when the player logs on, and unloaded when the player leaves.
<section><p><bold><big>Personal NPCs</big></bold></p></section>
<p>
Circumreality supports "personal NPCs". These are NPCs that are intended to
either hang out with the character (such as pets or henchmen), or
which are unique to each player character (such the the PC's family).
</p>
<p>
On a more technical note, a personal NPC is a NPC that is loaded
from the database only when the player character logs in, and which
is saved to the database when the player character logs out.
There are some exceptions to this, such as when the personal NPC
would have to be loaded into a room within a PC's instance when the
instance hasn't been created yet.
</p>
<p>
To make a personal NPC:
</p>
<ol>
<li>
Using any method, such as "new cRaceElf", <bold>create a NPC</bold>.
</li>
<p>
Note: <bold>You cannot turn a MIFL-defined object into a NPC.</bold> This means
that if you cannot use the MIFL editor to create an object that
has the "Automatically create as an object" checkbox ticked, and then
turn it into a personal NPC.
</p>
<li>
Call <bold>PersonalNPCMake()</bold> to turn the NPC into a personal NPC.
</li>
</ol>
<p>
That's it.
</p>
<br/><section><p><bold><big>Personal NPC details</big></bold></p></section>
<p>
Once the object is a personal NPC, it obeys the following rules:
</p>
<ul>
<li>
When the player logs on, all personal NPCs <bold>not</bold> in instances
will be loaded.
</li>
<li>
When the player enters an instance, all personal NPCs <bold>located
to the instance</bold> will be loaded at that time.
</li>
<p>
This means that a personal NPC in an instance is in stasis until the
instance is entered. If you want to have the NPC to continue to
act even if the PC isn't in the instance, then create a "quest" based
off cQuest. Assigned it to the PC. Either have the quest act as
the personal NPC's "brains" while the pesonal NPC is in stasis,
or (less ideally because its slow), keep the instance
loaded even when the PC isn't there using InstanceLoad().
</p>
<li>
When an <bold>instance is shut down</bold> and a personal NPC is in it,
the personal NPC will be saved. It will reload in the instance the
next time the player enters a copy of the instance.
</li>
<p>
This has some weird side-effects. If PC A joined PC B's party, and followed
PC B into an instance, then the instance would be assigned to PC B.
If PC A had a personal NPC that followed him, the personal NPC would
enter into PC B's instance too. If PC A left their personal NPC
in PC B's copy of the instance, and then logged off, his personal NPC
would disappear from PC B's instance. The next time PC A logs on and
enters a copy of the instance, perhaps PC C's copy of the instance,
the personal NPC will be loaded into PC C's copy, NOT PC B's.
You can ensure that personal NPCs will only be loaded into the
instance of their PC by setting <bold>pPersonalNPCOnlyPCInstances</bold>.
</p>
<li>
When the player character logs off, all his personal NPCs will log off.
</li>
<li>
If the personal NPC dies, and <bold>pPersonalNPCResurrect</bold> is
TRUE, then the NPC will be resurrected in the PC's (not the NPC's)
last save room, specified by pRoomResurrectionLocation.
</li>
<li>
If the personal NPC has <bold>pPersonalNPCPartyAuto</bold> set then
the NPC will automatically be included as a quasi-member of any
party that the PC joins; this is handy for creating pets and henchmen.
(This property is set when PersonalNPCMake() is called.)
</li>
<p>
For this to work, you will still need to include a goal
that <bold>has the personal NPC follow the PC around</bold>,
such as <bold>oAIGoalMoveFollowPersonalPC</bold>.
</p>
</ul>
<br/><section><p><bold><big>Advanced</big></bold></p></section>
<p>
Some other functions, methods, and properties to know about:
</p>
<ul>
<li>
<bold>pPersonalNPCs</bold> is a property in the player character that
is used to store the PC's list of NPCs.
</li>
<li>
Every personal NPC has <bold>pPersonalNPCFor</bold> set to the
PC to whom they belong.
</li>
<li>
<bold>pPersonalNPCPartyLast</bold> is the last time the personal NPC
saw his PC.
</li>
<li>
<bold>gPersonalNPCPartyAutoTime</bold> is the number of seconds that
the NPC is allowed to be away from the PC before it is no longer
automatically included in the PC's party.
</li>
<li>
<bold>PersonalNPCDatabaseCheckOut()</bold> will load a personal
NPC from the database so long as the room into which it should
be loaded exists.
</li>
<li>
<bold>PersonalNPCDelete()</bold> will dete a personal NPC.
</li>
<li>
<bold>PersonalNPCPartyAuto()</bold> is a method to test and see if
a personal NPC is in pPersonalNPCFor's party.
</li>
</ul>
How to create more interesting writing.
<section><p><bold><big>Replacing generic phrases</big></bold></p></section>
<p>
Circumreality uses a lot of <bold>generic phrases</bold>, like "%1 (pick/pick/picks) up %2", from
sPerceiveMoveGet. These phrases get boring after awhile,
particularly when something signficant has happened. For example:
Instead of, "You pick up the jewel.", you might want to
have the computer say, "Eyes filled with green, you eagerly pick up up the jewel."
</p>
<p>
To do this:
</p>
<ol>
<li>
Create a <bold>StringReword()</bold> method for your oJewel object (which is
picked up.)
</li>
<p>
You could alternatively create a StringReword() for the room that the
jewel appears in, or for the object upon which player characters are based.
For this particular example, the jewel object works best.
</p>
<li>
Write code for StringReword() that checks for sPerceiveMoveGet and the
use of oJewel. If these match, it returns the new string. For example:
</li>
<blockquote><font face=courier>
if (IsList(Replace2) && (Replace2[0] == oJewel) && (String == sPerceiveMoveGet))
<br/>&tab;return "Eyes filled with green, %1 eagerly (pick/pick/picks) up %2.";
</font></blockquote>
</ol>
<p/>
<p>
StringReword() is called by <bold>NLPStringFormat2()</bold>, so it
only works in cases where the string if being formatted
by NLPStringFormat2().
</p>
<br/><section><p><bold><big>Other ways</big></bold></p></section>
<p>
Circumreality also provides some other ways to reword generic phrases:
</p>
<ul>
<li>
<bold>StringActorEnterLeave()</bold> can be used to change "X enters from the north" or
"X goes north"
into something more exciting.
</li>
</ul>
Creating custom commands for objects.
<section><p><bold><big>Custom commands for objects</big></bold></p></section>
<p>
There are several ways to create custom commands:
</p>
<ul>
<li>
Modify <bold>oParserVerb</bold> with your own commands. This works best when
creating commands that will be supported by a number of objects (of different
classes). See The "How command parsing works" tutorial for more information
on this.
</li>
<li>
In each object, write your
own <bold>NLPRuleSetTempVerbs()</bold> and <bold>NLPCommandParse()</bold> methods.
This is very flexible, but is a fair amount of work.
</li>
<li>
The <bold>easiest</bold> way is to use the ObjectCommandXXX() methods,
as described below.
</li>
</ul>
<br/><section><p><bold><big>ObjectCommandXXX() methods</big></bold></p></section>
<p>
The easiest way to add a custom command to an object is to
provide the ObjectCommandXXX() methods for the object (or the object's
class).
</p>
<p>
For example: You might want a special command to "Turn over the table",
perhaps because a secret message is written underneath.
</p>
<p>
To do this:
</p>
<ol>
<li>
<bold>Come up with a lower-case string name</bold> for the action,
like "turnover".
</li>
<li>
Since the player must be able to access the bookshelf to turn it
over, <bold>prefix you name with "access",</bold> turning it into "accessturnover".
If the player only needed to see the object, then you'd
use "seeturnover", or "holdturnover" if the player's character needed
to hold the object.
</li>
<li>
Write your own <bold>ObjectCommandEnum()</bold> method for the table.
In this case, CommandList would merely have ["accessturnover",
"Turn over <object>", "flip *1"] appended.
</li>
<p>
"accessturnover" is the identifier for the command. "Turn over <object>" is
the primary command that's parsed, as well as the command that's displayed
in the object's context menu. "flip *1" is an alternate way of parsing
the command; you could leave this out in most cases.
</p>
<li>
Write you own <bold>ObjectCommandIsValid()</bold> method that returns
TRUE if it's passed a CommandID of "accessturnover".
</li>
<li>
Finally, write <bold>ObjectCommandAct()</bold> to actually flip the
table. This code might refresh the room's image with an upside-down table
and have the narrator speak something.
</li>
</ol>
<p/>
<p>
<bold>Design tip:</bold> Traditionally, interactive fiction titles have
huge problems with "guess the verb", where the player must (a) guess that
turning the table over will help solve a puzzle, and (b) guess the wording
for "turning the table over". Players <bold>don't like</bold> guessing
the verb! To be nice to the player, unless guessing the verb is very
important, always make sure to include important actions
in the object's context menu, by writing your own <bold>ContextMenuItemGet()</bold>.
If you are using the ObjectCommandXXX() methods, then
you <bold>don't have to</bold> write your own ContextMenuItemGet() because
the commands will automatically be displayed.
</p>
Some information about sending E-mail that you should know.
<section><p><bold><big>Sending E-mail from an online IF title</big></bold></p></section>
<p>
When Circumreality is used for online titles, it likes to send E-mail to players
to notify them of:
</p>
<ul>
<li>
<bold>Send "activation passwords"</bold> to the E-mail accounts supplied by
new users.
</li>
<li>
Alert players that their <bold>characters have received game-mail.</bold>
</li>
<li>
Warn players when their <bold>user accounts are about to be deleted</bold> because
they haven't been used for awhile.
</li>
</ul>
<p>
For Circumreality to send E-mail, you need to fill in the following global variables:
</p>
<ul>
<li>
<bold>gEmailAdministrator</bold> - The administrator's E-mail account that is
actually read, like "Fred@MSN.com". Occasional notifications may be E-mailed here.
</li>
<li>
<bold>gEmailDomain</bold> - The domain name where your E-mails are
being sent from. For example: If your automatic E-mails are sent
from "AutoMail@mXac.com.au" then the domain name would be "mXac.com.au".
</li>
<li>
<bold>gEmailSMTPServer</bold> - The SMTP server to send E-mails
through. If you don't know what this is, then look in Outlook Express
(or whatever E-mail you use), for your POP and SMTP server settings.
The SMTP name is for outgoing mail messages, and might look like
"mail.bigpond.com".
</li>
<li>
<bold>gEmailMailSendEmail</bold> - This is the E-mail associated with
your automatic E-mails, like "AutoMail@mXac.com.au".
</li>
<p>
Despite the "Do not reply to this mail" warning, <bold>People
will reply</bold> to automatic E-mails. You should occasionally check
your automatic E-mail's in-box and empty the messages.
</p>
<li>
<bold>gEmailAccessPasswordEmail</bold> - Like gEmailMailSendEmail,
except this is used when an access password is mailed.
</li>
<li>
<bold>gEmailServerName</bold> - Change this to the name of your
world, like "Fred's world at www.Fred.com". It will be included in
all automatic E-mails so players know which online IF title is
alerting them.
</li>
<li>
<bold>gEmailAuthUser and gEmailAuthPassword</bold> - Most E-mail
servers require that you provide an authorization user account
and password before they send E-mail. You may need to fill these
out for E-mail to be successfully sent. (You can look at the logs
to see if it was send successfully.)
</li>
<li>
<bold>gUserAccessPassword</bold> - Set this to TRUE to
require users to enter a valid E-mail address to access the online game.
</li>
</ul>
<p>
You might also wish to change these:
</p>
<ul>
<li>
<bold>gUserAccessPasswordValidTime</bold> - How long an access password will
be valid before the player needs to reapply. This defaults to 30 minutes.
</li>
<li>
<bold>sMailSendGameMailName</bold> - This is the friendly E-mail string
displayed instead of gEmailMailSendMail. You might wish to set this
to "MYWORLD automatic E-mailer", or something.
</li>
</ul>
How to add rules of conduct or an end-user license agreement to your online game.
<section><p><bold><big>Rules of conduct and end-user licence agreements</big></bold></p></section>
<p>
It's fairly common for online games (MUDs and MMORPGs) to require that players
agree to a "Rules of conduct" or "End user license agreement (EULA)" before
they are allowed to play.
</p>
<p>
Circumreality automatically requires users to agree to rules of conduct before being
allowed to play. You can change the rules of conduct to suit your needs.
</p>
<ul>
<li>
Change <bold>rLogonMainEULA</bold> to whatever rules of conduct you
wish to require.
</li>
<li>
<bold>Every time</bold> you change rLogonMainEULA, make sure
to change <bold>gEULAVersion</bold> to a new number. When gEULAVersion
is changed, all users (new and old) will be required to agree to
the new rules of conduct.
</li>
<li>
If you <bold>don't</bold> require users to agree to the rules,
then set <bold>gEULAVersion</bold> to NULL.
</li>
</ul>
How to link to another world.
<section><p><bold><big>Linking to other worlds</big></bold></p></section>
<p>
You may wish to provide exits in your world that link to other
worlds. The character information won't be transmitted, but the
player will quickly be forwarded onto the new world.
</p>
<p>
To do this:
</p>
<ol>
<li>
<bold>Create a <TitleInfo></bold> resource for the other world.
When you create the resource, use the option in the resource
editor to load all the information from an existing link
file, such as MyWorld.crk.
</li>
<li>
When the user goes in a specific direction, enters a portal,
or presses a linking book, call <bold>LogOff()</bold> with the
<TitleInfo> resource.
</li>
</ol>
Some tips about where to store your CircumReality files on your hard drive.
<section><p><bold><big>Where to store your CircumReality files</big></bold></p></section>
<p>
When you create a world using CircumReality, you'll probably end up creating
hundreds of different files, including MIFL libraries, 3D scenes, sound
files, etc.
</p>
<p>
My recommendation for how to store them is:
</p>
<ol>
<li>
<bold>Create a directory</bold> on your hard drive, like "c:\WorldFiles".
</li>
<li>
<bold>Add a link to the "Startup"</bold> folder underneath "Programs" in the Start menu.
Have the link call "subst.exe w: c:\WorldFiles". What this will do
is remap the w: drive to c:\WorldFiles. (You'll need to reboot to have this
take effect, since the link is only run on start-up.)
</li>
<p>
This is handy, because if you decide to move your world files to a different
directory, you just change the "subst.exe" link and your
file dependencies <bold>won't</bold> be broken.
</p>
<li>
<bold>Within the w: drive, create sub-directories</bold> for:
</li>
<ul>
<li>
<bold>Audio</bold> - Audio files are saved here.
</li>
<li>
<bold>Scenes</bold> - Save your "3D Outside the Box" scenes here.
</li>
<li>
<bold>Scripts</bold> - This is where you save for MIFL project file and
MIFL libraries.
</li>
<li>
<bold>Textures</bold> - Save texture bitmaps here.
</li>
<li>
<bold>Voices</bold> - Save your text-to-speech voices here.
</li>
</ul>
</ol>
<p/><section><p><bold><big>Text-speech voice locations</big></bold></p></section>
<p>
The default libraries have several resources for the standard English
text-to-speech voices, such as: rVoiceFemale1, rVoiceFemale2, rVoiceFemale3,
rVoiceMale1, ..., rVoiceMale5, rVoiceNarrator, rVoiceMasterFemale1 ...
rVoiceMasterFemale3, rVoiceMasterMale1 ... rVoiceMasterMale5.
</p>
<p>
By default, these point to locations in "c:\program files\mxac\CircumReality".
</p>
<p>
You may wish to override these in the following cases:
</p>
<ul>
<li>
If you only use a few of the standard voices, then override some of
the extra voices to point back at a voice you are planning to
use. Thus, if you're only using the first two female voices,
then modify rVoiceFemale3 to one of the text-to-speech voices
you used in rVoiceFemale1 or rVoiceFemale2. If you don't do
this then when the .crf file is built, it will include the third
female voice, making your .crf file larger than you need.
</li>
<li>
If you want to provide your own custom text-to-speech voices.
</li>
</ul>
A "brief" list of the work required to create a CircumReality map.
<section><p><bold><big>Sample work involved in creating a CircumReality map</big></bold></p></section>
<p>
This is a list describing the work I undertook to create a CircumReality
map. The map, of course, is just a portion of the entire game.
</p>
<ol>
<li>
Figure out roughly <bold>what happens</bold> in the map, why the players are there, etc.
</li>
<li>
Figure out <bold>what NPCs exist</bold>, why they're important to the players,
and how the NPCs interact with one another.
</li>
<li>
Determine <bold>what the map should look like</bold>, what buildings there should be, etc.
</li>
<li>
Using 3D Outside the Box, <bold>create the topography</bold> for the map.
</li>
<li>
Using 3D Outside the Box, <bold>create shells for buildings</bold> and other structures or
objects that affect room layout. These don't need to be detailed for now
since the important part it determining where the rooms go.
</li>
<li>
<bold>Create a cMap object.</bold>
</li>
<li>
(Optional) Create a <bold>grid of room objects using oRoomXXxYY</bold>, so that the room locations and
exits are automatically calculated.
</li>
<li>
For rooms in the grid, fine-tune the location
using <bold>pLocation</bold>, as well as automatic exits,
by setting pExitXXX to NULL.
</li>
<li>
<bold>Create non-automatic rooms</bold>, as per usual.
</li>
<li>
<bold>Create doors.</bold>
</li>
<li>
Revisit the room objects and assign them to special classes,
such as for indoors, near the beach, etc.
</li>
<li>
Make sure to set <bold>room flags</bold> like pProvidesLight and pRoomIsOutside.
</li>
<li>
<bold>Create NPC objects</bold> based on cRaceXXX for each of your NPCs.
Give them a pNameReal, pGender, and not much else.
</li>
<li>
Make sure the NPCs are maked as <bold>cSaveToDatabase</bold> so
they have a memory.
</li>
<li>
You may want certain <bold>merchants</bold>, using cMerchant, or guards
with pIsGuard.
</li>
<li>
<bold>Can the NPCs be attacked?</bold> Fill in pDamageCanBeAttacked.
</li>
<li>
Fill in the <bold>NPC's work and sleep schedules</bold> with
pAIScheduleLocationEat, pAIScheduleLocationLive, pAIScheduleLocationWork,
pAIScheduleTimeEat, pAIScheduleTimeSleep, pAIScheduleTimeWork.
If a NPC's schedule is critical then set pDontSleep.
</li>
<li>
Figure out <bold>what the NPCs do during the day</bold> and
write AIScheduleEnum(), AIScheduleGoal(), AIScheduleLocatio(),
and AIScheduleWhatDoing().
</li>
<li>
You may need to <bold>write some of your own goals</bold> based off
of cAIGoal.
</li>
<li>
Fill in the <bold>NPCs' relationships</bold> with pAIRelationships.
</li>
<li>
<bold>Create any objects</bold> that are important for the game,
as well as providing an examine description, pExamineGeneral.
Don't bother with custom artwork yet, leaving the pVisual blank
for many objects.
</li>
<li>
Likewise, create objects that the <bold>NPCs will carry</bold>.
</li>
<li>
Create <bold>books that expose backstory</bold> to players, cBook.
</li>
<li>
Some rooms will <bold>spawn</bold> hidden (or visible) objects.
Fill in pSpawnTime, pSpawnClass, pSpawnResourceTime, and
pSpawnResourceClass.
</li>
<li>
<bold>Allow NPCs to be given or shown objects</bold> using AIListenForActCmdOffer()
and AIListenForActCmdShow().
</li>
<li>
<bold>Create conversations scripts</bold>, based on cConvScript, for NPCs
to talk to one another as part of AIScheduleGoal(),
or chance meetings, using pAIConvScripts.
</li>
<li>
Some of the <bold>conversation scripts may reveal cKnowledge</bold> objects to
players that listen in, so you'll need to create some knowledge
objects at this point.
</li>
<li>
Create <bold>cKnowledge objects that the player can ask</bold> NPCs, and
then tell to other NPCs. Fill in pAIConvStories.
</li>
<li>
Have the <bold>NPCs respond to specific knowledge</bold> by
writing PerceiveKnowledgeSay().
</li>
<li>
Have the <bold>NPC respond to other player actions</bold> by
writing PerceiveXXX() methods. Some objects may prevent characters
from leaving the room, using ActionBlock().
</li>
<li>
Give <bold>NPCs skills</bold>, modifying pSkills.
</li>
<li>
Fill in <bold>pDescribed for the NPCs, rooms, and objects</bold>.
</li>
<li>
Fill in <bold>pExamineGeneral for NPCs, rooms, and objects</bold>.
</li>
<li>
Fill in <bold>pAIInfoSpeakXXX</bold> for NPCs, such
as pAIInfoSpeakFuturePlansNPC.
</li>
<li>
<bold>Does the NPC like gifts, or can the NPC be bribed?</bold> Fill in
pAIValueObject and pAIOfferCanBribe.
</li>
<li>
<bold>Create any story segments</bold>, based on cStorySegment.
</li>
<li>
<bold>What favors does the NPC grant once the NPC likes the PC?</bold> Changed pAIFavor.
</li>
<li>
<bold>Is player-vs-player combat (PvP) allowed?</bold> Set pRoomCanAttack.
</li>
<li>
Using 3D Outside the Box, and <bold>create models for your objects</bold>.
</li>
<li>
Place these <bold>models in a resource, and assign a pVisual property</bold> to
your objects.
</li>
<li>
Add details to your 3D Outside the Box scene, such as furniture, knicknacks,
and distant mountains (pMapMountains).
</li>
<li>
Using an administator account, use the <bold>"change my appearance"
and "change my voice"</bold> command
to create visuals for each of the characters. Then type in "appearance info"
to see the code for character's appearance voice, which
you can then paste into pVisualModifiers and pVoiceSub.
</li>
<li>
<bold>Record any sounds</bold> you might need, or find a sound library on
the internet.
</li>
<li>
<bold>Create ambient sound resources</bold> and place them in the world.
</li>
<li>
<bold>Create a custom text-to-speech lexicon</bold> using
the mXac NLP program included in 3D Outside the Box. Specify
the pronunciations of mispronounced words, particularly names.
Have your derviced
text-to-speech voices use the custom lexicon.
</li>
</ol>
Goes through the steps necessary to "ship" a Circumreality title and put it online.
<section><p><bold><big>"Shipping" your Circumreality title</big></bold></p></section>
<p>
Once you have completed your Circumreality title, or
if you just want to distribute it to some friends so they
can try it out, you need to do the following:
</p>
<ol>
<li>
Create a <bold>TitleInfo</bold> resource.
</li>
<li>
Generate and distribute your <bold>.crf or .crk file.</bold>
</li>
<li>
(Optional) Put your <bold>server online.</bold>
</li>
</ol>
<br/><section><p><bold><big>Create a TitleInfo resource</big></bold></p></section>
<p>
The "TitleInfo" resource provides information about the
interactive fiction title, such as whether it's run single-player
or as an online-game, the end user license agreement, and
a description of the title.
</p>
<p>
Every interaction fiction <bold>must</bold> have one TitleInfo resource
named <bold>rTitleInfo</bold>. The server library
comes with a default TitleInfo named <bold>rTitleInfo</bold>.
Therefore, if you will need to override it by creating your
own TitleInfo resource called rTitleInfo.
</p>
<p>
To do this:
</p>
<ol>
<li>
Select the <bold>Add new resource</bold> menu item from
the <bold>Misc</bold> menu.
</li>
<li>
In the "Add a resource" page, press the <bold>TitleInfo</bold> button.
</li>
<p>
The title info "Modify resource" page will appear.
</p>
<li>
Change the <bold>name</bold> to <bold>rTitleInfo</bold>.
</li>
<li>
Press the <bold>Add</bold> button to add a resource for
a given language. You can have different TitleInfo resources
for each language, since you might wish to translate the
product description and end-user license agreement.
</li>
<p>
The "Title information" page will appear.
</p>
<li>
Type in the product's <bold>name</bold>, <bold>short
description</bold>, <bold>long description</bold>, and <bold>web site</bold> in the
first tab.
</li>
<li>
If the interactive fiction title can be run offline (without
requiring an Internet connection) then <bold>check</bold> the
item in the third tab allowing for this. Otherwise, uncheck
it.
</li>
<li>
If the interactive fiction title can be run over the Internet,
visit the <bold>third tab.</bold> See below.
</li>
<li>
Make sure to <bold>set the title's file name</bold> in the resource
to the same name that you'll be using for the link file. For
example, if you're distributing a "MyWorld.crk" link file,
then enter "MyWorld" for the filename. This
file name <bold>must be unique</bold> amongst all the worlds.
</li>
</ol>
<p>
For the interactive fiction title to run over the Internet you'll
need to tell the editor what "shards" you'll have running.
A shard is copy of the world. If you don't have many users you'll
only have one shard. However, if you have thousands of users,
one computer may not be fast enough to handle all those users,
so you'll need run several servers, each one with a different
shard. (You could even run several shards on one computer.)
You can also use shards to provide subtly different experiences,
with some shards encouraging player vs. player activities,
while others encourage role playing. (See below.)
</p>
<p>
To add a shard (in the "Internet" tab):
</p>
<ol>
<li>
Press the <bold>Add a new shard</bold> to add a new one.
</li>
<li>
Type in a <bold>name</bold> and <bold>description</bold> for
the shard.
</li>
<li>
Type in the domain name of the <bold>server</bold>, such
as <bold>MyIFServer.com</bold>.
</li>
<p>
If your server doesn't
have a domain name, but does have a fixed IP address
then type in the IP address, such as <bold>12.34.567.89</bold>.
</p>
<p>
If you don't know either the name or fixed IP address,
the enter a website URL, <bold>http:://www.MyIFWebsite.com</bold> that
can be updated with the IP address; with this last option,
users will be asked for the IP address every time they run
your IF title. (By the way, when you run your server, the
main window will display the computer's IP address in
the title.)
</p>
<p>
If you have no clue about what I'm talking about then you'll
need to find a server provider to host your IF title. They'll
tell you all about domain names and IP addresses.
</p>
<li>
Type in the <bold>port</bold> your shard will be listening
to. If you don't know what this means then enter a number
between 4000 and 5000. (The only reason why a port would matter
is if another Internet application were using the same port
(unlikely) or another IF title or MUD were using the same
port (possible).)
</li>
<li>
Type in a <bold>parameter</bold> so that your code can
identify what shard it's running on. See below.
</li>
</ol>
<p>
Each shard allows a "parameter" to be typed in. When the
shard is running, the software can access this parameter
by calling the <bold>ShardParam()</bold> call.
You can use the parameter to cause different shards to act
differently.
</p>
<p>
For example: You might want one of your
shards to allow player-vs-player combat. If you set your
shard parameter to "PvP" and then test (ShardParam() == "PvP")
to see if player-vs-player is allowed.
</p>
<p>
If the IF title is being run stand-alone, without any Internet
connection, then the ShardParam() call will return "offline"
</p>
<p>
If you wish to test your application with differnt ShardParam()
values, you can set the shard to test with on the first page
of the editor. When you first run CircumrealityWorldSim.exe (this application)
to edit your Circumreality project (.mfp) there's an option at the bottom
page for which "Shard number" you wish to use. Type in a
number (1 and up) for the shard number; ShardParam() will return
the value of that shard number. If you wish to test the offline
behavior then enter "0" into the shard number.
</p>
<br/><section><p><bold><big>Generate and distribute your .crf or .crk file</big></bold></p></section>
<p>
Once you have entered your rTitleInfo resource:
</p>
<ol>
<li>
Select the <bold>Compile</bold> menu item from
the <bold>Misc.</bold> menu.
</li>
<li>
Select the <bold>Test compiled code</bold> menu item from
the <bold>Misc.</bold> menu.
</li>
</ol>
<p>
Running these menu options causes the editor to generate
a .crf and .crk file for you. The files are located in the
same directory as your main project file (.mfp). They have
the same name as your project file, but have different
extensions (with .crf or .crk on the end instead of .mfp).
</p>
<p>
If you wish your players to run your interactive fiction
offline you'll need to distribute the <bold>.crf</bold> file
on your web site. This is a <bold>large</bold>, and can
easily be hundreds of megabytes for a finished title.
A very small title will be more like 30 MB.
</p>
<p>
All your offline players need to do is install and
run the Circumreality client. When they're asked what interactive fiction
title to run they select the <bold>.crf</bold> file
your have provided.
</p>
<br/><section><p><bold><big>Offline vs. online</big></bold></p></section>
<p>
The standard IF library works slightly differently between online
(over the Internet) and offline (on the local PC only) usage.
Throughout the library it checks for ShardParamIsOffline(); you
can search through the code for it. The offline parameter
causes the IF to act like an adventure game, while the online
is more like a MUD.
</p>
<p>
Here are some differences:
</p>
<ol>
<li>
The offline game only allows one player at a time. Online
supports a thousand or more players.
</li>
<li>
When the player logs off, the offline version will automatically
shutdown the server. If online version will keep the server
running until another user logs on.
</li>
<li>
The offline code saves all the objects into a saved game
file using SavedGameXXX() function, and the master SaveGame()
function. The online code only saves the player characters
and the objects held by the player charaters; these are saved
in the database.
</li>
<li>
The offline code supports the "Save" command. Online does not.
</li>
</ol>
<p>
Tip: If you want to run your IF server offline (without requiring
the Internet) but you wish it to act is though it were on
the Internet, then create a shard that connects to IP address
"127.0.0.1"... which is basically an IP address only available
within your computer.
</p>
<br/><section><p><bold><big>Put your sever online</big></bold></p></section>
<p>
If you wish your players to access your IF title over
the Internet (enabling multiple players to play in one world,
and providing extra protection against piracy), then
distribute the <bold>.crk</bold> file to them. This
is a small file (only a few KB). The only information it
stores is what you entered into the TitleInfo resource.
</p>
<p>
For your players to use it, they must download
the <bold>.crk</bold> file, and download and install
the Circumreality client. When they run the Circumreality client they need
to select the .crk file you distributed. If you have more
than one shard,they will be asked which shard to use,
and then the Circumreality client will connect to your server over
the Internet. It uses the domain name or IP address you
entered into the TitleInfo resource.
</p>
<p>
On your side, you must have a server (fancy term for a computer)
running and connected to the Internet. The computer's Internet
address must be the one provided in the TitleInfo resource,
which was also stored in the <bold>.crk</bold> file.
</p>
<p>
You then need to run the CircumrealityWorldSim.exe and have it run your
IF title. The best way to do this is to provide a command
in the "Run..." dialog (from the Windows Start menu) with
the following <bold>"c:\program files\mXac\Circumreality\CircumrealityWorldSim.exe"
-1 c:\MyIFFiles\MyIFTitle.CRF</bold>.
</p>
<p>
You'll need to replace <bold>c:\program
files\mXac\Circumreality\CircumrealityWorldSim.exe</bold> with whatever the full path
for the CircumrealityWorldSim.exe file is. The <bold>quotes</bold> around the
server's path are very important since they prevent the
operating system being confused by the space in "program files".
</p>
<p>
<bold>c:\MyIFFiles\MyIFTitle.CRF</bold> should be replaced
with the location of the .CRF file for your IF title.
</p>
<p>
The <bold>-1</bold> tells the server to run using the
parameter for Shard #1. <bold>-2</bold> will use Shard #2's
parameters, etc.
</p>
<p>
You will probably want your server to run the command line
as soon as it starts up, so that even if the server reboots
your IF title will be running.
</p>
<p>
That's it.
</p>
<br/><section><p><bold><big>Quick test with friends</big></bold></p></section>
<p>
If you just want to do a quick test with some friends,
and don't have a dedicated server for your IF title:
</p>
<ol>
<li>
In the <bold>TitleInfo</bold> resource, create a shard
whose address is a bogus web site, such
as <bold>http://www.NotARealWebSite.com</bold>.
</li>
<li>
Go through all the compiling and distribute
the <bold>.crk</bold> file to your friends.
</li>
<li>
When you wish to test your IF, <bold>connect
to the Internet</bold> and <bold>run CircumrealityWorldSim.exe</bold> from
the command line, as from above. Make sure the shard number
in the <bold>-1</bold> points to the shard with the
bogus web site.
</li>
<li>
When the Circumreality server window appears, it will display the
server's IP address in the title. It will be a number
separated by periods, like <bold>12.34.567.89</bold>.
</li>
<p>
If the listed IP address is <bold>127.0.0.1</bold> or <bold>0.0.0.0</bold> then
the server either couldn't connect to the Internet or (for whatever
reason) wasn't able to get the computer's IP address.
Retry and hope it works, or you're out of luck. (Although there
are ways to get the computer's IP address from the network control
panel and other applications. You can
also try <a href="http://www.WhatIsMyIP.com">http://www.WhatIsMyIP.com</a>)
</p>
<li>
Call your friends and tell them to run the client on their
computer. When they select your special shard (with the
bogus web site) they'll be asked for an IP address. Have
them type in the IP address from the server's title.
</li>
<p>
They should connect to your server.
</p>
</ol>
<p>
NOTE: If you turn off your computer (or log off the Internet),
your IP address will probably be different the next time you
log on.
</p>
The effort needed to get your world hosted third party.
<section><p><bold><big>Getting your world hosted by a third party</big></bold></p></section>
<p>
The chances are that you won't want to leave your computer up
and running all the time, nor will your ISP like you using several
hundred gigabytes of bandwidth a month.
</p>
<br/><section><p><bold><big>Finding a host</big></bold></p></section>
<p>
To find a host, you should google for "virtual private server" (or "VPS"),
"hosting", "private server", "dedicated hosting", etc.
</p>
<p>
For my part, I chose www.Prohosters.com. As of writing this, I've only
used them for a few days, so I can't really give an opinion other
than their product support personel have been very helpful so far.
I decided to use them because they were mid-priced; I'm always
wary of the cheapest products.
</p>
<p>
From Prohosters (and others), you have two choices:
</p>
<ul>
<li>
<bold>Dedicated server</bold> - A computer is set aside specially
for you. Only your software is running on it. This is the ideal
solution, but it's also expensive, $200+ per month (as of this
writing). If you were expecting hundreds of simultanous players,
this would be a requirement.
</li>
<li>
<bold>Virtual private server (VPS)</bold> - In a VPS, you share one
computer with many other users, sometimes as many as 60.
The up-side side this that VPS's
are affordable for amateurs, starting at around $50 per month.
Unfortunately, you have less memory and CPU since the computer's
resources are shared amongst all the users. This isn't an issue
for amateur worlds since you'll be lucky to have 50 players online
at once.
</li>
<p>
However, one annoyance with VPS servers is that with 60 users,
there's a <bold>much</bold> greater chance of a server crash.
This means that your world won't have 99.9% up-time. Again,
amateur worlds can get away with downtime, but professional
ones cannot.
</p>
</ul>
<p>
I chose prohoster's $70/month package to start out with. It's
a Windows 2003 server running Plesk. It provides me with 256 megabytes
of RAM (up to 1 gigabyte burst RAM), 10 gigabytes of
storage, 500 gigabytes of bandwidth. Under this plan, one server is
divided amongst 30 users.
My guestimate is that such a package would support 25-50 simultaneous
players, which is plenty for an amateur world, or for my
initial test world.
</p>
<p>
Here's a general idea of what your hosting service needs to
offer, for an amateur world:
</p>
<ul>
<li>
<bold>Windows</bold> 2003, XP, or Vista.
</li>
<li>
<bold>256-512 megabytes RAM</bold> assigned to you. 5-10 gigabytes of hard drive.
About <bold>one-tenth to one-fifth of a CPU-core</bold>. The Prohoster machine seems to have
four processors, divided by 30 users, is about 13% of a CPU core
per user.
</li>
<li>
<bold>SMTP (and E-mail) server</bold> since CircumReality will send E-mail to
players when the receive in-game mail. You don't need to have the same
service providing the E-mail though. This seems to be a standard option.
</li>
<li>
Access to the server via Windows' <bold>Remote desktop connection</bold>. This
seems to be a standard option.
</li>
<li>
The ability to have the CircumRealityWorldSim.exe program <bold>automatically
restart if the server reboots</bold>; this is particularly important for VPS (cheaper)
servers since they seem to reboot every few days. Windows comes with this
functionality built in, and it should be standard for hosting services.
</li>
<li>
<bold>A domain name</bold>, like www.MyWorld.com. You don't really need this
since CircumReality will also work with just an IP address. Domain names
are standard.
</li>
</ul>
<br/><section><p><bold><big>E-mail</big></bold></p></section>
<p>
CircumReality sends E-mail to players to notify them when they get in-game
mail, when their user accounts are about to be automatically
deleted, and when players forget their password.
</p>
<p>
For this to work you must:
</p>
<ol>
<li>
<bold>Set up an E-mail server</bold>. Hosting services assume that people
will automatically want their own E-mail servers when they subscribe,
so E-mail is a given.
</li>
<p>
Unfortunately, setting up an E-mail server isn't necessarily as easy
as it should be. ProHosters uses Plesk. In Plesk, I can to create assing
my IP address(es) to the IP pool, and then create a "domain" that was
associated with one or more of the IP addresses in the pool. Then I had
to create an E-mail account, which didn't work right away... but
product support got it working eventually.
</p>
<p>
Once you set up your E-mail server, you need to find out the address/name
of the SMTP server, and your SMTP-authentication name and password.
(SMTP is "Simple mail transfer protocol" for sending mail, not receiving it;
POP is for receiving, but CircumReality doesn't recieve E-mail, only sends
it.)
This information is the same stuff you type into Outlook Express (or
whatever E-mail program you're using) under the "Accounts" option.
</p>
<li>
Once you get all the right addresses for your SMTP server, <bold>you
need to set the following globals:</bold> gBugEmailAddress, gBugMailToCharacter,
gEmailServerName, gEmailAccessPasswordEmail,
gEmailMailSendEmail, gEmailSMTPServer,
gEmailDomain, gEmailServerName, gEmailAuthUser, and gEmailAuthPassword.
</li>
<p>
This might be a good point to set gEULACompanyName with your company's name,
</p>
<p>
You also need to set the string, sMailSendGameMailName.
</p>
</ol>
<br/><section><p><bold><big>TitleInfo resource</big></bold></p></section>
<p>
You need to create a <bold>TitleInfo</bold> resource to override the default one,
rTitleInfo. (It must use the same name too.)
The resource contains a lot of information about your world, such as
its name, a description of it, and a background picture.
</p>
<p>
As far as this tutorial is concerned, what's really important is that you
need to create a "shard" entry that lists the domain for your server.
</p>
<ol>
<li>
In the TitleInfo resource editor, <bold>click on the "Internet" tab.</bold>
</li>
<li>
If a shard isn't already listed, then press <bold>Add a new shard</bold>.
</li>
<li>
Type in a <bold>name</bold> for the shard. If your game only has one shard then
this doesn't really matter because players won't see it.
</li>
<li>
Type in a <bold>short description</bold> for your shared. Again, this is
only important if you are going to run more than one shard.
</li>
<li>
Type in the <bold>server</bold>. This <bold>is important</bold>, since without
it CircumReality won't be able to locate your server. This the the domain
name that's associated with your server, such as www.MyGame.com. Of course, you'll
have to pay someone to reserver this name, probably the same
company that provides your server.
</li>
<li>
Type in the TCP/IP <bold>port</bold>. If you don't know what this is, or don't
care,then leave it at its default, 4000.
</li>
<li>
Type in a <bold>parameter</bold> that will be accessible through ShardParam(). This
is only necessary if you are running more than one shard.
</li>
</ol>
<br/><section><p><bold><big>Build your .crf (and .crk) files</big></bold></p></section>
<p>
You must build a .crf and ,crk file:
</p>
<ol>
<li>
In the "Misc" menu, select <bold>"Compile"</bold>.
</li>
<li>
In the "Misc" menu, select <bold>"Test compiled code"</bold>.
</li>
<li>
A "CircumReality World Simulator" window will appear. It's
not important now. <bold>Close it</bold>.
</li>
</ol>
<p>
Look in the directory where you store your project file, such as
"w:\Scripts\MyProject.mfp". In that same directory, you'll find
a newly-created "w:\Scripts\MyProject.crf", and
"w:\Scripts\MyProject.crk". <bold>Copy these</bold> someplace safe.
</p>
<p>
Then, <bold>upload</bold> both the .crf and .crk files to a FTP
server that your hosted world-server can access. The easiest option
is to upload them both to your world-server's FTP site. (How you
get FTP working on your server is another issue.)
</p>
<p>
The .crf file contains all the data and script necessary to
run your world, as is probably 100 megabytes or more. The .crk
file should be small, around 50 KBytes, and is the file you hand
out to players who want to enter your world.
</p>
<br/><section><p><bold><big>Starting up your world</big></bold></p></section>
<p>
To start your world (on your hosted server), you need to do the following:
</p>
<ol>
<li>
<bold>Run "Remote desktop connection"</bold>. It's hidden under
your Start menu, in Programs, Accessories, Communication.
</li>
<li>
You'll need to type in your server's <bold>domain</bold> as
well as the <bold>account and password</bold> to long in is.
Your domain will be www.MyGame.com, or whatever domain you've
chosen. The account will probably be "Administrator", but not
necessarily. The password will have been chosen by you
at some point. ProHosters uses Virtuozzo, which lets you
set the log-in password.
</li>
<p>
When you do successfully log in, your desktop will be replaced
by the desktop on your remote server. At the top of the screen
is a small toolbar listing the server name and providing
buttons for minimize and close.
</p>
<li>
On your remote server, <bold>download and install CircumReality</bold> from
www.CircumReality.com.
</li>
<li>
On your remote server, either <bold>download MyProject.crf</bold> or
find where FTP stored it on the sever by searching for MyProject.crf.
</li>
<li>
<bold>Copy MyProject.crf</bold> to c:\, or some directory of your
choosing.
</li>
<li>
<bold>Open the "c:\program files\mXac\CircumReality" directory</bold>,
or wherever CircumReality was installed.
</li>
<li>
<bold>Run CircumRealityWorldSim.Exe</bold>.
</li>
<li>
Press <bold>Dialog</bold>.
</li>
<li>
Open up <bold>c:\MyProject.crf</bold> or whatever you used.
</li>
<li>
Press the <bold>Open (and run)</bold> button.
</li>
<p>
The "CircumReality World Simulator" window will appear. Your world is live!
</p>
</ol>
<br/><section><p><bold><big>Some sanity checks</big></bold></p></section>
<p>
To make sure your world is working:
</p>
<ol>
<li>
On your local computer (not your server), <bold>run CircumReality</bold>.
</li>
<li>
If you have a <bold>password file</bold> then use that, otherwise create one.
</li>
<li>
Press <bold>Try playing in a new world</bold>.
</li>
<li>
In the world-selection page, find "w:\Scripts\MyProject.crk"
(<bold>not</bold> the .crf file) and <bold>open it</bold>.
</li>
<p>
Within a few seconds, <bold>you should be connected to your world</bold>.
</p>
<li>
You can verify this by switching to the <bold>remote desktop</bold> and
check that the <bold>"CircumReality World Simulator" shows a connection</bold>.
</li>
</ol>
<p>
If you can't connect, then try the following:
</p>
<ol>
<li>
Make sure you have <bold>the right domain name</bold> in rTitleInfo.
It should be www.MyGame.com, or whatever you told the server company.
</li>
<li>
From the command line, type <bold>"ping www.MyGame.com"</bold>. It should
say that it worked; if it didn't then you need to talk to your server
provider.
</li>
<li>
The ping command will display an IP address for www.MyGame.com. This
should <bold>be the same</bold> as the IP address shown by the
"CircumReality World Simulator" running on the server (accessible through
the remote desktop). If it isn't then talk to your server provider.
</li>
</ol>
<p>
Another thing to check is that the CircumReality World Simulator can send out E-mails:
</p>
<ol>
<li>
<bold>Run CircumReality</bold> from your local computer. You should already
have an entry in your password file for "My project".
</li>
<li>
Before clicking on the "Play My Project" button, <bold>hold down the control
key</bold>. Then click on the button, then release the control key.
</li>
<p>
This will log you on, but won't immediately go to play. Instead you'll be presented
with a few options, one of which is "Change personal information".
</p>
<li>
Press <bold>Change personal informatiion</bold>
</li>
<li>
Type in your <bold>E-mail address</bold> if it isn't already typed in.
</li>
<li>
Press <bold>E-mail password</bold>.
</li>
<p>
Check your E-mail account a minute later. You should have received an E-mail with
your password.
</p>
</ol>
<p>
<bold>If you didn't</bold>, then something isn't set up properly with your
E-mail server. Check the following:
</p>
<ol>
<li>
Set up Outlook Express (or your E-mail program) to use the POP and SMTP settings
for your E-mail server. Try <bold>sending an E-mail</bold> from your server's E-mail
account to your main E-mail account. If this doesn't work then see
your server company's support staff for help.
</li>
<li>
<bold>Make sure</bold> that gEmailAuthUser, gEmailAuthPassword, etc. are
entered properly.
</li>
<li>
Assuming that you saved your .crf file as "c:\MyProject.crf" on the
remote server, <bold>switch to</bold> "c:\MyProject" on the remove server.
</li>
<p>
In the directory you'll find one or more files that look like,
"log2007032621.txt". These are log text files. You'd normally access them
from an administrator account in CircumReality, but since since you already
have the remote desktop opened, this is just as easy.
</p>
<li>
Find <bold>the most recent logXXX.txt file and double-click it</bold> to
open it with notepad.
</li>
<p>
Scroll down the the bottom of the text file, and look for text indicating that
E-mail was set. For example: Search for "SMTP" or "Domain" in the text.
Read then next 10-20 lines and look for some indication about what type of
error occurred.
</p>
</ol>
<br/><section><p><bold><big>Create the main administrator account</big></bold></p></section>
<p>
By default, <bold>the first person</bold> to log into the world and create the user-name,
"Administrator", as well as the character name, "Administrator", gets special priviledges.
(You can change these defaults.)
</p>
<p>
Therefore, you <bold>must</bold> create an "Administrator" user account, and an
"Administrator" character within that account:
</p>
<ol>
<li>
<bold>Run CircumReality, and then select and type the password for your password file.</bold>
</li>
<li>
Press <bold>I have an existing user acount in a world</bold>.
</li>
<li>
In the world selection page, <bold>select w:\Scripts\MyProject.crk</bold> (not the .crf file).
</li>
<p>
The "Account view/edit" page will appear.
</p>
<li>
Under <bold>Name</bold>, type "Administrator account", or something that's easy to identify.
</li>
<li>
Under <bold>User name</bold> type in "Administrator".
</li>
<li>
Under <bold>Password</bold> type in a password. Make sure not to lose it.
</li>
<li>
Press <bold>Back</bold>.
</li>
<li>
<bold>Press "Play Administrator account"</bold>.
</li>
<p>
You will log into your server.
</p>
<li>
When asked for your <bold>character's name</bold>, type in "Administrator".
</li>
<p>
That's it.
</p>
</ol>
<br/><section><p><bold><big>Handling re-boots</big></bold></p></section>
<p>
The server is guaranteed to reboot at some point. If you are using a virtual
private server, it'll probably reboot every few days. To handle this circumstance:
</p>
<ol>
<li>
On the server (using the remote desktop), <bold>Find a select the
"Schedule Tasks"</bold> option. In Windows, you'll normally find it
under the Start menu, Programs, Accessories, System Tools. In the ProHosters
server I was given, it was underneath "Control panel".
</li>
<p>
When the "Schedule Tasks" list appear, it will show a list of
scheduled tasks, along with an option for "Add scheduled task".
</p>
<li>
Double-click <bold>Add scheduled task</bold>.
</li>
<li>
The "Scheduled task" wizard will appear. Press <bold>next</bold> to
get to the application-selection pane.
</li>
<li>
You'll be shown a list of applications. Unfortunately, the right one
isn't on them. Instead, click <bold>Browse</bold>.
</li>
<li>
Find the "c:\program files\mXac\CircumReality" directory (or
wherever you installed) and <bold>select CircumRealityWorldSim.exe</bold>
by double-clicking it.
</li>
<li>
The wizard will ask you when to perform the task. <bold>Clik on
"When my computer starts" and press "Next"</bold>.
</li>
<li>
You'll then be asked for your <bold>user name and password.</bold> This
is the user name and password that you used for the "Remote desktop
connection" program. Press <bold>Next</bold>.
</li>
<li>
In the last pane, you'll be given an option for "Open advanced properties
for this task when I click Finish". <bold>Check this</bold>.
</li>
<li>
<bold>Press "Finish"</bold>.
</li>
<p>
A new window wil appear with three tabs, "Task", "Schedule",
and "Settings".
</p>
<li>
In the "Task" tab, <bold>append " -1 c:\myProject.crf"</bold> to
the "Run" line. After doing this, the line will probably
be, "<italic>"C:\Program Files\mXac\Circumreality\CircumrealityWorldSim.exe"
-1 c:\myProject.crf</italic>"
</li>
<li>
In the "Settings" tab, make sure "Stop the task if it runs for:"
is <bold>unchecked</bold>
</li>
<li>
Press <bold>OK</bold>.
</li>
</ol>
<p>
You won't find out if this works until the server reboots and players start
E-mailing you that the server is down. Some potential ways
this can fail are:
</p>
<ul>
<li>
You <bold>mistyped something in the "Run" edit field</bold>. You can
test this by copying the line to the clipboard, press the Start menu,
then "Run...", pasting in the text, and pressing "OK". If
the "CircumReality World Simualtor" doesn't appear a few seconds later
then you probably have a typo. (Make sure that any running versions
of the world simulator are shut down before trying this.)
</li>
<li>
You <bold>may have typed in the wrong user and password</bold>.
</li>
<li>
The scheduled task may not have gone off, perhaps because of <bold>permissions
that the hosting company has set.</bold> Talk to their product support staff
for help.
</li>
</ul>
<br/><section><p><bold><big>Detecting failed reboots and crashes</big></bold></p></section>
<p>
You can run a monitoring application to make sure that the CircumReality
server is running properly. To do this:
</p>
<ol>
<li>
<bold>On the sever</bold>, using "Remote desktop connection, <bold>run CircumRealityWorldServer.exe</bold>.
</li>
<li>
Press the <bold>Set up the monitoring mode parameters</bold> button at
the bottom of the page.
</li>
<li>
Press the <bold>Dialog</bold> for "World #1" and use it to find
the .crf file, like "c:\myProject.crf".
</li>
<li>
Type in the <bold>Shard number for world #1</bold>, which is probably "1".
</li>
<li>
If you're running any other worlds on this server, repeat.
</li>
<li>
<bold>Fill in all the Email info</bold> on the page. It's exactly the same information
that you used to send notification E-mails from the server.
</li>
<li>
You might want to <bold>uncheck "E-mail even if it's all running properly"</bold> after
you have the system working. Leave it checked for now so that you know it's working.
</li>
<li>
<bold>Close</bold> the window.
</li>
<li>
Test that the program works by
running <bold>"c:\program filex\mXac\CircumReality\CircumRealityWorldSim.exe
-monitor"</bold>. It should run the monitoring application, potentially start up the
server, potentially send E-mail, and then shut down.
</li>
<li>
As in "Handling re-boots", <bold>create a timer</bold>. However, make this
timer run <bold>once and hour</bold> (or so), and have it
run <bold>"c:\program filex\mXac\CircumReality\CircumRealityWorldSim.exe
-monitor"</bold>.
</li>
</ol>
<br/><section><p><bold><big>Exiting the remote desktop by staying logged on</big></bold></p></section>
<p>
That's it! Your world is running. One important note:
</p>
<ul>
<li>
When you close the "Remote desktop connection" window, <bold>you will stay logged
onto Windows (on the remote computer)</bold>. This is good. If you log off,
the CircumReality World Simulator will be closed, and the world will
stop functioning... until you log back on and re-run it.
</li>
</ul>
Code for basic administration commands.
This library handles many/most administrator commands and features that allow administrators to remotely monitor and control the IF title.
How to create administrator accounts.
<section><p><bold><big>Administrator accounts</big></bold></p></section>
<p>
Online virtual worlds need administrators with special powers,
such as the ability to kick off users.
</p>
<p>
The <bold>user whose name is "Administrator"</bold> will automatically
have a level 100 (maximum) for the pUserAdmin property.
That means, <bold>you must log on and claim the user name
"Administrator"</bold> before a player does.
</p>
<p>
Once you log on as an administrator, any character used
by the administrator will be able to use administration
commands that allow you to create other administrator
accounts. (See the online help for details.)
</p>
<p>
If you want your master administrator account to have a
different name then change gUserAdministrator.
</p>
<p>
You should also <bold>create a character named "Administrator"</bold> that
will receive game-mail send to "[Admins]" and complaints
about griefers.
</p>
Some built-in crash protection.
<section><p><bold><big>Safety measures</big></bold></p></section>
<p>
Circumreality contains some built-in crash protection code and settings.
You might wish to adjust the following:
</p>
<ul>
<li>
<bold>gSafetyMaximumCPU</bold> - This is the maximum CPU that
the server will (attempt) utilize. Once the CPU level reaches this
amount then no more users will be able to log on. This defaults
to 0.8 (out of 1.0) of the CPU.
</li>
<li>
<bold>gSafetyMaximumMemory</bold> - If the server's memory footprint
exceeds this amount then the server will automatically shut down
and restart. This is protection against memory leaks of various
sorts. This defaults to 1.5 GB, since Windows 32 runs out of
memory at 2 GB.
</li>
<li>
<bold>gSafetyMaximumNetwork</bold> - Use this to (approximately) limit the amount
of bandwidth used. If the recent bandwidth usage is more than this
value then users won't be able to log on. This is
in megaBYTES per second. Thus, an 8 megabit line would be 1.0.
A 56 kBaud modem is 0.007.
</li>
<li>
<bold>gSafetyMaximumUserAccounts</bold> - If the number of user
accounts is greater than this, then no more new-user accounts
will be allowed. This defaults to 10,000.
</li>
<li>
<bold>gSafetyMaximumUsersConnected</bold> - New users won't be able to
log in if the number of connections is greater than
gSafetyMaximumUsersConnected. This defaults to 500.
</li>
<li>
<bold>gSafetyMaximumIP</bold> - How many users can be connected
from the same IP address. This defaults to 4 to allow family members
to play together.
</li>
</ul>
How to keep performance statistics.
<section><p><bold><big>Keeping performance statistics</big></bold></p></section>
<p>
If you want to keep statistics about your world's performance, such
as the number of users logged on at any one time, the number of
characters created, etc. then use
the <bold>cStatistics</bold> class.
</p>
<ol>
<li>
<bold>Create a new object</bold> based on cStatistics.
</li>
<li>
Set <bold>pNLPNounName</bold> and <bold>pNLPParseName</bold> to
the name of your statistics, such as "Illegal password attempts".
</li>
<li>
Set <bold>pExamineGeneral</bold> to a description of the
statistics.
</li>
<li>
<bold>pStatisticsDatasets</bold> and <bold>pStatisticsUnits</bold> should
be filled with string labels so viewers of the data know what they're
looking at.
</li>
<li>
If you want players to be able to see the statistic then
set <bold>pUserAdmin</bold> to 0. This defaults to 1, and is the
minimum administration level necessary to see the statistic.
</li>
<li>
Set <bold>pStatisticsSamplesPerDay</bold> to the number of numbers
you wish to store in your statistics per day. In the case of
illegal password attempts, you may only need one per hour, so
a number like 24 would do.
</li>
<li>
Normally, only the 1000 most recent entries will be kept. If
you wish to keep more or fewer entries then
change <bold>pStatisticsMaxEntries</bold>.
</li>
<li>
Whenever an event occurs, such as a failed password attempt,
then call <bold>pStatisticsIllegalPassword.StatisticsTick(1);</bold>.
This will record the event.
</li>
</ol>
<p>
That's it. Administrators will now be able to see how many wrong
passwords were entered, with an accuracy of once per hour.
</p>
<br/><section><p><bold><big>Statistics based on other statistics</big></bold></p></section>
<p>
The illegal password statistics object will only keep 1000 entries, at
once every 24 hours, is about 40 days worth. If you wished to keep
several years worth, you could increase the maximum number of entries,
but that would end up storing more data than you need.
</p>
<p>
Alternatively, you can create a statisic, "slow" illegal password, that's
based on the illegal password statistic and only takes a measurement once
a day:
</p>
<ol>
<li>
<bold>Create a statistics object</bold> just like you created
the one for illegal paswords, except...
</li>
<li>
Set <bold>pStatisticsSamplesPerDay</bold> to 1, or 4 is you want to
keep statistics ever 6 hours.
</li>
<li>
<bold>Don't</bold> call StatisticsTick() for this object when
a bad password is entered.
</li>
<li>
Set <bold>pStatisticsBasedOn</bold> to oStatisticsIllegalPassword.
This tells the new "slow" object to get all its information from
the faster oStatisticsIllegalPassword, using StatisticsQuery().
</li>
</ol>
<p>
The result will be two statistics object, a "fast" one that measures
data every hour, and a "slow" one that sums up the fast one's data
once a day.
</p>
<br/><section><p><bold><big>Non-counter statistics</big></bold></p></section>
<p>
To make a statistic that keeps track of values, such as the
number of simultanous users logged on, as opposed to the number
of users logging on in the last 10 minutes, you'll need to
write some code:
</p>
<ol>
<li>
Write your own <bold>StatisticsSnapshot()</bold> method for the object.
This will get any values, such as gConnectionList.ListNumber(), and
return it. The returned value (or values) will be stored in the
statistic object's database, along with a time stamp.
</li>
<li>
You may need to write your own <bold>StatisticsStarted()</bold>. This
method is called when the statistics object is first started, and can
be used to initialize as settings.
</li>
<li>
When a set of values are "combined", are the summed or averaged?
In the case of the number of users logged on, you wish to average them,
so set <bold>pStatisticsSumToPairDown</bold> to FALSE. If
you were keeping track of the amount of data sent or received then
you'd sum the values, so
set <bold>pStatisticsSumToPairDown</bold> to TRUE.
</li>
</ol>
<p>
Again, easily done. And, just like with the counters, you can create
a slower version of the number of users logged on.
</p>
<br/><section><p><bold><big>Other things</big></bold></p></section>
<ul>
<li>
<bold>StatisticsQuery()</bold> will get the statistical information
from the statistics object.
</li>
<li>
<bold>StatisticsStartStop()</bold> will start or stop keeping the
statistics.
</li>
<li>
If you want to keep track of how long a quest took, or when players
started/stopped it, then create statistics objects based
on <bold>cStatisticsQuestDuration and/or cStatisticsQuestStartEnd</bold> and
reference the objects in the quest's <bold>pQuestStatistics</bold>.
</li>
</ul>
Using the speech log to improve text-to-speech
<section><p><bold><big>Improving text-to-speech</big></bold></p></section>
<p>
Circumreality relies heavily on text-to-speech. Unfortunately, text-to-speech
is of questionable quality because the AI and simulation needed to
produce quality text-to-speech is a long-long ways away.
</p>
<p>
However, you can take some steps to improve text to speech:
</p>
<ol>
<li>
<bold>Use less text-to-speech</bold> - I know this may sound strange,
but minimizing the amount of synthesized speech helps. One of the reasons
I added voice-chat to Circumreality was to minimize the amount that text-to-speech
was used.
</li>
<li>
<bold>Don't mix text-to-speech with real speech</bold> - One sure
way to make text-to-speech sound lousy is to put it side-by-side
with real recorded speech. However, if you still wish to use
recorded speech, MIFL will let you do it.
</li>
<li>
<bold>Use transplanted prosody</bold> - This technology takes a recording
of your voice, "copies" the prosody (pitch, timing, and volume) from the
recording and "pastes" it onto the synthesized voice. It significantly
improves the quality of text-to-speech, but it's a <bold>lot</bold> of work,
and is only useful for pre-canned phrases. Automatically generated phrases
can't be pre-recorded for text-to-speech.
The <bold>speech log</bold> is useful for transplanted prosody. See below.
</li>
<li>
<bold>Use automatic transplanted prosody</bold> - See below.
</li>
<li>
<bold>Use quick transplanted prosody</bold> - See below.
</li>
<li>
<bold>Create a prosody model</bold> - Part of the reason why text-to-speech
sounds so bad is because its prosody (pitch, timing, and volume) are so
wrong. Prosody is wrong because the text-to-speech system doesn't really
understand what it's speaking, so it doesn't know what words to emphasize.
</li>
<p>
You can "Create a prosody model" specific to the text your game world uses.
It will require you to record 500-1000 phrases that are commonly spoken
by the text-to-speech engine, and then learns the prosody for those
phrases, or ones similar.
</p>
<p>
The <bold>speech log</bold> is useful for transplanted prosody.
Also, the <bold>Easy recording</bold> tool might come in handy. See below.
</p>
<li>
<bold>Make your own TTS voice</bold> - Making your own text-to-speech voice
is a <bold>lot</bold> of work (about 5000 recorded phrases), but will help for two reasons.
</li>
<ol>
<li>
In the act of recording your own text-to-speech voice, you end
up creating a <bold>prosody model</bold>, as above.
</li>
<li>
Text-to-speech synthesizes a word, like "lantern", <bold>by attaching
smaller recordings it copied from other words</bold>. For example: "lantern"
may be constructed from the "lan" part of "land" and the word "turn" that
were recorded in the training sentences. If a recording of the word
"lantern" exists in the training sentences, then that will be used
in preference to "lan(d)" + "turn". A recording of the original word
will sound better than combining two smalelr bits.
However, "lantern" may not actually have been in the text-to-speech
training set, or may not have occurred enough to be considered important
enough to keep. If you create your own voice using the 5000 most-common
phrases used in your game world, the words that you
most commonly use will sound better
as a result.
</li>
</ol>
<p>
The <bold>speech log</bold> is useful for transplanted prosody. See below.
</p>
</ol>
<p>
The <bold>speech log</bold> keeps track of the 5000 most-commonly used
text-to-speech phrases. You can use this list of phrases as (a) a
way to decide what sentences to record for transplanted prosody,
(b) sentences to record for your prosody model, or (c) sentences to
record for your own text-to-speech voice.
</p>
<p>
To see the list of phrases, type <bold>"Show speech log"</bold> using
your administrator account. This will display the most common 2500 phrases,
sorted by the most common ones first. (This assumes that your world has
been running long enough to actually speak 5000 different phrases.) It doesn't
display the last 2500 since they have probably only occurred once or twice,
and aren't statistically signifcant.
</p>
<p>
As a programmer, you should be aware of the following:
</p>
<ul>
<li>
<bold>oSpeechLog</bold> is the object that stores the speech
log. It is a sub-class of cSaveToDatabase, so it will be
saved away and reloaded.
</li>
<li>
<bold>oSpeechLog.SpeechLog()</bold> is called whenever SpeakNarrator()
and SpeakObject() are called. If you bypass these two functions you
may wish to append call oSpeechLog.SpeechLog() yourself.
</li>
<li>
<bold>gSpeechLogMax</bold> controls how many spentences will be stored
in the speech log. You can lower or raise this value. 5000 is a
good value, even if you are creating your own text-to-speech voice, since
many of the sentences you record for text-to-speech should be
"phonetically balanced" (look it up on the internet) and not taking
from your game's phrases.
</li>
</ul>
<br/><section><p><bold><big>Easy recording</big></bold></p></section>
<p>
The chances are, you'll be play-testing your IF title and hear text-to-speech
speak a phrase that ends up sounding lousy because of the prosody.
You would always run M3DWave.exe and record the wave then and there, but
there's an easier solution:
</p>
<ol>
<li>
When you first run CircumrealityWorldSim.exe, check
the <bold>Enable tools in the client</bold> checkbox at the bottom of the
page. This will enable some author-specific tools in the client that
are normally hidden from typical players.
</li>
<li>
In the client, select the "Settings & Options" menu item,
following by the "Speech settings" dialog. In
that dialog, type in a <bold>Record-wave-to directory</bold> where
you want any recorded waves to be saved, such as "c:\\MyDirectory".
</li>
<p>
If you're recording waves for your own text-to-speech voice then
type in the directory where all the TTS recordings are saved. If
you want to the use wave for a prosody model, type in that
directory. Or, if you're using automatic transplanted prosody (see
below), then type in the directory where all those files go.
</p>
<li>
Whenever you hear a text-to-speech phrase that has lousy
prosody, <bold>bring up the transcript window</bold>. Next to
each sentence, you'll see a red link for "(Record)". To
record a version of that sentence, press <bold>(Record)</bold>.
The file will automatically be saved in the "Record-wave-to
directory".
</li>
<p>
If you are using the wave file for a text-to-speech voice or a
prosody model, you'll still need to run MNLP.exe
and <bold>add</bold> the recording to the list of wave files
used for the voice or model. Assuming that the directory where
you saved the file is only meant for the specific text-to-speech voice or
prosody model, you can press the "Add all visible wave files" at the
bottom of the wave-file open dialog box.
</p>
<p>
Likewise, if you're using the wave file for transplanted prosody,
you'll need to rebuild the automatic transplanted prosody resources.
</p>
</ol>
<br/><section><p><bold><big>Quick transplanted prosody</big></bold></p></section>
<p>
The MIFL editor makes it easy to record transplanted prosody for strings and
spoken strings in some resources (such as conversation scripts, cut scenes, and
speak-scripts.)
</p>
<p>
Whenever you modify a string, or other appropriate resource, you'll see
options for "Spell check", "Speak", "Record transplanted prosody", and
"Delete trans. pros.".
</p>
<ul>
<li>
<bold>"Spell check"</bold> makes sure all of the words are in the
text-to-speech voice's vocabulary. If they aren't, they'll probably be
mispronounced, and you'll need to add them using 3D Outside the Box's
"Natural Language Processing" application.
</li>
<li>
<bold>"Speak"</bold> has the text-to-speech engine speak the
phrase <bold>without</bold> using transplanted prosody. This will help
identify words not in the voice's vocabulary, as well as detect
gramatical mistakes.
</li>
<li>
<bold>"Record transplanted prosody"</bold> lets you easily record a
transplanted prosody resource for the string. The resource is named,
"rTransProsQuick_XXX", where XXX is a randomly generated number.
Whenever you use Speak(), SpeakScript(), SpeakNarrator(), etc., the
transplanted prosody that you just recorded will be used.
</li>
<p>
This is the <bold>easiest way to use transplanted prosody</bold>.
</p>
<li>
<bold>"Delete trans. pros."</bold> deletes a rTransProsQuick_XXX resource
for the given text.
</li>
</ul>
<br/><section><p><bold><big>Automatic transplanted prosody</big></bold></p></section>
<p>
If you're trying desperately to improve the quality of a text-to-speech
voice, you should first try to use "Easy recording" (above) to record
wave files for a prosody model.
</p>
<p>
However, a prosody model may not be able to speak a specific sentence with
the correct and subtle prosody. If that's the case then the next step
is to use "Automatic transplanted prosody".
</p>
<p>
To use "Automatic transplanted prosody", do the following:
</p>
<ol>
<li>
Use "<bold>Easy recording</bold>" (above) to record problem sentences as .wav files.
Store them all in the same directory.
</li>
<li>
<bold>Create a new library</bold> in your project where you will store
the automatic transplanted prosody resources.
</li>
<li>
In the <bold>Misc menu</bold>, select the <bold>Automatic
transplanted prosdy</bold> tool.
</li>
<li>
<bold>Fill in</bold> all the fields, especially the directory where
you have saved all the troublesome .wav files.
</li>
<li>
Press <bold>Create transplanted prosody resources from .wav files</bold> and
the computer will process for awhile. (If you have just recorded some
new .wav files, this may take a long time.)
</li>
<li>
Switch to your <bold>List of resource</bold>, also in the "Misc" menu.
</li>
<p>
You will see a number of new resources labelled "rTransProsAutoXXXXX", where
XXXXX is replaced by a number. These are the transplanted prosody resources
automatically generated by the .wav files you stored in the directory.
</p>
<li>
When you have a NPC <bold>SpeakScript()</bold>,
or the narrator <bold>SpeakNarrator()</bold>, if
the text <bold>exactly</bold> matches the transplanted prosody text, then
the transplanted prosody resource will be spoken instead of the text.
</li>
<p>
You should be able to hear the difference in quality. If you're not 100%
convinced, then have the NPC speak the text string, but with an added space,
so the automatic transplanted prosody resource won't be used.
</p>
</ol>
Showing a notice when players log on.
<section><p><bold><big>Log-on notices</big></bold></p></section>
<p>
If you want to show a notice when players log on, such as, "The mountains
are under rennovation. Please E-mail me if you find any bugs," then
do the following:
</p>
<ol>
<li>
Override <bold>rLogonMainNotice</bold> with your own notice.
</li>
<li>
Change <bold>gNoticeVersion</bold> to a new number so that players
will be shown the notice. (A notice will only be shown once, and the
unique ID in gNoticeVersion is used to determine if the notice has
already been seen.)
</li>
</ol>
The basic communications library provides internal E-mail (sometimes called mud-mail) and bulletin board facilities for users.
Tells you how to add one or message boards to your online world.
<section><p><bold><big>Message boards</big></bold></p></section>
<p>
Adding message boards to your online world is very easy:
</p>
<ol>
<li>
<bold>Create</bold> a new object and base it
off of <bold>cMessageBoard</bold>.
</li>
<li>
Make sure the <bold>Automatically create as an object</bold> button
is checked. The message board <bold>should not</bold> be located in
a room though.
</li>
<li>
Set the <bold>pNLPNounName</bold> and <bold>pNLPParseName</bold> with
the message board's name, like "Suggestions".
</li>
<li>
Set <bold>pExamineGeneral</bold> to a string describing the message board.
</li>
<li>
<bold>pMessageBoardDatabase</bold> should be filled with a unique database
name for the message board where the message thread objects will
be stored. This string will be passed into the DatabaseXXX() functions.
You might wish to prefix the string with "MessageBoard" so the database is
obviously a message board.
</li>
</ol>
<p>
That's it. You're done!
</p>
<p>
If you wish to change the priviledges for the message board then
write a <bold>MessageBoardCanAcces()</bold> method for the object.
</p>
Provides functionality to handle standard CRPG features.
The Basic RPG Library provides functionality to handle standard CRPG features, such as attributes, skills, combat, combat with NPCs, talking to NPCs, etc. It also handles a lot of MUD-style functionality.
This library is based on the Basic Interactive Fiction Library, which must also be included in the project. It must have a higher priority than the Basic Interactive Fiction Library.
Describes how to define new skills, attributes, and disadvantages, and how to use them.
<section><p><bold><big>Skills, attributes, and disadvantages</big></bold></p></section>
<p>
The RPG library does <bold>not</bold> support class levels,
as is typical in Dungeons & Dragons. Instead, it supports
skills, allowing players to advance their characters in any
direction they like.
</p>
<p>
There are three types of skills:
</p>
<ul>
<li>
<bold>Skills</bold> - These are learned abilities, such as
swordfighting, archery, and basket weaving. Skills can be
learned by the characters and trained over time to improve
them; training is done using experience points.
</li>
<li>
<bold>Attribute</bold> - Attributes are characteristics that
are fundamental to a character and cannot be changed (under
normal circumstances.)
</li>
<li>
<bold>Disadvantages</bold> - Disadvantages are, in many
ways, negative skills. They are something the character begins
with, but can pay off over time and get rid of. Some sample
disadvantages are: fear of spiders, studdering, and alcoholism.
Disadvantages are useful role-playing devices.
</li>
</ul>
<p>
Role playing games often have "special abilities" and "feats".
These could be categorized under skills too.
</p>
<br/><section><p><bold><big>Create your own attributes, skills, and disadvantages</big></bold></p></section>
<p>
The "Basic RPG library" and "Basic Fantasy library" include some
standar attributes, skills, and disadvantages. You can always
create your own. Here's how:
</p>
<ol>
<li>
<bold>Create</bold> a new object, called "oSkillXXX", where XXX
is replaced with your skill's name.
</li>
<li>
Base the skill off the super-class, <bold>cSkill</bold>. The
cSkill class is a sub-class of cObject.
</li>
<li>
Check the <bold>Automatically create as an
object</bold> button. All skills are instantiated objects.
</li>
<li>
Fill in the <bold>pNLPNounName</bold> and <bold>pNLPParseName</bold> for
the skill with an appropriate name,
</li>
<li>
Provide a description for the skill in <bold>pExamineGeneral</bold>.
This description is displayed when the user types the "skills" command.
</li>
<li>
Fill in the skill's <bold>pSkillCategory</bold> so that the
skill will be placed in the appropriate category in the skills
list, such as "Combat skills", or "Spells".
</li>
<li>
If this is an attribute, set <bold>pSkillType</bold> to <bold>'a'</bold> (case
is important). Attributes cannot be trained, and are randomly
generated for a character when it is first created. You
should also modify <bold>gAttributes</bold> to include the skill
object.
</li>
<p>
If the skill is really a disadvantage,
set <bold>pSkillType</bold> to <bold>'d'</bold> (case
is important). Users cannot train their characters with a
disadvantage, so you will need to modify the character creation
code so that disadvantages will be randomly applied or the
player will be able to select the character's disadvantage.
</p>
<p>
If the skill is a normal skill, don't set <bold>pSkillType</bold> since
the default value, set in cSkill, is <bold>'s'</bold>.
</p>
<li>
The <bold>pSkillLevelCap</bold> is normally set to a reasonably
high value, around 30. However, you can make this lower or higher.
If the value is low, the skill acts more like a "feat", where the
skill is either not known, or known completely. (For example: Riding
a bicycle.)
</li>
<li>
You may wish to set <bold>pSkillTrainTeacherLevel</bold>. A player
with a high enough level in a skill can teach the skill to
other players. This is a number indicating the required skill
level.
</li>
<p>
If <bold>pSkillTrainTeacherLevel</bold> is set to 0, any
characters can learn the skill without a teacher. You might
wish to use a 0-value for skills like climbing or jumping, where
the skills are just a matter of practice.
</p>
<p>
To prevent players from training the skill to
thousands of other players, the teacher <bold>loses</bold> one
experience point every time they train another character.
</p>
<li>
To make the skill require another skill, or an attribute,
set <bold>pSkillTrainPrerequisites</bold>. This can contain
zero or more skills with required levels.
</li>
<li>
If the skill is associated with a language,
set <bold>pSkillLanguage</bold> to the language-independent
(and case sensative) language string, such as "common" or "elvish".
If it's not a language, leave pSkillLanguage blank or set to NULL.
</li>
<li>
If the language is an animal or monster language, you might
wish to write a <bold>LanguageSoundEffect()</bold> for the skill
so that when the animal/monster speaks to players that don't
understand the language, a wave file will be played (like a growl sound)
instead text-to-speech.
</li>
</ol>
<p>
Those are the basics of creating a new skill. There is more,
but I'll get to the advanced topics after discussing experience points.
</p>
<br/><section><p><bold><big>Experience points</big></bold></p></section>
<p>
Every skill has a <bold>skill level</bold>. Skills begin at level 0,
and are increased as the user trains the skill using up
earned experience points.
</p>
<p>
The amount of experience points to raise a skill to the next
level is (roughly) the level of the skill. The actual equation
is that the <bold>level of the skill =
sqrt(experience points invested)/2</bold>.
This makes is more and more difficult for skill levels to improve.
</p>
<p>
As a general guideline, 1 XP is handed out for every hour
that the player plays. <bold>1 XP = 1 hour of play</bold>.
Therefore, if a player only raises one skill, they can go
from level 0 to level 10 in about 50 hours of play.
You are, of course, welcome to hand out experience points
any way you wish.
</p>
<p>
In online (multiplayer) mode, the default behavior is to
automatically hand out 1 XP for every hour the player is logged
on (even if they're chatting). This is limited to no more than 1XP
per day, and 3 XP per week. That means, that barring additional
XP from quests, a player can get a maximum of 156 XP per year.
</p>
<p>
In offline (single player) mode, experience points are handed out
based on what quests/tasks the player completes. To
provide XP in either offline or online mode,
call <bold>XPAward()</bold>.
</p>
<p>
The RPG library also includes an <bold>XPPenalize()</bold> call
that will penalize the player if they act out of character. When
the player has been penalized, his character can receive no
experience for the next 24 hours. An example of penalization would
be if the player's character has a "fear of heights" disadvantage
but persisted in having his character walk across a tall bridge
despite warnings.
</p>
<p>
A player's attributes (or even other skills) affects how much
1 XP helps a skill. Each skill has a <bold>pSkillAttribiteBase</bold> property
that can list zero or more attributes. You can use this so that
a higher attribute makes the experience go further towards advancing
the skill. For example: A high intelligence would make learning
a mathematics skill easier, thus making XP count for more. If
the same character had a low strength attribute, strength-based
skills would not advance as quickly.
</p>
<br/><section><p><bold><big>Attributes and standard deviations</big></bold></p></section>
<p>
In Dungeons & Dragons, attributes are rolled on 3 6-sided dice,
resulting in a value between 3 and 18, with the average value
being 10.5. The distribution of the numbers forms a <bold>bell curve</bold>,
with attribute values in the 8-12 range being much more common than
those at the extremes.
</p>
<p>
A bell curve is a non-technical term for a mathematical
entity known as a <bold>gaussian</bold>. The neat thing about
bell curves, as mentioned above, is that most people's strength (or
whatever) ends up being near the norm, but a very are very strong
or very weak.
</p>
<p>
The topic of bell curves brings up <bold>standard deviations</bold>.
A standard deviation says how close an attribute is close to the
norm. 68% of all characters will have a strength attribute
within <bold>1 standard deviation</bold> of the norm. 95%
will be within 2 standard deviations, 99% for 3, and 99.99% within
4 standard deviations.
</p>
<p>
In English terms, a world champion body builder is about 5 standard
deviations above normal (stronger than 99.9999% of the population).
Likewise, a exceptionally weak person is -5 standard deviations,
or weaker than 99.9999% of the population.
</p>
<p>
The RPG library <bold>stores attributes in standard deviations</bold>,
not values between 3 and 18. Therefore, most characters' attributes
will be close to 0 (meaning average), with ranges between
-1 and 1. Some will be higher 1 to 2 or lower (-1 to -2) meaning
above/below normal strength (or, dexterity, etc.) but nothing
too unusually. A rare character will have values higher than 2, or
lower than -2.
</p>
<p>
Storing attributes as standard deviations has many handy ramifications
that will become apparent as you read through the tutorials.
</p>
<p>
When a new character (based in cCharacter) is created, the constructor
automatically "rolls" random values for the attributes
in <bold>gAttributes</bold>. It uses the <bold>RandomStdDev()</bold> function
to generate the values. The attributes are also "normalized" so
that the sum of all the character's attributes are 0... so as
not to create any hopeless characters.
</p>
<br/><section><p><bold><big>Races and skills</big></bold></p></section>
<p>
While races won't be discusses completely until a later topic,
I will touch on some issues here where races affect what skills
a character has.
</p>
<p>
First of all, any race class (such as cRaceElf) is ultimately
sub-classed from cCharacter. cCharacter supports several
properties related to attributes, a few of which are relevant
to races.
</p>
<ul>
<li>
<bold>pSkillsRacialInnate</bold> are a list of skills that
are innate to a race. These include attribute adjustments
(like all Elves having a +1 dexterity), or abilities
that the race is born with (such as all Elves having a +5 magic skill).
</li>
<li>
<bold>pSkillRacialLearned</bold> are skills that are automatically
learned by the race. For example: All Elves being knowing cSkillElvish
at level 10, and cSkillArchery at level 5, or whatever.
When a character is first created, the character's constructor will
move all the skills from pSkillRacialLearned into the character's
skill list.
</li>
<li>
<bold>pSkillsMaleInnate</bold> and <bold>pSkillsFemaleInnate</bold> work
similarly to pSkillsRacialInnate except that they affect skills
based on gender. There is no equivalent male/female test
for pSkillRacialLearned though.
</li>
</ul>
<br/><section><p><bold><big>Characters and skills</big></bold></p></section>
<p>
Each character, based on cCharacter, has a number of properties
and methods pertaining to skills:
</p>
<ul>
<li>
<bold>pSkills</bold> is a list of all the skills the character
knows along with their skill level. As stated above, the
character's constructor will add any skills
from <bold>pSkillsRacialInnate</bold> that do not already
appear in pSkills.
</li>
<p>
If you write code to add or remove a skill from <bold>pSkills</bold>,
don't forget to call <bold>SkillAdded()</bold> or <bold>SkillRemoved()</bold>.
See the advanced skill topic below.
</p>
<li>
As a general rule, you should <bold>not</bold> access <bold>pSkills</bold> directly,
but instead use <bold>SkillGet()</bold> from the character object.
SkillGet() is a useful method that gets the skill level
from pSkills, and then <bold>adds</bold> the skill levels from
<bold>pSkillsRacialInnate</bold> and <bold>pSkillsTemporary</bold> if
there are any. This basically returns the skill level
that includes character-specific skills/attributes,
racial skills/attributes, and magic effect enhancements
from pSkillsTemporary.
</li>
<li>
<bold>pSkillsTemporary</bold> stores temporary modifications
to skills, usually resulting from the possession or use
of magic items. For example: A "Ring of Elvish Speech" would
add the oSkillElvish skill to pSkillsTemporary while it
was worn, allowing the user to speak elvish.
</li>
<li>
Rather than modifying pSkillsTemporary directly, you
should call the <bold>SkillTemporaryAdd()</bold> method,
as well as <bold>SkillTemporaryQuery()</bold> and <bold>SkillTemporaryRemove()</bold>.
</li>
</ul>
<br/><section><p><bold><big>Advanced skill topics</big></bold></p></section>
<p>
Now that I have spend time discussing how skills fit in with
characters, I'll delve into some advanced skill topics:
</p>
<ul>
<li>
Whenever a <bold>skill is added or removed</bold> from
a character's <bold>pSkills</bold> or <bold>pSkillsTemporary</bold> properties,
you should
call <bold>cSkill.SkillAdded()</bold> or <bold>cSkill.SkillRemoved()</bold>.
(You don't need to call these if you
use <bold>SkillTemporaryXXX()</bold> since the functions are
called automatically.)
</li>
<p>
These calls allow the skills to set or remove timers and other
hooks into the character object. The most common use for such
hooks is disadvantage, where, for example, a cSkillAlcoholic
disadvantage might require the character to drink some
alchohol every 30 minutes of play or suffer an XPPenalize() call.
</p>
<p>
Thus, if you wrote a cSkillAchoholic disadvantage, you'd
provide a <bold>SkillAdded()</bold> method that would set
a timer to go off every 30 mintes. <bold>SkillRemoved()</bold> would
destroy the timer.
</p>
<li>
You can write a <bold>SkillCanLearn()</bold> method for your
skill that only allows it to be learned by certain races.
</li>
<li>
If a skill allows a player to issue new command then
you should provide a <bold>NLPCommandParseQuery()</bold>,
<bold>NLPCommandParse()</bold>, and <bold>NLPRuleSetTempVerb()</bold> message
to parse th new command. For example: If the skill were
gymnastics, you might provide a "backflip" or "do a handstand"
command for the user. Players who did not know the skill wouldn't
be able to use the commands.
</li>
</ul>
Tells you how to make new body parts for characters, and allow objects to be worn or wielded by characters.
<section><p><bold><big>Body parts and wearing/wielding objects</big></bold></p></section>
<p>
All RPGs allow characters to wield swords, don shields, and wear
armor of various sorts. This requires the RPG library to know
about body parts (such as legs, arms, torso, and head), and
where items are worn on the body.
</p>
<p>
In the RPG library, all characters have a list of <bold>body parts</bold>; the
default human body parts are oBodyPartHead, oBodyPartTorso,
oBodyPartArmLeft, oBodyPartArmRight, oBodyPartLegLeft,
and oBodyPartLegRitht. Monsters or other races might have
different body parts, including tails.
</p>
<p>
Each body part has one or more <bold>body part locations</bold>.
For example: The head has a location for a hat (or helmet),
glasses, earrings, and necklace. Objects that are worn attach
themselves to these body parts.
</p>
<p>
Objects that can be worn maintain a list of body part locations
that they can attach to. Usually this is only one location (such
as the necklace), but some objects will connect to several
locations (such as a jumpsuit, which covers the character's torso,
arms, and legs).
</p>
<br/><section><p><bold><big>Creating body part locations</big></bold></p></section>
<p>
The list of body part locations that are building into the RPG
library are quite extensive. However, you may need to add your
own body part locations. Here's how:
</p>
<ol>
<li>
<bold>Create</bold> a new object, called "oBodyPartLocXXX", where XXX
is replaced with your location's name, such as "horn".
</li>
<li>
Base the body part location off the super-class, <bold>cBodyPartLoc</bold>. The
cBodyPartLoc class is a sub-class of cObject.
</li>
<li>
Check the <bold>Automatically create as an
object</bold> button. All body part locations are instantiated objects.
</li>
<li>
Fill in the <bold>pNLPNounName</bold> and <bold>pNLPParseName</bold> for
the skill with an appropriate name.
</li>
<li>
Fill in <bold>pBodyPartLocDisplay</bold> with a string displayed
when the object is equipped. This will normally be "Worn" or "Held",
although you could always be more creative.
</li>
<li>
Fill in <bold>pBodyPartLocEquipName</bold> with a list of command
strings that can be used to equip the an item to the body part.
Normally this is ["`wear"], allowing a user to type in "wear OBJECT", or
"put on OBJECT". If you use ["`hold"] the user can type in
"wield OBJECT" or "hold OBJECT". Right now, `wear and `hold are
the only two strings accepted, but you can add more.
</li>
<li>
Add the body part location object, oBodyPartLocXXX, to
the appropriate body parts. See below.
</li>
</ol>
<br/><section><p><bold><big>Creating body parts</big></bold></p></section>
<p>
The chances are that you'll have to add new body parts, especially
for monsters. The standard RPG library comes with humanoid body
parts (heads, arms, legs, torsos), but doesn't include tails, wings,
etc.
</p>
<p>
To create a new body part:
</p>
<ol>
<li>
<bold>Create</bold> a new object, called "oBodyPartXXX", where XXX
is replaced with your body part's name, such as "WingLeft".
</li>
<li>
Base the body part off the super-class, <bold>cBodyPart</bold>. The
cBodyPart class is a sub-class of cObject.
</li>
<li>
Check the <bold>Automatically create as an
object</bold> button. All body parts are instantiated objects.
</li>
<li>
Fill in the <bold>pNLPNounName</bold> and <bold>pNLPParseName</bold> for
the skill with an appropriate name.
</li>
<li>
If the body part is on the left side of the creature,
set <bold>pBodyPartRCL</bold> to -1. Use 0 if it's centered,
or 1 if it's on the right side.
</li>
<li>
Likewise, set <bold>pBodyPartTCB</bold> to 1 if the body
part is on the top of the create, 0 for mid-level, and
-1 for bottom.
</li>
<li>
Set <bold>pBodyPartFCB</bold> to 1 if the body part is on
the front of the create, 0 for mid, and -1 for back. Humanoids
will use 0, but a centaur might have its human torso marked
as being on the front, while the horse's torso mid, and the
rear legs and tail as back.
</li>
<li>
<bold>pBodyPartLocations</bold> is a list containing the
locations in the body part. Each location is a sub-list
beginning with the body-part location object, followed by
the shape string, and the weight scaling.
</li>
<p>
The "body-part location object" is the oBodyPartLocXXX object.
The shape string is a string that identifies the overall
shape of the body part, such as "human". If you wanted an orc's
head to be a different shape than a human's, so that orcs could
not wear human helmets, then the shape would be "orc". Set
the scale to 1.0, unless you have a combination creature like
a centaur.
</p>
<p>
For example: The pBodyPartLocations for the head might
be: <bold>[[oBodyPartLocHat, "human", 1],
[oBodyPartLocGlasses, "human", 1],
[oBodyPartNecklace, "human", 1]]</bold>.
</p>
<li>
You will need to write a <bold>BodyPartStatus</bold> function if
the body part affects any the ability for the character to
perform an action, such as legs affecting walking ability.
For more information, see the next tutorial about damage.
</li>
<li>
There are still more properties for body parts, but they
deal with the damage model (which is the next tutorial).
</li>
</ol>
<p>
You will need to modify the class that defines the race (or
creature) to include the body parts:
</p>
<ol>
<li>
In the race's class definition,
set <bold>pBodyParts</bold> to a list of the body parts
associated with the race. Each body part is a oBodyPartXXX
object.
</li>
<p>
Example: pBodyParts could be set to <bold>[oBodyPartHead,
oBodyPartTorso, oBodyPartArmLeft,
oBodyPartArmRight, oBodyPartLegLeft,
oBodyPartLegRight]</bold>.
</p>
<p>
Adding an extra oBodyPartArmLeft will make a 3-armed character,
etc.
</p>
</ol>
<br/><section><p><bold><big>Objects that can be equipped</big></bold></p></section>
<p>
Making an object than can be equipped (worn, held, wielded, etc.)
is easy:
</p>
<ol>
<li>
Set <bold>pEquipLoc</bold> to an oBodyPartLocXXX object if
the object is equipped in only one body part (such as a helmet
using oBodyPartLocHat), or provide a list of oBodyPartLocXXX
for objects using several body parts. For example: A two
handed sword would have [oBodyPartLocHeld, oBodyPartLocHeld],
ensuring that it's held in two hands.
</li>
<li>
If your object is normally equipped in the character's off hand,
such as a shield, then set <bold>pEquipOffhand</bold> to FALSE.
Otherwise, leave it blank.
</li>
<li>
If you want your object to be equipped in body part locations
of a specific shape, such as a helment that only fits orcs,
then set <bold>pEquipShape</bold> to the shape string,
such as "orc".
</li>
<li>
To make the object only fit certain sized characters,
set <bold>pEquipWeightMin</bold> and <bold>pEquipWeightMax</bold> to
the character's minimum and maximum weights. This way
you can make clothes that only fit small characters, etc.
</li>
<li>
You can add futher restrictions to what characters can equip
your item by providing your
own <bold>EquipCanBeEquipped()</bold> method. For example: You
might have a sword that can only be carried by character's
named "Arthur".
</li>
<li>
Whenever the object is equipped or unequipped, a
call to <bold>MoveNotify()</bold> will be made. You can
add a MoveNotify() method to your object so that its magic
abilities will only activate when it's equipped.
</li>
</ol>
<p>
That's it, your object can be held or worn.
</p>
<br/><section><p><bold><big>Some useful methods</big></bold></p></section>
<ul>
<li>
<bold>Equip()</bold>, supported by cCharacter objects, will cause
a character to equip an item.
</li>
<li>
<bold>EquipCanBeEquipped()</bold>, supported by cObject objects, will
indicate whether or not a character can equip the item.
</li>
<li>
<bold>EquipUnEquip()</bold>, supported by cCharacter objects, will cause
a character to unequip an item.
</li>
</ul>
Goes over what kinds of damage (wounds, etc.) characters can take.
<section><p><bold><big>Damage model</big></bold></p></section>
<p>
Most role-playing games use the concept of "hit points" to
indicate how much damage a character can take, either in combat
or from other mishaps (such as falling). Circumreality does NOT use
hit points, but instead uses a more realistic damage model
with bleeding, flesh wounds, and broken bones. As a result,
combat is more dangerous, and not something a player will
enter into lightly.
</p>
<p>
There are two basic types of damage: Damage which is systemic,
like fatigue or bleeding, and damage which is localized to
a body part (such as a flesh wound or broken bone). I'll
being by discussing systemic damage since localized damage
can cause systemic damage.
</p>
<br/><section><p><bold><big>Systemic damage</big></bold></p></section>
<p>
A character's body can take several different forms of systemic
damage:
</p>
<ul>
<li>
<bold>Bloodloss</bold> - Weapons create wounds in body parts,
and wounds cause the character's blood to leak away slowly
(or quickly). If enough blood is lost the character will fall
uncsoncsious, and then die.
</li>
<li>
<bold>Dazed</bold> - The character still standing, but not
thinking clearly enough to do anything.
</li>
<li>
<bold>Death</bold> - Of course. Player characters will be
resurrected after a few minutes of death (during which time
their bodies may be looted). NPCs will be deleted.
</li>
<li>
<bold>Disease</bold> - Disease in Circumreality is treated more realistically
than standard RPGs. Diseases take time to appear, and then
bedrest (or magic) before they disappear.
</li>
<li>
<bold>Drop held items</bold> - This isn't exactly damage, but
it's an effect of taking damage.
</li>
<li>
<bold>Fatigue</bold> - Every action the character undertakes
tires him/her slightly. Do enough strenuous actions close
enough together and the character will become fatigued.
</li>
<li>
<bold>Knocked down</bold> - Again, it's not damage, but it
a consequence of damage.
</li>
<li>
<bold>Magic effect</bold> - Some sort of spell affects the
character.
</li>
<li>
<bold>Mana</bold> - The about of magical energy that the
character can muster to case spells. It's very similar to
fatigue.
</li>
<li>
<bold>Unconscious</bold> - Of course, the character can
become unconscious.
</li>
<li>
<bold>Venom</bold> - Venom is treated more realistically
in Circumreality than other RPGs. Instead of doing damage, venoms slowly
(or quickly) seep into the character's bloodstream and
cause mayham, potentially even killing the character.
</li>
</ul>
<br/><section><p><bold><big>Bloodloss</big></bold></p></section>
<p>
Characters have blood, and if they lose it through damage they
will fall unconscious or die. The amount of blood that a
character has lost is stored in <bold>pDamageBloodLoss</bold>.
It ranges from 0.0 to 1.0. At 0.0 the character has all his/her
blood, at 0.5 the character falls unconscious, and at 1.0 the
character dies.
</p>
<p>
Furthermore, the character's fatigue and mana
can not be recovered to less than twice the bloodloss. Therefore,
if the character has lost 0.25 of their blood (25%) then their
fatigue can never get better than 0.5 (50%).
</p>
<p>
To cause bloodloss in a character (and potentially unconsciousness
or death), call <bold>DamageBloodLoss()</bold>.
</p>
<br/><section><p><bold><big>Dazed</big></bold></p></section>
<p>
Blows to the head or chest, or perhaps even magic, can cause
the character to become dazed. A dazed character is unable
to act for a few seconds. To daze a character,
call <bold>DamageDazed()</bold>.
</p>
<br/><section><p><bold><big>Death</big></bold></p></section>
<p>
Characters can be killed outright by
calling <bold>DamageDeath()</bold>. Usually death will
result from some other sort of damage, such as bloodloss.
</p>
<p>
A dead character falls to the ground and their body can
be looted. After a few minutes, set by a global that you can
modified, they're resurrected and moved to the last safe
room they visited. If the character is a NPC the NPC and all
contents it still holds are deleted.
</p>
<p>
If you wish more severe penalties for death, such as permadeath
or skill loss, you can modify the code for DamageDeath().
</p>
<br/><section><p><bold><big>Disease</big></bold></p></section>
<p>
Diseases are more complex than the other systemic damages I've
described so far. A Circumreality disease acts much more like a real diseases
than other RPGs (which usually decrease the diseased character's
strength for a few minutes and then the disease vanishes).
</p>
<p>
A character catches a disease when <bold>DamageDisease()</bold> is
called. If the character already has the disease then nothing
happens. (I haven't implimented disease immunity though, so
diseases can be caught more than once.)
</p>
<p>
The disease begins with 0 "disease units", an intentionally
vague value. Every second, the number of disease units is
increased by an amount specified by the disease, multiplied
by the virulence of the strain. Furthermore, if the character
does any activity (that causes fatigue) then the disease will
spread more rapidly.
</p>
<p>
After a time (specified by the disease), the character's body
will identify the diseas and then decrease the number of
active disease units. When the number of disease units is
knocked down to 0, the disease is eradicated.
</p>
<p>
To create your own disease, you need to:
</p>
<ol>
<li>
<bold>Create a new object</bold> for your disease, called
oDiseaseXXX, where XXX is the disease name.
</li>
<li>
Set the disease's <bold>superclass to cDisease</bold>.
</li>
<li>
Check the option to <bold>automatically create</bold> the
disease object, although it will never be contained within
a character.
</li>
<li>
Fill in <bold>pNLPNounName</bold> and <bold>pNLPParseName</bold> to
name the disease.
</li>
<li>
Fill in <bold>pExamineGeneral</bold> with a line describing the
disease's effects, that will be shown when the character
asks for his/her status. If you don't wish to show anything,
set this to NULL.
</li>
<li>
Set <bold>pDiseaseRelease</bold> to the number of disease
units created per second the disease is active.
</li>
<li>
<bold>pDiseaseFatigue</bold> affects how much faster the
disease reproduces when the character is active.
</li>
<li>
<bold>pDiseaseStartResist</bold> is the number of seconds
(on average) that it will take the character's body to
begin resisting the disease.
</li>
<li>
<bold>pDiseaseRecover</bold> is the number of disease
units reduced per second once the character's body fights
the disease.
</li>
<li>
You should provide a <bold>VenomTimer()</bold> method.
It is called when the disease is first introduced, and
every few seconds thereafter until the disease is
eradicated. The code in this function will control the
effects of the disease; you can have the disease do
any sort of damage by calling DamageXXX(), or even
non-damage effects.
</li>
</ol>
<br/><section><p><bold><big>Drop held items</big></bold></p></section>
<p>
Calling <bold>DamageDropHeld()</bold> will cause the character
to drop any held items in the given body part. This can be called
as part of the damage. It is automatically called if the
character receives significant damage to an arm, or is stunned
in the arm.
</p>
<br/><section><p><bold><big>Fatigue</big></bold></p></section>
<p>
Character's get tired, stored in a property
called <bold>pDamageFatigue</bold>, which ranges from 0.0 to
1.0. 0.0 means the character is fully rested, while 1.0
is dead tired.
</p>
<p>
You can cause fatigue damage by
calling <bold>DamageFatigue()</bold>, although very few
attacks will cause fatigue. (Some diseases might.) Furthermore,
if too much blood is lost, fatigue will also be redurced.
</p>
<p>
A small amount of fatigue is lost whenever the character
performs an action. Fatigue is quickly regained, so
the loss is seldom notieced However, if a character tries
to perform too many high-fatigue actions in too short
of a time, the character's fatigue will drop, performance
will degrade, and eventually the character will be too tired
to even move. (This frequently happens in combat.)
</p>
<p>
To fatigue a character through an action,
call <bold>ActionFatigue()</bold>.
</p>
<br/><section><p><bold><big>Knock down</big></bold></p></section>
<p>
To knock a character to its feet, call <bold>DamageKnockDown()</bold>.
Some attacks (such as shoves) will cause one character to
fall down. If the character sustains enough damage to a leg
it may also be knocked down.
</p>
<br/><section><p><bold><big>Magic effect</big></bold></p></section>
<p>
Magic effect "damage" acts a lot like diseases. A magic effect
is an object derived from cMagicEffect, although the exact
mechanics of disease are not duplicated.
</p>
<p>
To cause a magic effect, call <bold>DamageMagicEffect()</bold>.
</p>
<p>
To create your own magic effect, you need to:
</p>
<ol>
<li>
<bold>Create a new object</bold> for your magic effect, called
oMagicEffectXXX, where XXX is the magic effect name.
</li>
<li>
Set the magic effect's <bold>superclass to cMagicEffect</bold>.
</li>
<li>
Check the option to <bold>automatically create</bold> the
magic effect object, although it will never be contained within
a character.
</li>
<li>
Fill in <bold>pNLPNounName</bold> and <bold>pNLPParseName</bold> to
name the magic effect.
</li>
<li>
Fill in <bold>pExamineGeneral</bold> with a line describing the
magic effect's effects, that will be shown when the character
asks for his/her status. If you don't wish to show anything,
set this to NULL.
</li>
<li>
Set <bold>pMagicEffectDuration</bold> to the average number
of seconds that the character will be affected by the magic.
Characters with higher oSkillMana will be affected less.
</li>
<li>
You should provide a <bold>VenomTimer()</bold> method.
It is called when the magic effect is first introduced, and
every few seconds thereafter until the magic effect
times out. The code in this function will control the
effects of the magic effect; you can have the magic effect do
any sort of damage by calling DamageXXX(), or even
non-damage effects.
</li>
</ol>
<br/><section><p><bold><big>Mana</big></bold></p></section>
<p>
Mana works a lot like fatigue, except instead of it being
physical damage, it's mental/magical. To decrease a character's
mana, call <bold>DamageMana()</bold>.
</p>
<br/><section><p><bold><big>Unconscious</big></bold></p></section>
<p>
To knock a character out, call <bold>DamageUnconscious()</bold>.
</p>
<p>
Characters will automatically be knocked out if they lose too
much blood or receive a severe blow to the head or chest.
</p>
<br/><section><p><bold><big>Venom</big></bold></p></section>
<p>
Venoms (and poisons), like disease, are more complex in Circumreality than
other RPGs. In Circumreality, a venom is somehow injected into the body (or
in the case of alchohol, imbibed). A quantity of the venom
sits in a "reserve" and slowly seeps into the character's
bloodstream or lymph nodes. Doing physical activity increases
the amount seeping in. At the same time, the character's live
removes a fixed amount of venom from the bloodstream every
second.
</p>
<p>
To "inject" a venom into a character,
call <bold>DamageVenom()</bold>.
</p>
<p>
To create your own venom (or poison), you need to:
</p>
<ol>
<li>
<bold>Create a new object</bold> for your venom, called
oVenomXXX, where XXX is the venom name.
</li>
<li>
Set the venom's <bold>superclass to cVenom</bold>.
</li>
<li>
Check the option to <bold>automatically create</bold> the
venom object, although it will never be contained within
a character.
</li>
<li>
Fill in <bold>pNLPNounName</bold> and <bold>pNLPParseName</bold> to
name the venom.
</li>
<li>
Fill in <bold>pExamineGeneral</bold> with a line describing the
venom's effects, that will be shown when the character
asks for his/her status. If you don't wish to show anything,
set this to NULL.
</li>
<li>
Set <bold>pVenomDose</bold> to the number of liters of venom
in one dose. (In the case of beer, this is 1.0 liter.)
</li>
<li>
<bold>pVenomRelease</bold> is the percent of venom that seeps
from the reserve into the character's bloodstream every second.
</li>
<li>
<bold>pVenomFatigue</bold> lets you control how quickly the
venom spreads if the character undertakes activity. (In real
life, you have a much better chance of surviving a venemous
snake bite if you remain calm than if you run around and
quickly spread the venom through your system.)
</li>
<li>
<bold>pVenomRecover</bold> is the number of liters of
venom removed from the character's system every second.
Characters with a higher oSkillEndurance will have venom
removed more quickly.
</li>
<li>
<bold>pVenomNonLethal</bold> controls whether NPCs with
!pDamageCanBeAttacked are allowed to ingest the venom.
</li>
<li>
You should provide a <bold>VenomTimer()</bold> method.
It is called when the venom is first introduced, and
every few seconds thereafter until the venom is
eradicated. The code in this function will control the
effects of the venom; you can have the venom do
any sort of damage by calling DamageXXX(), or even
non-damage effects.
</li>
</ol>
<br/><section><p><bold><big>Localized damage</big></bold></p></section>
<p>
In addition to systemic damage, each body part (based on
oBodyPartXXX) can receive damage. The types of damage are:
</p>
<ul>
<li>
<bold>Bloodloss</bold> - Wounds within the body part can cause
bloodloss, either external or internal.
</li>
<li>
<bold>Bone breakage</bold> - Some weapons will break bones.
</li>
<li>
<bold>Paralysis</bold> - Magic items and electrical damage
will cause body paralysis.
</li>
<li>
<bold>Severed</bold> - A body part can be severed.
</li>
<li>
<bold>Tissue damage</bold> - Cuts into the flesh and blunt-weapon
damage.
</li>
</ul>
<br/><section><p><bold><big>Bloodloss</big></bold></p></section>
<p>
To cause a wound that results in bloodloss,
call <bold>BodyPartBloodLoss()</bold> in the body part object.
Keep the bloodloss amount small; a number like 0.01 is a fairly
large wound since it means that the character loses 1% of his
blood every second, resulting in unconsiousness in 50 seconds,
and death in 100 seoconds.
</p>
<p>
There are two types of bloodloss, external and internal. Both
result in calls to <bold>DamageBloodLoss()</bold> every few
seconds. However, external bloodloss can be seen and can be
bandaged. Internal bloodloss is invisible and cannot be
healed by normal means; a character won't know he has internal
bleeding until he falls unconscious.
</p>
<p>
NOTE: The BodyPartXXX() functions for bloodloss, etc. are
not usually called directly. They're most often a result of
weapon damage, which is covered in the next tutorial.
</p>
<br/><section><p><bold><big>Bone breakage</big></bold></p></section>
<p>
Calling <bold>BodyPartBoneBreak()</bold> will break the major
bone in the body part, rendering it useless.
</p>
<p>
Bones will not heal until they're set by someone skilled in
setting bones. Then, they'll heal very slowly.
</p>
<br/><section><p><bold><big>Paralysis</big></bold></p></section>
<p>
Calling <bold>BodyPartParalysis()</bold> temporarily paralyzes
the body part. There are three types of paralysis: quick-recovery,
medium-recovery, and slow-recovery. An electrical shock
would cause quick-recovery paralysis, which disappears within
a few seconds. A sting from a jellyfish might cause slow-recovery
paralysis, taking a few minutes to completely recover.
</p>
<p>
If a character's head or chest is paralyzed, the character
may fall unconscious or even die.
</p>
<br/><section><p><bold><big>Severed</big></bold></p></section>
<p>
Calling <bold>BodyPartSevered()</bold> severs the body part.
Slashing weapons can cause a limb to be severed. If the head
or torso of a character is severed, the character dies.
</p>
<br/><section><p><bold><big>Tissue damage</big></bold></p></section>
<p>
The <bold>BodyPartTissueDamage()</bold> causes the body part's
muscles to be damaged. (Or in the case of the head, the character's
brain.) Enough damage and the body part will be useless until
it heals. Significant tissue damage to the character's head or
chest will knock it unconscious or kill it.
</p>
<br/><section><p><bold><big>Extensions to the cBodyPart class</big></bold></p></section>
<p>
The cBodyPart class includes some extra properties and methods
to handle body part damage. These are:
</p>
<ul>
<li>
<bold>pArmorXXX</bold> is a collection of armor values indicating
how well the body part is self-armored against various sorts
of damage. Humans have armor in their head (the skull) and torso
(the rib cage). Some creatures, such as armadillos, have armor
all around.
</li>
<li>
<bold>pBodyPartDamgeImpaleCriticalHit</bold> is the chance,
from 0.0 to 1.0, that a signficantly deep impaling hit will
instantly kill the character. In humans, both the head and
chest suffer this weakness, from hits to the brain or heart.
</li>
<li>
Damage to a character is affected by the character's size (weight).
However, some body parts are relatively smaller or larger
than other body parts, and take more or less
damage. <bold>pBodyPartDamageScale</bold> controls this; for
a human, all the limbs are 1.0, while the head is 1.5 (since it's
smaller), and the torso is 0.5 (since it's larger).
</li>
<li>
<bold>pBodyPartDamageSlashCriticalHit</bold> is the chance that
a sufficiently deep slash will cause a critical hit and kill
the character instantly. In humans, the head and torso have
a slight chance of instant death from a slash.
</li>
<li>
<bold>pBodyPartIsHand</bold> should be set to true if the
body part includes a hand or other gripping mechanism.
</li>
<li>
<bold>pBodyPartIsLeg</bold> should be set to true if the body
part acts like a leg. Significant damage to a leg could cause
the character to be knocked to the ground.
</li>
<li>
A value of true for <bold>pBodyPartIsLifeCritical</bold> will
cause the character to fall unconscious or die if enough
damage is done to the body part.
</li>
</ul>
<p>
Each body part object also suppots
the <bold>BodyPartDamage()</bold> method. This interprets
how damage from different types of weapons (such as slashing or
impaling) affect the body part. The default code should work
well enough for most creatures, but you may wish to rewrite
this for particularly unusual creatures.
</p>
<p>
As a general rule, you won't call BodyPartDamage() directly
either. It is called, however, by existing methods when a
character receives a hit from a weapon. This will be
described in the next tutorial.
</p>
<br/><section><p><bold><big>Extensions to the cCharacter class</big></bold></p></section>
<p>
The cCharacter class includes many properties and methods
to handle the damage model. Many of them have already
been described above. There are a few others of note:
</p>
<ul>
<li>
<bold>pBodyPartsInjuries</bold> is where the body-part specific
injury lists are stored.
</li>
<li>
If <bold>pDamageCanBeAttacked</bold> is set to TRUE, the character
can be attacked. If it's FALSE, it can't be; you may wish
to set this to FALSE for NPCs whose purpose is conversation or
some other non-combative role.
</li>
</ul>
Describes how to create armor objects and how armor works.
<section><p><bold><big>Armor</big></bold></p></section>
<p>
In Circumreality, armor is an equippable object which, when worn,
provides protection. Armor includes objects like chainmail
shirts, greaves, and helmets. Armor does <bold>not</bold> include
shields; these are considered to be weapons that parry.
</p>
<br/><section><p><bold><big>Creating an armor object/class</big></bold></p></section>
<p>
To create a new piece of armor, such as a chainmail shirt:
</p>
<ol>
<li>
<bold>Create a new object/class</bold> as usual.
</li>
<li>
Assign the <bold>cArmor</bold> superclass to it.
</li>
<li>
Fill in <bold>pNLPNounName</bold> and <bold>pNLPParseName</bold>,
as is typical for all objects.
</li>
<li>
Because armor is usually heavy and bulky, make sure
to fill in <bold>pWeightSelf</bold> and <bold>pVolumeSelf</bold>.
</li>
<li>
Since armor usually only fits certain-sized people,
make sure to fill
in <bold>pEquipWeightMax</bold>, <bold>pEquipWeightMin</bold>, and
maybe <bold>pEquipShape</bold>.
</li>
<li>
Set <bold>pEquipLoc</bold> to the armor's location,
such as <bold>[oBodyPartLocTorso, oBodyPartLocArmLeft,
oBodyPartLocArmoRight]</bold> for a chainmail shirt.
</li>
<li>
You need to specify how much damage the armor will absorb
by setting various <bold>pArmorXXX</bold> properties,
where XXX is
substituted. <bold>pArmorBlunt</bold>, <bold>pArmorImpale</bold>,
and <bold>pArmorSlash</bold> are the most common ones, but
other damage types exist.
</li>
<li>
You may also with to set <bold>pArmorAllBodyParts</bold> to
TRUE if the armor is a magical medallion that protects the
entire body, instead of just where it's worn.
</li>
<li>
<bold>pArmorDamageAbsorb</bold> affects how much damage the
armor can absorb before it's useless.
</li>
<li>
Whenever armor is hit, it will transfer some damage "energy"
into pushing damage that may knock the character over, even
if the character isn't hurt. You may want to
create magic armor that reduces the chances of
knockdown due to this effect by
setting <bold>pArmorPushScale</bold>.
</li>
</ol>
<br/><section><p><bold><big>Armor gotchas</big></bold></p></section>
<p>
Here are some things to watch out for with armor:
</p>
<ul>
<li>
Don't forget the <bold>pEquipWeightMin</bold> and <bold>pEquipWeightMax</bold>,
because characters will be different sized.
</li>
<li>
The armor's <bold>pWeightSelf</bold> and <bold>pVolumeSelf</bold> will
be higher/lower for larger/smaller characters.
</li>
<li>
The <bold>damage absorbed</bold> will be greater/smaller for larger/smaller
suits of armor.
</li>
<li>
Likewise, the amount of <bold>pArmorDamageAbsorb</bold> will
vary with the size of the armor.
</li>
<li>
You may wish to <bold>create a class</bold> for each type of armor, such
as cArmorChainShirt, which looks
at <bold>pEquipWeightSelf</bold>, and automatically calculated
the above settings. That way, creating chainmail armor for halflings
vs. armor for humans is only a matter of chaning one parameter,
not four or five.
</li>
<li>
You could go one step further and include some sort of
steel-quality property that makes the armor more protective
or longer-lasting.
</li>
<li>
If you have non-human races that can't wear human-shaped
armor you'll need to set <bold>pEquipShape</bold>.
</li>
</ul>
<br/><section><p><bold><big>Armor on characters</big></bold></p></section>
<p>
When a character is successfully attacked,
the <bold>DamageArmor()</bold> call will eventually be
called. (Usually after
the <bold>DamageDoge()</bold> and <bold>DamageParry()</bold> calls.)
This function looks through all the armor the character is wearing
and reducing the damage accordingly. It, in turn,
calls <bold>DamageNoArmor()</bold>, which then inflicts the damage
onto the character's body parts.
</p>
Information about how to create your own weapon and shield objects.
<section><p><bold><big>Weapons and shields</big></bold></p></section>
<p>
Weapons and shields are equippable objects. When the character
attacks a weapon will be chosen to use. The character can
also parry with a weapon and shield.
</p>
<br/><section><p><bold><big>Creating a weapon object/class</big></bold></p></section>
<p>
To create a new weapon, such as a sword:
</p>
<ol>
<li>
<bold>Create a new object/class</bold> as usual.
</li>
<li>
Assign the <bold>cWeapon</bold> superclass to it.
</li>
<li>
Fill in <bold>pNLPNounName</bold> and <bold>pNLPParseName</bold>,
as is typical for all objects.
</li>
<li>
Make sure
to fill in <bold>pWeightSelf</bold> and <bold>pVolumeSelf</bold>.
</li>
<li>
Since weapons can only be wielded by people large enough (halflings
don't do well with longswords), make sure to
set <bold>pEquipWeightMax</bold>, <bold>pEquipWeightMin</bold>, and
maybe <bold>pEquipShape</bold>.
</li>
<li>
By default, a weapon's <bold>pEquipLoc</bold> is set
to be single-handed. You can change this for two-handed weapons.
</li>
<li>
You need to specify how much damage the weapon will do
by setting <bold>pWeaponAttackDamage</bold>. This is list
of the types of attacks that a weapon can do. For example: A
sword can "swing" or "stab", swinging doing slashing damage
and stabbing doing impaling. For each attack type, you need
to supply the damage lists, with the carrier, non-carrier,
and extra damage. For a complete description see the
documentation for pWeaponAttackDamage.
</li>
<p>
If one of the damage types is "throw" then the weapon can
be thrown, like a dagger or an axe. A thrown weapon can't be
parried (except by a shield), but it must be picked up by
the attacker.
</p>
<li>
You'll also need to set <bold>pWeaponHitSuccessDamage</bold> to
control how much damage is done by barely-made hits. For swords,
this number would be relatively high, such as 2.0, since a
barely-made hit results in a slight gash, and only when a solid
hit results is the full damage done. For arrows, a barely-made
hit is nearly as good as a solid hit, so the value would be 0.5.
</li>
<li>
Set <bold>pWeaponAttackTime</bold> to the number of seconds
(optimum) between swings. Daggers will have fast attack
times, of 4 or 5 seconds, while two handed-swords will be
10-15 seconds.
</li>
<li>
<bold>pWeaponLength</bold> specifies the length of the weapon.
It allows characters with long weapons to attack taller characters,
like giants.
</li>
<li>
If it's a missle weapon, set <bold>pWeaponIsMissile</bold>. Only
shields can parry missle weapons.
</li>
<li>
<bold>pWeaponParry</bold> is the ability for the weapon to parry.
Use 0.0 for long swords. Daggers will have a parry closer to -1 or -2,
while shields will have a parry ability around 1.0.
If the weapon cannot parry then set this to NULL.
</li>
<li>
Set <bold>pWeaponSkillDamage</bold> to the skill (probably
the strength attribute) that allows the weapon to do more damage.
</li>
<li>
<bold>pWeaponSkillToHit</bold> should be filled with information
about the weapon's skill, such as [oSkillSwordsmanship, 0.3]. The
second entry in the list affects how many standard deviations
are added to the character's chance of hitting, per skill level.
</li>
<li>
If you want your weapon to be more accurate (or less accurate)
than normal, then set <bold>pWeaponAccuracy</bold>.
</li>
<li>
If your weapon wears out every time its used, wether or not
it hits, such as a bow, then
set <bold>pWeaponDamagedPerUse</bold> to a number like
0.001, which allows 1000 uses out of it.
</li>
</ol>
<br/><section><p><bold><big>Bows and arrows</big></bold></p></section>
<p>
Bows and arrows (or guns and bullets) are a bit trickier since the
bow is the weapon, but the arrows (or bullets) are used up for
each attack.
</p>
<p>
To create a bow:
</p>
<ol>
<li>
Base your object on <bold>cBow</bold> instead
of cWeapon; cBow is, in turn, a sub-class of cWeapon.
</li>
<li>
Set all the <bold>properties</bold> that you would for a weapon.
</li>
<li>
Set <bold>pBowShape</bold> to a "shape" for the bow; only
arrows of the same shape as the bow can be used. For
example: You might use "longbow", "crossbow" or "10mm shell".
</li>
<li>
The cBow class assumes that the bow uses 1 arrow per shot.
However, if the "bow" is a squirt gun, and uses 0.1 liters
of water per shot, then set <bold>pBowUnits</bold> to 0.1.
</li>
<li>
If the bow stores its "arrows" internally, such as a gun,
which contains bullets in a cartridge, then you should
also base the bow off of <bold>cContainer</bold> and
set <bold>pBowArrowLoc</bold> to TRUE so the bow code knows
to look <bold>within</bold> the bow for arrows.
Setting pBowArrowLoc to FALSE, or leaving it blank, will
cause the bow to look for arrows held by the character.
</li>
</ol>
<p>
You must also create arrow objects:
</p>
<ol>
<li>
An arrow object is a subclass of <bold>cArrow</bold>.
</li>
<li>
Fill in <bold>pNLPNounName, pNLPParseName,
pVisual, and pExamineGeneral</bold> as
usual.
</li>
<li>
Make sure that <bold>pArrowShape</bold> is set to the same
string as <bold>pBowShape</bold> in the bow.
</li>
<li>
Arrow objects are ultimately collections. Therefore, you
need to set <bold>pCollectionType</bold> so that arrows of
the same type can be combined together.
</li>
<li>
Likewise, set <bold>pQuantity</bold> to the number of arrows
in the collection.
</li>
</ol>
<br/><section><p><bold><big>Shields</big></bold></p></section>
<p>
The cShield class, used for shields, is based off cWeapon.
The only difference is that cSheild sets some properties
appropriate for shields, such
as <bold>pWeaponParryMissile</bold> so the shield can parry
missile attacks, <bold>pWeaponParryAlways</bold> so
the shiled is always parrying when
equipped, <bold>pEquipOffhand</bold> so it's equipped in
the left hand,
and <bold>pWeaponSkillToHit</bold> to oSkillShield.
</p>
<br/><section><p><bold><big>Weapon gotchas</big></bold></p></section>
<p>
Here are some things to watch out for with weapons:
</p>
<ul>
<li>
Don't forget the <bold>pEquipWeightMin</bold> and <bold>pEquipWeightMax</bold>,
because characters will be different sized.
</li>
<li>
You may wish to <bold>create a class</bold> for each type of weapon, such
as cWeaponSword, which looks
at <bold>pEquipWeightSelf</bold>, and automatically calculates
the above settings and the damage.
That way, creating sword for halflings
vs. a sword for humans is only a matter of changing one parameter,
not four or five.
</li>
<li>
You could go one step further and include some sort of
steel-quality property that makes the sword do more damage
or longer-lasting.
</li>
<li>
If you have non-human races that can't wield human
weapons you'll need to set <bold>pEquipShape</bold>.
</li>
</ul>
<br/><section><p><bold><big>Where weapons are invoked</big></bold></p></section>
<p>
When a character is attacked, the <bold>DamageParry()</bold> call
will be made. If the character can parry, it will be done
here. If the parry fails, then <bold>DamageDodge()</bold> is called.
</p>
Describes the basics behind MIF's combat design.
<section><p><bold><big>Combat</big></bold></p></section>
<p>
Most MUDs and virtual worlds have a turn-based combat system
where each combatatant gets one attack evern 2-5 seconds.
Because there aren't any choices for a player (or NPC) to make,
players can press an "auto-attack" button and bash on their
opponent until either the opponent dies, or the player decides
to run away.
</p>
<p>
Circumreality's combat is entirely different...
</p>
<ul>
<li>
An attack only occurs <bold>when the player presses the attack button</bold> or
types in "attack ENEMY".
</li>
<li>
A player can attack once every second if they wish, except that
their <bold>attacks become ineffective when done that rapidly</bold>.
This is handled by the <bold>pWeaponAttackTime</bold> parameter.
Attacking too quickly will significantly decrease the chance of hitting and
reduce the attacker's damage. Attacking longer will slightly
increase the hit chance and damage, but at the expense of attacks.
The <bold>ActionEffectiveness()</bold> call is made to determine
how effective the attack it.
</li>
<li>
Each attack also <bold>fatigues</bold> the attacker in
a significant way, so a player wants to make their attacks count.
A call to <bold>ActionFatigue()</bold> takes care of this.
Note that it fatigues characters holding a lot of weight more
than those lightly encumbered.
</li>
<li>
However, if an attacker does not attack quickly enough, the
defender will get in a counterattack. This will cause
the attacker to <bold>parry or dodge</bold> causing
a distraction, that then distrupts the attacker's attack.
The function to disrupt a character
is <bold>ActionDistraction()</bold>.
</li>
<li>
Since parrying and dodging both use fatigue, and end up
distracting the attacker, a character can
control <bold>how much parrying and dodging</bold> their character does.
If they have both on full, they aren't likely to get hit,
but they won't be able to effectively attack, and they'll
tire quickly. Conversely, a character in a full suit of armor
can turn both off and just attack without worrying about
the enemy's attacks getting through his armor.
</li>
<li>
The attacker can also decide where to <bold>aim</bold>:
high, middle, or low, and left, center, or right. If the
attacker hits an undefended spot their chance of hitting
increases.
</li>
<li>
Aiming also (obviously) influences what body
part is hit, allowing the attacker to use <bold>different strategies</bold>,
like taking out the defender's legs, etc.
</li>
<li>
Likewise, the defender can <bold>defend</bold> a location
of their body.
</li>
<li>
<bold>Different weapons do different types of damage</bold>: Swords
causes bleeding, maces cause tissue and bone damage, etc.
</li>
<li>
Some weapons have <bold>several attack types</bold>, such as
swords being able to slash and stab, each producing a different
type of damage.
</li>
<li>
Some <bold>armor provides better protection</bold> against some types
of attack. For example: Chainmail is great against slashing
damage, but lousy against clubs or impaling.
</li>
<li>
Characters can sustain <bold>several types of damage</bold>, including
broken bones, severed limbs, tissue damage, and bleeding.
Bleeding is very different from a traditional game because
a wound will continue bleeding after it has been created, so
a character hit by major sword wound won't fall unconscious right
away, but may take 30 seconds before bloodloss overcomes him.
</li>
</ul>
<br/><section><p><bold><big>Attack function calls</big></bold></p></section>
<p>
When an attack is made, the following method calls are made:
</p>
<ol>
<li>
Attacker.<bold>CombatAttack()</bold> to initiate the attack.
</li>
<li>
Attacker.<bold>CombatWeaponPrefGet()</bold> and
Attacker.<bold>CombatWeaponPrefSet()</bold> to get and set the
weapon that the attacker uses for combat.
</li>
<li>
Defender.<bold>DamageParry()</bold> to allow the defender to
parry the attack.
</li>
<li>
Defender.<bold>DamageDodge()</bold> so the defender can dodge
the attack.
</li>
<li>
Defender.<bold>DamageArmor()</bold> is then called, letting
the defender's armor absorb some of the damage.
</li>
<li>
Defender.<bold>DamageNoArmor()</bold> is finally called with
the damage that reaches the defender's flesh.
</li>
<li>
cBodyPart.<bold>BodyPartDamage()</bold> receives the call
with the damage that's specific to the body part.
</li>
<li>
In turn, it calls <bold>BodyPartBloodLoss(),
BodyPartBoneBreak(), BodyPartParalysis(), BodyPartSevered(),
and BodyPartTissueDamage()</bold>.
</li>
<li>
These may make calls back into the cCharacter,
including <bold>DamageBloodLoss(), DamageDazed(),
DamageDeath(), DamageDisease(), DamageDodge(),
DamageDropHeld(), DamageFatigue(), DamageKnockDown(),
DamageMagicEffect(), DamageMana(), DamageUnconscious(),
and DamageVenom()</bold>.
</li>
</ol>
Describes how players are informed about the results of an attack.
<section><p><bold><big>Combat narration</big></bold></p></section>
<p>
Most MUDs display the results of combat in a predictable and
fairly boring manner: "X hits Y for N damage." "Y dies."
Unforuntately, this system does not work well for Circumreality because
a) it's uninteresting, and b) Circumreality's combat is more complex
than a typical MUD's.
</p>
<p>
If Circumreality were to take the same apporach then a typical attack
would produce a string like, "X attacks Y. Y defends high. X attacks
middle. Y's parry fails. Y's dodge fails. X hits Y's legs.
Y's leg breaks." This is a) uninteresting, and b) too longwinded.
</p>
<p>
To combat this problem (pun intended), Circumreality first stores
all of the combat information into a combat transcription (a
cCombatTranscript) object, without speaking it out.
It then passes the transcript object to CombatNarrate(), which
investigages the transcript and finds a good way of wrapping
up all the information into an interesting sentence, such
as "X swings at Y's feet, avoiding Y's parry, hits Y's
leg with a bone-crunching sound."
</p>
<p>
For the most part, you don't need to worry about the specifics
of the implimentation, unless you wish to add your own descriptions.
For example: If you invented a special magical sword, you could
have any attacks with it produce special descriptions. You could
also write custom descriptions for monster attacks... "The octopuses
tentacles grab X and squeeze."
</p>
<br/><section><p><bold><big>cCombatTranscript</big></bold></p></section>
<p>
If you wish to write your own descriptions, the first thing
you need to understand is how cCombatTranscript works.
</p>
<p>
When an attack is initiated, a cCombatTranscript object is
created. This object has no methods or properties, and is just
a means of storing information. The object is passed through
to all the combat functions and methods. If they have anything
to "say", they will add a property to cCombatTranscript with
the information instead of calling SpeakNarratorAll().
</p>
<p>
Below are some of the basic properties that will be added.
For a complete list, look at the cCombatTranscript defintion
or help, or search through the code for "Transcript." references.
</p>
<ul>
<li>
<bold>pCombatLastAttacked</bold> - The character that was attacked.
</li>
<li>
<bold>pCombatLastAttacker</bold> - The character doing the attacker.
</li>
<li>
<bold>pCombatTranscriptType</bold> - This is "parry" if the
defender parries, "dodge" if the defender dodges, or "hit"
it the attack gets through.
</li>
<li>
<bold>pCombatDodge</bold> - If the defender dodges, this
is the how well the dodge went (in standard deviations). Thus,
0.1 is barely dodged, while 2.0 is easily dodged.
</li>
<li>
<bold>pCombatParry</bold> - If the defender parries, this
is the how well the parry went (in standard deviations). Thus,
0.1 is barely parries, while 2.0 is easily parried.
</li>
<li>
<bold>pCombatTranscriptArmorAbsorb</bold> - If the defender's
armor absorbs all the damage, this is the piece of armor that
does it.
</li>
<li>
<bold>pCombatTranscriptBoneBreak</bold> - If the defender's
bone breaks as a result of the attack, this is the body
part that breaks.
</li>
<li>
<bold>pCombatTranscriptSevered</bold> - Like bone break,
except this indicates that the limb was severed.
</li>
<li>
<bold>pDamaged</bold> - The severity of the wounds inflicted on the
defender. 4 for very large wounds, 3 for large, 2 for medium, 1 for minor,
and left NULL/Undefined/0 for mere flesh wounds.
</li>
<li>
<bold>pDamageDazed</bold> - Set to a number of the attack dazed the defender.
</li>
<li>
<bold>pDamageDeath</bold> - Set to a number of the attack killed the defender.
</li>
<li>
<bold>pDamageKnockDown</bold> - Set to a number of the defender was knocked down.
</li>
<li>
<bold>pDamageUnconscious</bold> - Set to a number of the defender was
knocked unconscious.
</li>
<li>
<bold>pEquip</bold> - A list of equipment dropped by the defender,
usually as a result of blows to arms.
</li>
</ul>
<br/><section><p><bold><big>CombatNarrate()</big></bold></p></section>
<p>
When an attack is finished and all its information is wrapped
into a cCombatTranscript object, a call is made to
CombatNarrate().
</p>
<p>
CombatNarrate() first finds all the player characters in the
room and determines what level of detail they wish to
hear about. Usually, players will request a high level of detail
for attacks they make and enemies attacking them, while a lower
level of detail for combat ocurring with their friends.
The levels are 0 for brief, 1 for short, 2 for normal, and 3
for detailed.
</p>
<p>
Next, CombatNarrate() finds all the narration objects
in the system. These are based on <bold>cCombatNarrator</bold>, whose
constructor automatically adds the object to gCombatNarrators.
</p>
<p>
Each narrator object is passed in the transcript object and asked
how relevent the narrator is to what just happened. (The method
call is <bold>CombatNarratorWeight()</bold>. Most narrators
will return a value of 1.0. However, if the narrator is designed
for a special purpose, like attacks from giant octopi, then
it tests the properies in cCombatTranscript. If the attack matches
what the narrator is designed to describe, it will return a high
value, like 10. If the narrator isn't designed for the type
of attack, such as the octopus-narrator not handle sword attacks,
then it will return 0.
</p>
<p>
CombatNarrate() selects a narrator at random, taking into account
the narrator's weight. It calls the
the narrator's <bold>CombatNarratorNarrate()</bold> method.
If the narrator can produce a decent narration for the event then
it generates a string and sends it to SpeakNarratorList().
If the narrator is unsuccessful, it returns FALSE and the
CombatNarrate() function tries another narrator. Eventually,
a narrator will be found that works, and the process stops.
</p>
<br/><section><p><bold><big>cCombatNarrator</big></bold></p></section>
<p>
If you wish to write your own narrations,
you need to <bold>create an object based on
cCombatNarrator.</bold> I don't need to describe this in detail.
Make sure that the narrator object is created as an object (as
opposed to a class.)
</p>
<p>
Each narrator object will have 10 to 1000 possible ways of
"<bold>phrasing</bold>" the combat transcript. For example: "X attacks Y"
could be phrased as "Y is attacked by X", "X hits Y",
"X attacks Y with a sword", etc.
Some phrasings will only be applicable to parries, dodges,
or different attack types though.
</p>
<p>
To create one of the phrases, <bold>add a private
method</bold> to your new narrator object. The private method
must accept a transcript object, list of actors who are to
receive the narration, and a detail level. It should return
a score, which indicates how accurately it was able to
narrate the transcript. The easiest way to do this
is return the value from CombatNarratorTieLooseEnds(),
which calculates the score based on the properites used,
as well as penalties for missed events. You might add a few
extra points for an exceptionally good narration. If the phrasing
code doesn't have any appropriate transcript it should return NULL.
</p>
<p>
To see an example of narration code, look in oCombatNarratorBasic.
You'll notice quite a few private methods, prefixed with "Short_",
"Normal_", or "Detailed_". The "Short_" ones are used for
a detail level of 0 or 1. The "Normal_" ones are used for
a detail level of 2, while "Detailed_" is used for level 3.
</p>
<p>
Some things to notice about the phrasing code:
</p>
<ul>
<li>
The method first checks to make sure that it can describe
the information in the transcription.
A method designed to describe succesful parries would return
NULL if it were passed a "dodge" or "hit" value
in pCombatTranscriptType.
</li>
<li>
Detail level 0 (brief) and 1 (short) often use the same
methods. They are virtually identical, except
that level 0 won't display anything if the attack is parried
or dodged.
</li>
<li>
<bold>CombatNarratorTieLooseEnds()</bold> is called at the
end of every phrasing method. This will speak out important
information that was "missed" by the narrator code. For example:
An attack might wound a defender's arm so badly that the defender
drops his weapon. Since this is an unusual event, the narrator
code may not pick up on it. The call to CombatNarratorTieLooseEnds()
will speak the inforamtion though.
</li>
<p>
To prevent CombatNarratorTieLooseEnds() from reiterating what
the method just spoke, make sure to keep a record of the
properties that were narrated and pass them to
CombatNarratorTieLooseEnds().
Thus, if the phrasing method told the players "Fred's mighty
blow slays the orc.", ["pdamagedeath", "pcombattranscriptweapon"]
would be passed. If you don't,
CombatNarratorTieLooseEnds() will speak of "The orc dies."
</p>
<li>
If the phrasing method is just being queried for a score,
the ActorsList is NULL.
</li>
</ul>
<p>
You need to write a <bold>CombatNarratorPhrasings()</bold> method
that returns a list of private methods that are appropriate for
each detail level. oCombatNarratorBasic returns [Short_Parry,
Short_Dodge, Short_Hit] for detail level 0 or 1. Higher levels of
detail have many more ways to phrase a transcript.
</p>
<p>
You may also wish to provide
a <bold>CombatNarratorWeight()</bold> method so your narrator
object is only used for certain types of attacks, or with
specific weapons or monsters.
</p>
<p>
That's it... Of course, you'll probably write a few hundred
different phrasings, which can add up to a fair amount of
work.
</p>
<br/><section><p><bold><big>Sound effects</big></bold></p></section>
<p>
Sound effects for combat are also generated using the transcript
object:
</p>
<ol>
<li>
<bold>CombatSoundEffectPlay()</bold> is passed a transcript
object.
</li>
<li>
It calls the <bold>CombatSoundEffect()</bold> method for the
attacker, defender, the attacking weapon, parrying weapon,
armor, and the body part attacked.
</li>
<li>
Each <bold>CombatSoundEffect()</bold> object fills in a list of
the sound effects it thinks should be used and returns that.
For the most part, the methods just
call <bold>CombatSoundEffectImpact()</bold> to determine the
impact sound of weapon against armor or body part.
</li>
<li>
<bold>CombatSoundEffectImpact()</bold> calls into the weapon
and whatever object it hit. It uses
the <bold>CombatSoundEffectMaterial()</bold> method
to determine if the weapon is metalic, wood, etc., and also
what the armor (or object that is hit) is like. Both
of these value are used to pick a wave file to play, such
as a sword clanking against steel armor, or sword against
chainmail.
</li>
<li>
<bold>CombatSoundEffectPlay()</bold> wraps up all the
returned sounds and plays them on all the nearby-players computers.
</li>
</ol>
Enabling player vs. player combat.
<section><p><bold><big>Player vs. player combat</big></bold></p></section>
<p>
Whenever a player character (or NPC) attacks or thinks about
attacking, the function, <bold>CanAttackFunc()</bold> is called.
This returns TRUE if the character can attack another,
or FALSE if combat isn't allowed.
</p>
<p>
CanAttackFunc() works by calling Attacker.CanAttack(),
Defender.CanAttack(), and Room.CanAttack(). All the values
are summed up (with the room's score being doubled). If
the sum is zero or more then <bold>an attack is allowed</bold>.
Otherwise, it isn't.
</p>
<p>
PCs use the following rules in their CanAttack() method:
</p>
<ul>
<li>
If a PC tries to attack another <bold>member of his party</bold>,
-100 is added, ensuring that party members can't be attacked.
</li>
<li>
If the enemy is an <bold>NPC</bold> then +10 are added, allowing
NPCs to be attacked by default.
</li>
<li>
If the enemy (or a member of his party) has <bold>recently attacked the PC
or the PC's party</bold>, then +10 points are added. The recently-attacked
information is stored in <bold>pCombatLastAttackerList</bold>.
</li>
</ul>
<p>
NPCs use the following rules:
</p>
<ul>
<li>
If the NPC has <bold>pDamageCanBeAttacked</bold> set to FALSE, then
-100 is added to the score, ensuring the NPC can't be attacked.
</li>
<li>
If the enemy (or a member of his party)
has <bold>recently attacked the NPC</bold>, then +10 points are added.
</li>
</ul>
<p>
Rooms use the following rules:
</p>
<ul>
<li>
If the room is a safe room, <bold>pRoomRessurectionLoc</bold> then
the room returns -100, ensuring that combat won't take place.
</li>
<li>
Otherwise, the room's <bold>pRoomCanAttack</bold> value is returned.
</li>
</ul>
<p>
To enable player vs. player combat, you can do one or more of the following:
</p>
<ol>
<li>
Set the room's <bold>pRoomCanAttack</bold> to +1.
</li>
<li>
Create your own <bold>CanAttack()</bold> method for PCs that returns
a high score (such as 10) when attacking each other. For example: You might allow
orc PCs to attack elf PCs, but orc PCs cannot attack other orc PCs.
Once one person has been attacked, they will be able to return the
attack because their <bold>pCombatLastAttackerList</bold> will
remember the attacker.
</li>
</ol>
How to make a room that characters will be resurrected into.
<section><p><bold><big>Resurrection</big></bold></p></section>
<p>
When a player character is killed, the character will be resurrected
in about minute. To ensure that they don't get resurrected into the
hands of their enemies, they are resurrected into the
last <bold>"safe room"</bold> they had entered. This
is stored in the character's <bold>pRoomRessurectionLoc</bold> property.
</p>
<p>
To create a safe room, set the
room's <bold>pRoomRessurectionLoc</bold> to TRUE. Whenever a PC enters
the room, their safe room will be set to the room. The player will
be notified of the new safe room too, assuming that their character's
safe room has actually changed.
</p>
Some objects can only be identified with the necessary skill.
<section><p><bold><big>Object identification</big></bold></p></section>
<p>
Australia has an ancient family of plants known as "cycads".
Cycads grow nuts on them that are poisonous. Most tourists don't
know this, but the locals do. How is this implimented in Circumreality?
</p>
<p>
Implimenting objects whose names change depending upon the
character's skills is easy:
</p>
<ol>
<li>
Instead of making
the <bold>pNLPNounName</bold> and <bold>pNLPParseName</bold> entries
strings, make them lists. The first element of the list is the
unskilled-name, such as "nut". The second entry is the name
that the object would be known by to a skilled viewer,
such as "poisonous nut". You can have more than two entry; in the
case of cycad nuts, the third entry might be "a poisonous cycad nut".
A fourth entry could contain the scientific name even.
</li>
<li>
You can also modify <bold>pNLPNounAnimate, pNLPNounCount,
pNLPNounGender, pNLPNounNoAutoArticle, pNLPNounNoAutoPlural,
pNLPNounNoAutoPossessive,
pNLPNounQuantity</bold>, and <bold>pNLPParseNameNoArticles</bold> to
be lists too, although they don't have to be. (Being lazy,
you'll usually leave the single values, and Circumreality will automatically
use the same value for every level of identification.)
</li>
<li>
Add a <bold>pNLPIdentifySkills</bold> property to the object. It
is a list that contains information about what skills can be
used to identify the object. There are multiple skills since,
for example, a botanist would know what a cycad nut was, as
well as someone with bush survival skills.
</li>
<p>
Each skill has a sub-list. The first element of the sub-list
is the skill object, followed by a number of (increasing) skill
levels that correspond to the levels of detail in the object's
name.
</p>
<p>
For example: A cCycadNut object might have a pNLPIdentifySkills
of [[oSkillBotany, 2, 4], [oSkillBushSurvival, 1, 2]].
Characters with a skill level of 2 in botany, or 1 in bush
survival would see the cycad nut described as "poisonous nut"
instead of just "nut". A skill of 4 in botany or 2 in bush
survival would see "poisonous cycad nut".
</p>
</ol>
How to create spawned NPCs with equipment and loot.
<section><p><bold><big>Loot</big></bold></p></section>
<p>
What good is a monster that isn't carrying some treasure?
What good is an evil knight that tries to kick you to death?
</p>
<p>
The Basic IF tutorial described how to have rooms automatically
spawn NPCs using <bold>pSpawnClass</bold>. Unforuntately, these
spawned creatures won't be carrying any treasure or weapons.
</p>
<p>
To make a spawned creature carry treasure or weapons:
</p>
<ol>
<li>
Add <bold>pLootEquip</bold> to the NPC's object. This
property allows you to control what weapons, armor, or other
standard equipment the NPC will be created with.
You can even randomly select the weapon or armor from a list,
so not all NPCs of the class carry the same weapon.
(The NPC's AI will automatically equip the weapon or armor
as soon as it's created.) For details about the syntax
for pLootEquip, see the property's help topic.
</li>
<li>
<bold>pLootCreate</bold> lets you specify what treasure
will be created on the NPC. Of course, the treasure selection
is random. It is controlled by a value you set so that
NPCs won't have too much treasure on them.
</li>
<li>
If you wish treasure to appear when the NPC dies, such
as a unicorn's horn being part of a unicorn's
loot, you can provide a <bold>pLootCreateOnDeath</bold>.
This acts just like pLootCreate except that the treasure is
only created when the creature dies.
(All NPCs support this, even those that <bold>aren't</bold> spawned
by rooms.)
</li>
</ol>
<br/><section><p><bold><big>Multiplayer loot appearing in rooms and containers</big></bold></p></section>
<p>
There are cases where you might want the player to find an object when they enter a room,
or when they open a container. For example: When a book is opened, a scrap of paper is inside
the book that provides a clue.
</p>
<p>
This is easy in a single-player game, since you just add an "oScrapOfPaper" and have it contained
within the book.
</p>
<p>
This won't work in a multiplayer game because only one player would ever get a chance to
take the paper. All the others would find an empty book.
</p>
<p>
There is a way to do (almost) the same thing in a multiplayer game:
</p>
<ol>
<li>
<bold>Create your room or container</bold> as usual.
</li>
<li>
If you create a container, you must set <bold>pImmobile</bold> to TRUE. (You have to do this
anyway in a multiplayer game since all take-able objects will either be taken by other
players, or they'll be automatically cleaned up by the invisible room cleaner.)
</li>
<p>
The container should have <bold>pOpen</bold> set to FALSE so it's closed. It should also
be set to automatically close itself after being opened, with <bold>pOpenAutoClose</bold> so
that if one player opens it, the object will automatically close and reset for the next player.
</p>
<li>
Set <bold>pLootMultiplayer</bold> in the room or container to "cScrapOfPaper", or whatever
class you have defined. You can also uses classes based on cMonster or cCharacter.
</li>
</ol>
<p>
That's it! The first time a player opens the book, a scrap of paper specifically for the
player will be created. It won't be created subsequent times because a note is made
in the character's <bold>pLootMultiplayerHistory</bold>.
</p>
How to make hidden objects, like secret doors.
<section><p><bold><big>Hidden objects</big></bold></p></section>
<p>
Making a hidden object is easy:
</p>
<ol>
<li>
Set the <bold>pHidden</bold> property of the object to TRUE.
</li>
<li>
To control how difficult the object is to find,
set the <bold>pSearchDifficulty</bold> property. This allows
you to set the base difficulty, which can then be affected
by skills (such as "Find hidden traps") that you might
specify.
</li>
<p>
Additonally, you can provide a bonus if the
players search in the right spot; "Search in the bottom of the chest"
might provide a bonus in finding a hidden panel
at the bottom, while "Search the lid of the chest"
would reduce the chance of finding the hidden panel.
</p>
<li>
You may wish to set <bold>pSearchDifficultyNoPerception</bold> if
the character's oSkillPerception skill <bold>won't</bold> have any
effect on searching.
</li>
<li>
If you wish to change the string displayed when the hidden
object is found the set <bold>pSearchFindString</bold>.
</li>
<li>
You may wish secret doors to re-hide themselves a few
minutes after they've been found.
Set <bold>pSearchAutoHide</bold> to do this,
as well as <bold>pSearchAutoHideString</bold> to change
the description.
</li>
<li>
You can change the string displayed while the player
searches the object from "%1 searches %2." to
"%1 rummages around the chest looking for something.", but
changing <bold>pSearchString</bold> in the chest object.
</li>
</ol>
<br/><section><p><bold><big>Spawned resources</big></bold></p></section>
<p>
Since having resources, like plants and metal ores, spawn is common,
the IF libraries contain special code for hidden resources. If
you use this, all the spawned resources will
be <bold>hidden</bold> by default.
</p>
<ol>
<li>
Set the room's <bold>pSpawnResourceClass</bold> to the number and
type of resources that are spawned in the room.
</li>
<li>
<bold>pSpawnResourceLocation</bold> is optionally set to the locations
over which the resource is spawned.
</li>
<li>
<bold>pSpawnResourceTime</bold> is optionally set to number of minutes
between resource spawning. This defaults to an average of 10 minutes.
See also <bold>pSpawnResourceTimeRange</bold> to limit the times.
</li>
<li>
<bold>pSpawnResourceSearchDifficulty</bold> controls how difficult
it is to find the resource, as well as what skills are needed.
If not defined, then the difficult defaults to 0.
</li>
<li>
<bold>pSpawnResourceSearchFindString</bold> describes the object being found.
This can be left blank.
</li>
</ol>
<br/><section><p><bold><big>Advanced hidden objects - Specific locations</big></bold></p></section>
<p>
When the player types in the search command, they can (optionally)
specify a location within the object to search. If a player
is specific about the location, you should increase their chance
of success if they state the right location, and decrease their
chances if they state the wrong one.
</p>
<p>
When the player types in their command, like "search the bottom
of the chest", this is parsed down to "`search the bottom of
CHESTOBJECT". The parser rules in rParserRPG also include
some rules for parsing "`search [*1] [the] bottom [*2]" to
"`search *1 `bottom *2". Thus, "search the bottom of
the chest" becomes "`search `bottom of CHESTOBJECT".
</p>
<p>
The Parse_Search() method in then discards the "of" word,
and assumes that "`bottom" describes where the player is searching.
The "`bottom" string is then passed into the search
functions, and eventually into SearchForThis(), where
the <bold>pSearchDifficulty</bold> property in the object
indicates how searching the "`bottom" affects the success rate.
See the docmentation of pSearchDifficulty for more information.
</p>
<p>
You will probably need to add new parse rules for "the lid",
"the sides", etc. of a chest, or whatever object you wish to
have searched. To do this, provide
a <bold>NLPRuleSetTempVerbs()</bold> property, or hardcode
the rules for all your objects into a global rule set.
</p>
<br/><section><p><bold><big>Advanced hidden objects - Replacing methods</big></bold></p></section>
<p>
You may also wish to replace some methods:
</p>
<ul>
<li>
<bold>SearchInsideThis()</bold> - If you add to this layered
function you can ensure that searching around a forest will
find random leaves and branches, searching around seashore
will produce seashells, etc. Adding a SearchInsideThis()
layer for trapped chests or doors will allow players to
find the traps before springing them.
</li>
<li>
<bold>SearchLookFor()</bold> - This is called when a specific
object is searched for. If you rewrite this code, you can
make objects that are hidden to some players while visible
to others. You'll also need to
replace <bold>IsHidden()</bold> and
maybe <bold>SearchAutoHide()</bold>.
</li>
</ul>
Gold pieces, silver pieces, copper pieces, and other currency.
<section><p><bold><big>Currency</big></bold></p></section>
<p>
The default fantasy library comes with three
currencies: <bold>cCurrencyGold, cCurrencySilver,
and cCurrencyCopper</bold>. 1 gold is worth 16 silver.
1 silver is worth 24 copper.
</p>
<br/><section><p><bold><big>Making your own currency</big></bold></p></section>
<p>
If you wish to make your own currency:
</p>
<ol>
<li>
Create a class, cCurrencyMyOwn, and
base it on <bold>cCurrency</bold>.
</li>
<li>
Set <bold>pNLPNounName, pNLPParseName, and
pExamineGeneral</bold> to describe the currency. You may wish
to take a look at cCurrencyGold's pNLPNounName and pNLPParseName
to ensure you handle all the parse/noun forms.
</li>
<li>
Set <bold>pVisual</bold> to control what the currency
looks like. Again, cCurrencyGold has a pVisual you may wish
to look at.
</li>
<li>
Set <bold>pCollectionType</bold> to a unique lower-case
string for the currency, such as "myown".
</li>
<li>
Modify <bold>gCurrencyList</bold> and add an entry for
your currency, which might look like ["myown", "cCurrencyMyOwn", 1010].
The 1010 is the value of the currency in terms inflation adjusted
units, <bold>before</bold> gCurrencyInflation is taken into account.
</li>
<p>
Make sure to place your currency entry in <bold>sorted order</bold>. Several
functions assume that gCurrencyList is sorted from the highest
value currencies to the lowest.
</p>
<p>
If you <bold>don't wish to support gold pieces</bold>, for example, then make sure
to remove that entry from gCurrencyList.
</p>
</ol>
<br/><section><p><bold><big>Currency functions</big></bold></p></section>
<p>
A number of handy currency functions are provided:
</p>
<ul>
<li>
<bold>CurrencyChargeActor()</bold> - This removes a given amount of
money from a character and provides change. Or, it gives money to
a character. It can also be used to provide change for a gold piece.
</li>
<li>
<bold>CurrencyCoinToString()</bold> - Creates a string that
can be displayed to the user, such as "2 gp, 3 sp" as in,
"That mace will cost you 2 gp, 3 sp."
</li>
<li>
<bold>CurrencyIAToCoin()</bold> - Converts from an inflation adjusted
value to a list of coins. It automatically handles rounding.
</li>
<li>
<bold>CurrencyOnActor()</bold> - Returns a list of all the currency
objects held by the actor, or in the actor's <bold>unlocked</bold> containers.
</li>
<li>
<bold>CurrencyOnActorToAI()</bold> - Accepts the
results from CurrencyOnActor(), and calculates how much money the actor
has in inflation adjusted units.
</li>
<li>
<bold>CurrencyValue()</bold> - Given a lower-case currency name, like "gold", this
returns the value in inflation adjusted units.
</li>
</ul>
Making items that can be repaired.
<section><p><bold><big>Repairable items</big></bold></p></section>
<p>
Items get damaged, stored in <bold>pDamaged</bold>. If
you wish to make items that can be repaired by player characters or
NPCs:
</p>
<ol>
<li>
Set <bold>pRepairSkill</bold> to indicate what skill is needed to
repair the item, along with the skill level.
</li>
<li>
Set <bold>pRepairTools</bold> to indicate the tools that must
be handy for the item to be repaired. For example: The object
might require a forge to be repaired.
</li>
<li>
Set <bold>pRepairMaterials</bold> to include a list of materials
that will be used up each time the item is repaired. The materials
should be collections, since the amount of material depends upon
how badly the item is damaged.
</li>
</ol>
<p>
Whenever a player or NPC repairs an item, it won't be 100% repaired.
Characters with low skills will do a shoddy job repairing the item,
while those with better skills will get closer to the 100% repaired
mark.
</p>
<p>
To account for this fact, <bold>pRepairBestCase</bold> will
be modified higher each time, so that items can only be repaired
so often before they're unrepairable.
</p>
How to create quests and quest objects.
<section><p><bold><big>Quests (cQuest)</big></bold></p></section>
<p>
Quests are a ubiquitous CRPG feature that is supported by Circumreality.
However, quests are handled slightly differently (and more powerfully)
in Circumreality than most CRPGs.
</p>
<p>
In Circumreality, a quest is really a hidden object that tags along in the
character's inventory. It receives all the PerceiveXXX() messages that
the character does, so it can monitor how many monsters were killed,
how many blueberries were picked, etc.
</p>
<p>
Unlike most CRPGs, Circumreality quests have a mind of their own. They are
their own object, and can do what they wish, including but not
limited to manipulating AIs. You can think of quests as "surrogate
dungeon masters" whose job is to tag along with the player and make
sure the game stays fun.
</p>
<br/><section><p><bold><big>Creating a quest</big></bold></p></section>
<p>
To create a quest, such as one in which the player must slay ten orcs:
</p>
<ol>
<li>
Create a <bold>new class</bold>, such as "cQuestKillOrcs".
</li>
<li>
Derive cQuestKillOrcs from <bold>cQuest</bold>.
</li>
<li>
Fill in <bold>pNLPNounName</bold> and <bold>pNLPParseName</bold> for
the name of the quest, "Bob wants you to slay 10 orcs".
</li>
<li>
Fill in <bold>pExamineGeneral</bold> with a description of the quest.
This description will be displayed when the player is offered the quest,
as well as when the player checks out his quest's status.
</li>
<li>
<bold>pQuestPlayers</bold> should be set the the recommended number
of players to complete the quest. If you don't set this, it
will default to 1, indicating that the quest is good for solo play.
</li>
<li>
<bold>pQuestTasks</bold> must be filled in with a list of tasks for
the player to complete.
</li>
<p>
A final task of "Return to END-NPC." is automatically included.
You can disable this task, or change the string, by
changing <bold>pQuestTasksReturnToEnder</bold>.
</p>
<li>
Set <bold>pQuestRewardEnum</bold> to the list of rewards offered to the
player when the complete the quest. They will be shown the rewards when
they accept the quest too. The <bold>default</bold> value for the
property is no reward except gratitude.
</li>
<p>
If any of your rewards are "special" rewards, that don't involve money
or objects, then you'll need to write
a <bold>QuestRewardSpecial()</bold> method.
</p>
<li>
You might wish to write your own <bold>QuestCanShare()</bold> method
to control whether players can share quests amongst themselves. The
default behavior allows sharing in most cases.
</li>
</ol>
<p>
You might also want to do the following:
</p>
<ul>
<li>
<bold>pQuestGiverSpeak</bold> controls what the guest giver says when
it hands out the quest.
</li>
<li>
<bold>pQuestGiverGive</bold> will cause the quest giver to hand
out items to the player, such as a letter that must be
delivered. <bold>pQuestGiverSpeakAfterGive</bold> has the NPC speak
a phrase after it has given the object(s) to the player.
</li>
<li>
You may wish to set <bold>pQuestEnder</bold> to the NPC that the player
needs to talk to in order to complete the quest. If you leave this
as Undefined, then the player will need to return to the quest giver.
If this is NULL, the player doesn't need to talk to anyone, and can
complete the quest anywhere.
</li>
<li>
<bold>pQuestEnderContextMenu</bold> is a string that's displayed in
the pQuestEnder NPC's context menu that allows the player to finish
the quest. The defaults to something like, "I'm done with the quest".
</li>
<p>
The NPC's reply is controlled by <bold>pQuestEnderSpeakOnCompleted</bold>.
It has a default.
</p>
<li>
If the quest ender requies a set of objects from the player in order
to complete the quests, then fill the objects
in <bold>pQuestEnderTake</bold>. You may also wish to
fill <bold>pQuestEnderSpeakBeforeTake</bold> with a phrase that the NPC
speaks before it takes the objects, like "I'll be taking your foozle skins now."
</li>
<li>
If the player completes the quest, starts talking to the pQuestEnder NPC,
but doesn't mention pQuestEnderContextMenu, then the NPC will eventually
bring up the fact that the player has completed
the quest. Set <bold>pQuestEnterQuestionOnCompleted</bold> to the string.
It defaults to something like, "So you completed the quest! What reward would
you like?"
</li>
<li>
When the player finally selects a reward, the quest is completed.
The NPC will then speak <bold>pQuestSpeakOnCompletion</bold> with
something like, "Thank you for completing the quest."
</li>
<li>
To have the quest ender automatically offer another quest,
set <bold>pQuestEnderOfferMoreQuests</bold> to TRUE, which is the
default.
</li>
<li>
If you want your quest to be hidden from the player,
set <bold>pIsInvisible</bold> to TRUE. You wouldn't normally have an
invisible quest since then players couldn't cancel it. However, if
the quest is acting like an invisible dungeon master, this might
be a good idea.
</li>
<li>
Unlike most CRPGs, there are consequences to a player cancelling
a quest. The most common one is that the quest giver will dislike
and/or distrust the player for cancelling the quest.
Adjust <bold>pQuestCancelledLike</bold> to control how much the
quest giver's opinion of the player character is affected if the
player cancels the quest.
</li>
<li>
Change <bold>pQuestCancelCoolDown</bold> to control how long
the player must wait before they can re-accept a cancelled quest.
</li>
<li>
<bold>pQuestCompletedSpeak</bold> is spoken by the narrator when the
quest is finished. If left blank, a default phrase is spoken.
</li>
<li>
Conversely, <bold>pQuestCompletedLike</bold> increases the quest
giver's like and trust when the quest is completed.
</li>
<li>
<bold>pQuestCompletedReputation</bold> affects the PC's reputation
when the quest is completed.
</li>
<li>
By default, all quests will identify all "involved" NPCs to the
player's characters so that when the player sees the NPC, he
will have a name associated with it. This makes life easier for
the player since they don't have to wander around the world
asking NPCs their names. The NPCs are gotten from pQuestGiver,
pQuestEnder, and any "MapToObject" listed in QuestTasks(). If
you don't wish to automatically identify NPCs then
set <bold>pQuestAutoIdentify</bold> to FALSE.
</li>
<li>
If you provide <bold>pQuestKnowledgeAnecdote</bold> then the player
can brag about his exploits on the quest to any NPC he may meet.
This bit of knowledge can be used by the player when trading stories
and rumors with NPCs.
</li>
<li>
Setting <bold>pQuestFracture</bold> will cause
PlayerCharacter.FractureAdd (pQuestFracture) to be called if
the player completes the quest. Thus, a quest to slay the orcs
will result in a happy village without any fires burning.
</li>
<li>
<bold>pQuestAutoComplete</bold> lets the quest automatically
end without having to see a NPC.
</li>
<li>
If your quest object has properties that it needs to initialize
when it's first created and assigned to a player, then
write your own <bold>QuestInit()</bold>.
</li>
<li>
If you quest needs special clean-up, then write your
own <bold>QuestEnd()</bold>.
</li>
<li>
You might write your own <bold>QuestTasks()</bold> method. This returns
a list of all the tasks that the player must complete in the quest,
as well as a percent-done for each task. The tasks are displayed
in the quest information page. The default code looks at
pQuestTasks and derives a list.
</li>
<li>
If you use pQuestTasks and provide your own callbacks, you
may need to call <bold>QuestTasksBoundary()</bold> from time to time
to ensure that the player isn't allowed to "work ahead" on a task.
</li>
<li>
Write your own <bold>QuestAIConversation()</bold> method to
allow NPCs that are involved with the quest to interpret sentences
spoken to them by the player. For example: If one task in your
quest is to talk to Fred and get some information from him, you
would provide a QuestAIConversation() method to return the
string's that player could speak to Fred, such as "do you have [any]
(information|info) for me".
</li>
<li>
Write your own <bold>QuestAIQuestionAdd()</bold> method so
that NPCs that are involved in the quest can bring up quest
information in their conversations with the player. As in the
example above, if the player doesn't mention that they are
looking for information, Fred might eventually bring it up.
</li>
<li>
If you use either QuestAIConversation() or QuestAIQuestionAdd() you
will need to provide <bold>QuestAICallback()</bold> to have the
NPC speak the phrase, and/or take actions.
</li>
<li>
<bold>QuestAIShowOffer()</bold> and pQuestAIShow and pQuestAIOffer can
be modified to control how pQuestEnder responds when shown or given one
of the objects needed to complete the quest.
</li>
<li>
You may wish to override <bold>QuestCanBeCompleted()</bold>, but
you probably won't need to.
</li>
<li>
<bold>QuestPercent()</bold> can be overridden, but as long as your
QuestTasks() list is accurate, QuestPercent() will work fairly well.
</li>
<li>
The default implimentation of <bold>QuestRewardEnum()</bold> returns
pQuestRewardEnum. If you need more control than this then override
QuestRewardEnum().
</li>
<li>
<bold>pQuestCanCancel</bold> can be set to FALSE to prevent the quest from
being cancelled, or you can write your own <bold>QuestCanBeCancelled()</bold> method.
</li>
<li>
If you want to keep statistics about how long a quest took to complete
or when players started/stopped the quest, then create statistics objects
based on <bold>cStatisticsQuestDuration and/or cStatisticsQuestStartStop</bold> and
reference the object(s) in <bold>pQuestStatistics</bold>.
</li>
</ul>
<p>
Quest objects also contain some useful properties that you don't need
to initialize, but which might be handy during your coding:
</p>
<ul>
<li>
<bold>pIsQuest</bold> is set to TRUE, identifying the object
as a quest.
</li>
<li>
<bold>pQuestClass</bold> is the class name of the quest, "cQuestKillOrcs".
</li>
<li>
<bold>pQuestGiver</bold> is the NPC that gave the player the quest.
</li>
<li>
<bold>pQuestStartedPlayTime</bold> is the number of hours the player had
played his characters when the quest was begun. You can use this
for timed quests, or penalties for taking too long on a quest.
</li>
<li>
<bold>pQuestStartedTime</bold> is the time from TimeGet() when the quest
was begun.
</li>
<li>
<bold>pQuestStoryline</bold> is the storyline object (based on cStoryline)
that this quest is part of.
</li>
</ul>
<br/><section><p><bold><big>Giving a quest to a player character</big></bold></p></section>
<p>
There are two ways to give a quest to a player character:
</p>
<ol>
<li>
You can call <bold>cCharacter.QuestsAdd()</bold> to add the quest to the player's
list of quests.
</li>
<p>
While you're looking at QuestsAdd(), you might also wish to look
at <bold>QuestsEnum(), QuestsFind(), QuestsRemove(),
pQuestsCancelled and pQuestsCompleted</bold>.
</p>
<li>
You figure out which NPC hands out the quest and modify some
properties of the NPC so it will hand out the quest. See below.
</li>
</ol>
<p>
To easily have a NPC hand out a quest:
</p>
<ol>
<li>
Set <bold>pAIQuestHandout</bold> to a list of all the quests that the
AI hands out, as well as information about requirements like previous
quests that must have been completed.
</li>
</ol>
<p>
You can get a bit more control by modifying:
</p>
<ol>
<li>
<bold>pAIQuestQuestionAdd</bold> controls questions/statements the NPC
might make based upon whether a quest has been completed or not. For
example: If the player hadn't yet accepted the "kill 10 orcs" quest, the
NPC (as well as every NPC in the village) might mention that orcs
were troubling the village. When the player accepts the quest, a different
line might be used. And finall, after the player finishes the quest,
NPCs might occasionally thank the player for defeating the orcs. Likewise,
if the player cancels the quest, the player might get some verbal abuse.
</li>
<li>
<bold>AIQuestHandOut()</bold> gives you more control over the quests
that a NPC hands out than pAIQuestHandOut.
</li>
<li>
<bold>AIQuestHandOutUI()</bold> brings up a dialog that allows players
to accept a quest that the NPC is providing. You probably <bold>won't</bold> need
to override this, although you may wish to call it at some point.
</li>
<li>
<bold>AIQuestQuestionAdd()</bold> provides even more control than
pAIQuestQuestionAdd.
</li>
</ol>
<br/><section><p><bold><big>Miscellaneous</big></bold></p></section>
<ul>
<li>
The <bold>QuestUI()</bold> function brings up a dialog for displaying
the player's quests, or displaying information about a specific quest.
</li>
<li>
<bold>cCharacter.QuestsCompletedOrCancelled()</bold> can be used to
determine if a player character has completed (or cancelled) a quest.
</li>
</ul>
<br/><section><p><bold><big>The rumor mill</big></bold></p></section>
<p>
Quests can take advantage of a sub-system called "the rumor mill".
The rumor mill causes NPCs to talk to one another about the quest, either
spurring the player's interest in the quest (if they haven't accepted it),
or talking about the quest's completion (assuming that the player has
completed it).
</p>
<p>
The rumor mill is a useful technique because:
</p>
<ol>
<li>
It make the quest feel important before players have heard about
the quest or accepted it. For example: NPCs might say to one another,
"I just saw another rat this morning!", "Yes, I know what you mean; They're everywhere!"
</li>
<li>
Once players have completed the quest, NPCs will comment about
their accomplishments. For example: "Thank god all the rats are gone!",
"<PC-Name> certainly did a good job."
</li>
</ol>
<p>
To modify a quest to use the rumor mill:
</p>
<ol>
<li>
Create a conversation script based on <bold>cConvScriptRumorMill</bold> which
described the "There's a plague of rats" conversation between NPCs. For more information,
see the cConvScript tutorial.
</li>
<li>
You may want to set <bold>pConvScriptAutoNPCs</bold> and <bold>pConvScriptAutoListenersExclude</bold> so
that only certain NPCs will discuss the rumor, or so that NPCs won't talk about another NPC
if that NPC is present.
</li>
<p>
For example: If the quest is about spying on the strange activities of NPC X, then NPCs won't discuss
the rumor while NPC X is in the room.
</p>
<li>
In the quest object, set <bold>pQuestRumorMillConvScriptsNotStarted</bold> to the conversation
script object that you just created.
</li>
<li>
You might also wish to create several other conversation scripts and assign them
to <bold>pQuestRumorMillConvScriptsActive, pQuestRumorMillConvScriptsCompleted,
and pQuestRumorMillConvScriptsCancelled</bold>. These control what NPCs will say to one another
once the quest has been accepted (but not completed), after it has been completed,
or after it has been cancelled.
</li>
<li>
If you want the quest's rumors to spread beyond the map where the quest is handed out,
then set <bold>pQuestRumorMillMap</bold> to a list of maps where the rumors will be discussed.
If you leave it blank, then the map where the NPC hands out the quest will be used.
</li>
</ol>
<p>
That's all you need to do.
</p>
<p>
If you want to include rumors outside of the quest, then:
</p>
<ol>
<li>
Fill in <bold>pRumorMillSource</bold> in the NPC with a list of rumors.
</li>
<li>
Optionally, fill in <bold>pRumorMillSourceMap</bold> with the map where the rumor
will be spoken.
</li>
<li>
Or, write your own <bold>RumorMillSource()</bold> method for the NPC.
</li>
<li>
Or, write your own <bold>RumorMillConvScripts()</bold> method layer, assigned to the player
characters. You can test for whatever conditions you like (such as different fractures) and
add rumor-mill conversation scripts.
</li>
</ol>
<p>
You might also wish to change the following:
</p>
<ul>
<li>
<bold>gRumorMillLastTime</bold> and <bold>gRumorMillSpokenTime</bold> affect how often rumors
are spoken, ensuring that players don't hear them too often.
</li>
</ul>
Magically changing a player character's shape... transforming them into frogs and whatnot.
<section><p><bold><big>Shape changing</big></bold></p></section>
<p>
A fairly common fantasy device is to change a character's shape,
such as turning them into forgs, mice, or bowls of petunias.
</p>
<br/><section><p><bold><big>Shape changing layer</big></bold></p></section>
<p>
Before you even think about changing a character into a frog (or whatever),
you need to create a special class, such as cShapeChangeFrog, that
contains code and properties describing the essense of what makes a frog
different from a man (or elf, dwarf, etc.)
</p>
<p>
To create a shape changing class:
</p>
<ol>
<li>
<bold>Create a new class</bold> in the usual way. Make sure that
it is <bold>not</bold> instantiated as an object. Give it a name,
such as cShapeChangeFrog.
</li>
<li>
Base the class off of <bold>cShapeChange</bold>.
</li>
<li>
In cShapeChangeFrog, write code
to <bold>override some important methods</bold>, such
as NameRace(). See below for more information.
</li>
<li>
Add some <bold>properties</bold> that you need to override,
such as pBodyParts or pHeight. This is a <bold>tricky</bold> situation.
See below for more details.
</li>
</ol>
<p>
When a character is changed into a frog, the cShapeChangeFrog class
is <bold>layered</bold> on top of their character's existing class.
(They layering is done using a call to LayerAdd().) This layer
intercepts all method and property calls made to the character's object.
If the layer happens to have the given method, that's called instead
of the main object's method.
Likewise, if the layer has a specific property, then that property
is used instead of the character's property (kind of... see below).
</p>
<p>
You may wish override one or more of the following methods in
your cShapeChangeFrog class:
</p>
<ul>
<li>
<bold>EmoteQuery()</bold> - Use this to limit or change the emotes
that the changed character can do. A frog may not be able to smile,
but it can "ribbit". (You may also need to add an extra emote command, "ribbit",
to allow the character to type the emote.)
</li>
<li>
<bold>HeightEye()</bold> - Override this if the character can fly.
</li>
<li>
<bold>NameRace()</bold> - Returns the race's name, such as "a frog".
If you don't replace this, the character may still be known as "an elf" or
"a dward".
</li>
<li>
<bold>StealthAutoHide()</bold> - Causes the transformed character not
to be noticed right away.
</li>
<li>
<bold>VisualEmoteEnum()</bold> - Override this to control what visual
emotes a character can make, perhaps eliminating "smile" and adding
"throat sack full".
</li>
<li>
<bold>VisualEmoteQuery()</bold> - Change this along with VisualEmoteEnum().
</li>
<li>
<bold>VisualModifierEnum()</bold> - Change this along with VisualEmoteEnum().
This method converts from an emote into morph shapes used in the character's
3d model.
</li>
</ul>
<p>
You will also need to <bold>add several properties</bold> to cShapeChange frog.
However, because this is a layered class added after the main object
has been created, <bold>any values you type into the properties' edit
fields will be ignored</bold>. The only way to actually change
the value is to <bold>check the "Get/Set" checkbox next to the property and
write your own Get() method</bold>.
</p>
<p>
For example: You want your frog to be 1/10th of a meter tall.
Normally, you'd set "pHeight" to 0.1. <bold>This won't work.</bold> Instead:
</p>
<ol>
<li>
<bold>Add</bold> the pHeight property as normal.
</li>
<li>
<bold>Leave the pHeight edit field blank.</bold> It won't make any difference.
</li>
<li>
<bold>Check the "Get/Set" checkbox.</bold>
</li>
<li>
Edit the "Get" code by <bold>pressing the "Get" button</bold> next
to the property edit field. The
default code will be "return pHeight". <bold>Change this
to "return 0.1;"</bold>.
</li>
<li>
You can <bold>leave the "Set" code alone</bold>. There's no point changing
it.
</li>
</ol>
<p>
You will need to write code for a number of properties. You may wish
to change once or more of the following:
</p>
<ul>
<li>
<bold>pBodyParts</bold> - Controls what limbs your character has.
Be aware that you may need to <bold>create special body parts</bold>
for the shape change; frogs legs are different than human legs.
</li>
<li>
<bold>pContainerItemsMax</bold> - The maximum number of items the character
can hold.
</li>
<li>
<bold>pContainerVolumeMax</bold> - The maximum volume of items the character
can hold.
</li>
<li>
<bold>pContainerWeightMax</bold> - The maximum weight of items the character
can hold. This is affected by the character's strength attribute.
</li>
<li>
<bold>pExamineGeneral</bold> - What other players hear if they examine
the shape-changed character.
</li>
<li>
<bold>pGender</bold> - If you want to do gender bending.
</li>
<li>
<bold>pHeight</bold> - The character's height.
</li>
<li>
<bold>pSkillsFemaleInnate</bold> and <bold>pSkillsMaleInnate</bold> - How male/female
versions of the shape differ.
</li>
<li>
<bold>pSkillsRacialInnate</bold> - Skills that the player automatically acquires
when they're turned into this shape. This might, for example, include improvements
to jumping as well as the ability to speak to other frogs.
</li>
<p>
Important: Skills from pSkillsRacialLeanred are <bold>kept</bold> when a
character is shape changed. That means that if oSkillElvish is part
of pSkillsRacialLearned, then when the character is transformed into
a frog, they will be able to speak Elvish. If you don't want this
to happen, you could either add a negative Elvish skill to
cShapeChangeFrog.pSkillRacialInnate, or you could make Elvish an
innate skill instead of a learned one.
</p>
<li>
<bold>pVoice</bold> - The character's voice; frogs might have a croaky voice.
</li>
<li>
<bold>pVolumeSelf</bold> - How much volume the character uses.
</li>
<li>
<bold>pWeightSelf</bold> - How much the character weighs.
</li>
<li>
<bold>pDamageScreamBig, pDamageScreamHuge,
pDamageScreamSmall, and pDamageScreamWince</bold> - What sounds the character
makes when it's hit, probably a croak.
</li>
<li>
<bold>pFOVRange360</bold> - The character's field of view.
</li>
<li>
<bold>pHandedness</bold> - Force the character to be left/right handed.
</li>
<li>
<bold>pThreeDSound</bold> - How the player hears sound when playing
the shape-changed character.
</li>
<li>
<bold>pVisualEffectAutoExposure</bold> - How well the shape-change sees at night.
</li>
<li>
<bold>pVisualEffectColor</bold> - How colorful the world looks.
</li>
<li>
<bold>pVisualEffectColorBlind</bold> - Make the shape-changed
character color blind.
</li>
<li>
<bold>pVisualEffectNearSighted</bold> - Make the shape-changed
character near-sighted.
</li>
<li>
<bold>pVisual</bold> - A 3D model of what the shape-changed character
looks like.
</li>
</ul>
<br/><section><p><bold><big>Changing a character's shape</big></bold></p></section>
<p>
Once you've done all that, actually changing the character's shape is
pretty easy:
</p>
<ol>
<li>
You can call <bold>ShapeChangeQuery()</bold> to get the character's
current shape.
</li>
<li>
Before changing the shape, find out what room the character is
in and call <bold>vRoom.RoomShapeChange()</bold>. This is a safety
check to ensure that characters won't be changed into shapes that
aren't suitable for the room.
</li>
<p>
For example: Part of the game might involve changing player characters
into mice and letting them run through the walls of a building. While
a player is in the wall, you don't (usually) want them to change back into a human.
For one, how would you draw the inside of a wall from the perspective
of a human?
</p>
<li>
Call <bold>ShapeChange()</bold> to change the shape. The function
takes parameters specifying what's to happen with the character's equipment.
</li>
<li>
You should use <bold>SpeakNarratorAll()</bold> to inform everyone in the
room that the shape change has occurred.
</li>
</ol>
<br/><section><p><bold><big>Miscellaneous</big></bold></p></section>
<ul>
<li>
As alluded to above, if you want rooms, maps, regions, or zones where
the player is forced into a specifc shape (or forced out of a shape),
then write your own <bold>RoomShapeChange()</bold> for the room, map,
region, or zone object.
</li>
</ul>
How sneaking about works.
<section><p><bold><big>Stealth</big></bold></p></section>
<p>
Circumreality allows PCs and NPCs to sneak around through various commands
like "Sneak DIRECTION", "Hide", and "Hide behind OBJECT". Sneaking
allows characters to avoid enemies as well as listen in on conversations.
</p>
<p>
For the most part, sneaking is auomatically handled by:
</p>
<ul>
<li>
<bold>oSkillStealth</bold> helps make characters more stealthy.
</li>
<li>
<bold>oSkillPerception</bold> improves a character's ability to find
hidden characters.
</li>
<li>
<bold>IsThereLight()</bold> affects how dark it is, and how
well characters can hide.
</li>
<li>
If an object's <bold>pVolumeSelf</bold> is high enough (a few times
higher than the character's volume), then the object can be hidden
behind (or in).
</li>
</ul>
<br/><section><p><bold><big>Objects/rooms that can hide in</big></bold></p></section>
<p>
If you want to ensure that characters can hide behind an object, or hide in
a room:
</p>
<ul>
<li>
Set <bold>pVolumeSelf</bold> for objects such as crates, dressers, or
anything that a character might wish to hide behind or in.
</li>
<li>
If pVolumeSelf isn't good enough, you can write your
own <bold>StealthHidingPlaceQuality()</bold> method for the object. For example:
If you wanted to allow players to hide under a pile of leaves,
you'd need to write this.
</li>
<li>
If you have a room that you want to make it easy to hide in, without
having to hide behind an object, write your
own <bold>StealthHidingPlaceQuality()</bold> for the room. For example: If the
room is very smokey then hiding would be easy.
</li>
</ul>
<br/><section><p><bold><big>How it works</big></bold></p></section>
<ol>
<li>
Whenever a character hides, their <bold>pStealthHidden</bold> property
is set to indicate the object they're hiding in/behind, as well
as how they're hiding.
</li>
<li>
If any characters come into the room where a character is hiding, or
if they search the room, <bold>StealthIsHiddenPerceived()</bold> is called.
ActorMove() automatically handles this.
</li>
<li>
If one character is hidden to another, then the
hiding character's <bold>pStealthHiddenTo</bold> is expanded to
include the character that can't see the hiding character.
</li>
<li>
When a character is listed in pStealthHiddenTo, calls
to <bold>IsHidden()</bold> will return TRUE for that character.
</li>
<li>
Whenever the hidden character performs an
action, <bold>ActionVoidStealth()</bold> is called, potentially
exposing the hiding character. This, in turn,
calls StealthIsHiddenPerceived().
</li>
<li>
NPCs that perceive a character sneaking around,
with <bold>PerceiveActorEnter(), PerceiveActorLeave(),
and PerceiveInNewRoom()</bold> will mistrust the character.
</li>
</ol>
<br/><section><p><bold><big>Miscellaneous</big></bold></p></section>
<ul>
<li>
If you want specific characters not to be noticed by
other characters, write a <bold>StealthAutoHide()</bold> method
for the character.
</li>
</ul>
This library provides artificial intelligence routines for the interactive fiction title. It requires the Basic RPG library, and well as the Basic IF library to run.
Information about what storylines are and how to create them.
<section><p><bold><big>cAI - Artificial intellgience</big></bold></p></section>
<p>
Circumreality supports more complex artificial intelligence abilities
than are found in most IF or MUD authoring kits.
</p>
<p>
All of the AI programming is placed in this library, the
artificial intelligence library. Most of the AI code is
accessed from cAI, which is automatically included in
the cCharacter class, so all characters will have AI.
</p>
<p>
The Circumreality AI system includes the following features:
</p>
<ul>
<li>
<bold>Goals</bold> - The ability for an AI to have goals,
and the use of a finite state machine to complete those
goals. Goals might be as simple as the desire to another
place in the world or to attack another character.
</li>
<li>
<bold>Memories</bold> - AIs remember information about
other characters. This information could include the
character's names, how much they liked the last character,
when they last saw them, etc.
</li>
<li>
<bold>Conversations</bold> - AIs support primitive natural
language parsing and conversation abilities. (Although it's
primitive, it's infinitely more complex than conversations
in most IF titles and MUDs though.)
</li>
</ul>
<p>
<bold>Important:</bold> If your game is a multiplayer game
and you want a character (based off cCharacter, which
is based off cAI) to <bold>remember players across reboots</bold>,
then that <bold>character must also be based off
cSaveToDatabase.</bold> However, characters that are based off
cSaveToDatabase <bold>aren't</bold> allowed to enter private
player instances, since those characters might end up trapped
in the instance of one specific player, and not be available to
other players.
</p>
Information about what storylines are and how to create them.
<section><p><bold><big>cAIGoals - Goals and finite state machines</big></bold></p></section>
<p>
When a game developer creates the code to handle the intelligence
behind a computer-controlled character they usually use an algorithmic
technique called a "Finite State Machine" (FSM). The FSM provides
a semblence of intelligence, and allows the character to walk
around the world, attack any enemies that come into range,
and run away if it's wounded. FSMs <bold>don't</bold> handle conversations
though.
</p>
<p>
A FSM works by defining several "states" for a character's AI.
A state is, essentially, a state of mind. Enemies in most
first-person shooter games (FPS), have the following states:
</p>
<ul>
<li>
<bold>Partol</bold> - The NPC wanders around on a predefined
route. If the NPC sees an enemy it switch to the "Attack"
state.
</li>
<li>
<bold>Attack</bold> - The NPC sees an enemy (such as the
player) and attacks. If the player dies or manages to run
away, the AI returns to the "Patrol" state. If the NPC
is badly wounded the AI goes into the "Flee" state.
</li>
<li>
<bold>Flee</bold> - The NPC runs away from the enemy
and finds a space to hide. It then heals up over time, and
returns to the "Patrol" state.
</li>
</ul>
<p>
Finite state machines work pretty well for FPS games because
the player to NPC interactions are pretty simple: Kill or be
killed.
</p>
<p>
FSMs do start showing their weaknesses though. One major
weakness in a simple FSM is that sub-FSMs are needed.
For example: The "Attack" state really requires a number of
sub-states, such as "Load weapon", "Fire", "Run towards enemy",
"Take cover", etc. Each of these sub-states are a FSM by
themselves. In effect, the ability for a FSM to have sub-FSMs
(and sub-states) is like the C++ main() function being able
to call other functions.
</p>
<p>
The other weakness about FSMs is that they have a single
track mind. If the programmer didn't program in a state
transition, there's AI will never diverge from the state.
This is especially noticable when FSMs are applied to
AIs for IF titles or MUDs.
</p>
<p>
You might want to design a character that wanders around
the world. If the character sees something valuable it picks it
up. If it happens by a merchant, it'll sell the object. If
it's attacked, it will attack back. You can impliment this in
a FSM, but the number of states and complexities of transitioning
between states is huge. What you really need is a FSM for
wandering around, a FSM for picking things up, a FSM for selling
items to a merchant, and a FSM for attacking. However, a normal
FSM system cannot handle <bold>four</bold> FSMs running at once.
</p>
<p>
Circumreality's AI system can handle both sub-FSMs and multiple FSMs running
at once.
</p>
<p>
If you want to learn more about Finite State Machines, do a search
on the Internet, or read some books about game AI.
</p>
<br/><section><p><bold><big>cAIGoal</big></bold></p></section>
<p>
A finite state machine in Circumreality is called a "goal", because it
does a lot more than a traditional FSM. Each goal is an object
derived from cAIGoal. The object construct is used to group
the code together rather to provide a common storage method.
(In fact, you shouldn't store any information in your goal object
because the same object will be used for several AIs, all running
at the same time.)
</p>
<p>
To create a goal object:
</p>
<ol>
<li>
<bold>Create a new object</bold> and name it "oAIGoalXXX", where
XXX described what the goal does. A goal that wanders around
randomly might be called "oAIGoalWanderAroundRandomly".
</li>
<li>
Make it a sub-class of <bold>cAIGoal</bold>.
</li>
<li>
Check the option to <bold>create as an object</bold> so
it's an actual object, not just an abstract class.
</li>
<li>
Write the <bold>AIGoalStart()</bold> method, as well as
method for other states. See below.
</li>
<li>
Optionally, write the <bold>AIGoalPriorityAdjust()</bold> method.
See below.
</li>
<li>
Optionally, you may want players to be able to ask the NPC
what they're doing. To support this, provide
a <bold>pAIGoalWhatDoingSpeak, pAIGoalWhatDoingLike,
and pAIGoalWhatDoingImportance</bold> or
write your own <bold>AIGoalWhatDoing()</bold>.
</li>
</ol>
<p>
Speaking in FSM terminology, AIGoalStart() is the first "state"
in the FSM. Usually AIGoalStart() doesn't even act as a state
in the traditional FSM meaning, but acts like an "AI planner"...
</p>
<p>
When an AI creates a goal, it can send a parameter (or list
with multiple parameters) to the goal just like a call to
a function or method can be passed parameters. Many goal objects
are written so the parameter doesn't contain enough information
to determine what states need to be run. Instead, the first
task of the goal object is to plan out the specifics of how
to interpret the parameters.
</p>
<p>
If this doesn't make sense (which it probably doesn't), then
let me explain in English: An AI may have a goal of walking
from its current room to the neighborhood bakery. The neighborhood
bakery is a room in the world that isn't necessarily near
the character's current position. When the oAIGoalMove goal
is invoked by the AI, it will be passed in a parameter for
the bakery's room, oRoomBakery. It's then up to the
oAIGoalMove.AIGoalStart() method to figure out exactly how to
get to the bakery, such as going north to oRoomStreetMain, then
east to oRoomStreetOriely, then south into oRoomBakery. Only
once it has a plan will AIGoalStart() pass control to another goal.
</p>
<p>
Therefore, if you wanted to write oAIGoalMove, you'd need to
write the AIGoalStart() method with the following pseudo-code:
</p>
<ol>
<li>
Figure out what room the AI is in.
</li>
<li>
Figure out how to get from the character's current room to the new one.
</li>
<li>
Remember this sequence of rooms so you don't need to recalculate it
each time.
</li>
<li>
Switch to the "walk to next room" state.
</li>
</ol>
<p>
You'll also need a "Walk to next room" state which has the following
code:
</p>
<ol>
<li>
Look at the list of rooms to walk to. If it's empty then the AI
has met it's goal, and should return TRUE to whatever began
the goal.
</li>
<li>
Wait 5 seconds (or whatever) so that the NPC appears to
be waking at a normal pace. If the AI doesn't wait, the NPC
will get to the bakery in mere microsends.
</li>
<li>
Otherwise, look at the next room in the list and walk there.
</li>
<li>
Remove the name from the list.
</li>
</ol>
<p>
Goal objects can do easily do this. Here's how:
</p>
<ol>
<li>
<bold>Write the code</bold> for AIGoalStart() as described
above.
</li>
<p>
AIGoalStart() is passed the character's object
in the <bold>Actor</bold> parameter. This can be used to determine
where the character is.
</p>
<p>
The <bold>Parameter</bold> parameter of AIGoalStart() should
expect the destination room to be passed as an object.
With the character's current room and destination room known,
AIGoalStart() can calculate the path needed using the A*
algorithm. I won't get to that right now
</p>
<p>
The AIGoalStart() method is passed a list in
the <bold>Settings</bold> paramter. You can store any information
in this list that you like. (It acts as a substitiute for member
variables, which you <bold>cannot</bold> use to store information.
Remember your oAIGoalMove object may be used by more than
one NPC simultanously.
</p>
<p>
Once the path has been written into the "Settings" list,
AIGoalStart() should <bold>call AIGoalNext() and return
the value</bold>. AIGoalNext() is provided by cAIGoal,
and wraps up the input parameters into a list, that can
be returned from AIGoalStart(), and is interpreted by
AIGoalStart's caller. The code might look
like <bold>return AIGoalNext (WalkToNextRoom, 5.0,
NULL);</bold> WalkToNextRoom is the method that handle the
"walk to next room" state. 5.0 is the number of seconds to
delay before reaching that state. NULL is the parameter that
will be passed into WalkToNextRoom, but it isn't needed.
</p>
<li>
<bold>Create a private method called WalkToNextRoom</bold>. It
should accept the same paramters as AIGoalNext().
</li>
<p>
WalkToNextRoom() receives the same Settings list that was
passed into AIGoalsStart(). It can look at the list
and see what rooms it needs to move to. <bold>If the list
is empty</bold> then WalkToNextRoom should <bold>return
AIGoalNextFinished (TRUE);</bold> This informs the goal
monitoring code in cAI that the goal is finished, and
that is has been successfully completed (by returning TRUE).
</p>
<p>
If there are still rooms on the list,
call <bold>ActorMove()</bold> with the room, and <bold>remove
the room from the Settings list</bold>. Then, <bold>return
AIGoalNextRepeat(NULL)</bold> to repeat the same state (of
WalkToNextRoom()), 5 seconds from now.
</p>
</ol>
<p>
If you want the NPC to walk to the bakery as soon as its
created you can just modify <bold>pAIGoalsEmpty</bold> in the
NPC object, and
change it to <bold>[[oAIGoalMove, 0, oRoomBakery]]</bold>.
If you start up your IF title, and can log on quickly enough,
you'll see the NPC walking to the bakery. He will move to
a new room every 5 seconds.
</p>
<p>
The <bold>pAIGoalsEmpty</bold> parameter is used by cAI whenever
it comes across an AI without any goals... which basically
means when the object is first created. The list contains
a sub-list for every goal the AI should start out with. Each
sub-list starts with a goal object, followed by a priority, and
then the parameter to pass into the goal's AIGoalStart() method.
(I'll get to priority in a bit.)
</p>
<br/><section><p><bold><big>Sub-goals</big></bold></p></section>
<p>
What happens if you want the NPC to wander from his starting
room to the bakery, and then to the local pub, and then
back to his starting location?
</p>
<p>
You could modify the oAIGoalMove object that you wrote to
handle all three locations, or you could use sub-goals, letting
oAIGoalMove handle the sub-goal of moving from one spot to another.
</p>
<p>
To create a goal that handles "Follow a path" that would
move the NPC from A to B to C and back to A, just do the following:
</p>
<ol>
<li>
Create a new object, <bold>oAIGoalFollowAPath</bold> just like
you created oAIGoalMove.
</li>
<li>
Write the <bold>AIGoalStart()</bold> method for the goal.
</li>
<p>
It should expect that the <bold>parameter</bold> passed into it is a list
of rooms, such as [oRoomBakery, oRoomPub, oRoomStart].
AIGoalStart() should <bold>remember this list in the Settings list</bold>.
You can just do <bold>Settings.ListConcat(0, Parameter);</bold>,
which will leave Settings with <bold>[0, oRoomBakery,
oRoomPub, oRoomStart]</bold>. The first paremter, "0", is the next
room in the list to go to.
</p>
<p>
AIGoalStart() then should call <bold>return AIGoalNext
(WalkedToRoom, 0, TRUE);</bold> This causes the WalkedToRoom
goal to be run in 0-seconds and be passed in TRUE (which means
that no error has occurred).
</p>
<li>
Write a private method, <bold>WalkedToRoom</bold>, with the
same parameters as AIGoalStart().
</li>
<p>
If the <bold>Parameter != TRUE</bold> then there has
been an error, and WalkedToRoom() should <bold>return
AIGoalNextFinished (FALSE);</bold> This will stop the NPC's
wandering around from room to room.
</p>
<p>
Otherwise, the character should walk to the next room.
Remember <bold>var vRoom = Settings[Settings[0]+1];</bold>,
which is the next room on the list. Advance to the next
room by calling <bold>Settings[0] = (Settings[0] + 1) %
(Settings.ListNumber()-1);</bold>
</p>
<p>
Then, to walk to vRoom, just <bold>return AIGoalNextSubGoal
(WalkedToRoom, oAIGoalMove, 0, 0, vRoom);</bold> This
tells the AI system to run the sub-goal, oAIGoalMove, which
you wrote previously. oAIGoalMove is run with a priority of
0 (which I'll explain later), and a delay of 0 seconds (why
wait?). The parameter is vRoom, which is the next room to
walk to. The first parameter, WalkedToRoom, is the state in
this goal that will be called with oAIGoalMove reaches its
goal. When oAIGoalMove.WalkToNextRoom() reaches its destination
it will call AIGoalNextFinished(TRUE). The "TRUE" parameter
will be passed into oAIGoalFollowAPath.WalkedToRoom(), so
the "follow a path" goal knows that everything went okay.
</p>
<li>
Modify <bold>pAIGoalsEmpty</bold> to
be <bold>[[oAIGoalFollowAPath, 0, [oRoomBakery, oRoomPub,
oRoomStart]]</bold>. Now, the NPC will walk between the
bakery, pub, and start room and won't ever stop.
</li>
</ol>
<br/><section><p><bold><big>Running multiple goals at once</big></bold></p></section>
<p>
What if you want your NPC to pick up any objects that it sees
along its path?
</p>
<p>
You could modify your oAIGoalMove code to also look for unclaimed
objects and pick them up. However, since not all NPCs will
be cleptomaniacs, you now need two goal objects, oAIGoalMove and
oAIGoalMoveClepto. Plus, you'd need two "follow a path" objects,
oAIGoalFollowAPath and oAIGoalFollowAPathClepto.
</p>
<p>
There is an easier way...
</p>
<ol>
<li>
Create a new goal, <bold>oAIGoalPickUpAnything</bold> that
ends up looking around and picking up objects that it sees.
That's all it does. It will require a few methods just like
my previous examples. I won't go into detail.
</li>
<li>
Modify <bold>pAIGoalsEmpty</bold> to
contain <bold>[[oAIGoalFollowAPath, 0, [oRoomBakery, oRoomPub,
oRoomStart], [oAIGoalPickUpAnything, -10] ]</bold>. This
new property tells the AI for the NPC object to create two goals,
one of which is to wander around and the other is to pick
up objects. The list element after oAIGoalPickUpAnything is
-10, which is the priority. I didn't put a third element,
the parameter, because pick up anything doesn't require
any parameters.
</li>
<li>
Modify your world to include <bold>some objects
placed along the NPC's path</bold>.
</li>
<li>
<bold>Start your IF</bold>.
</li>
</ol>
<p>
You'll be disappointed. The AI just wanders around the world
and doesn't ever pick any of the objects up. That's because
the priority score for oAIGoalPickUpAnything is less than
the priority for oAIGoalFollowAPath. If you make the priority
higher, you'll discover that the NPC picks up all the objects
in the starting room, but doesn't ever move.
</p>
<p>
The solution is to write a <bold>AIGoalPriorityAdjust()</bold> method
for the oAIGoalPickUpAnything object.
</p>
<p>
When the AI system realizes that more than one goal is active
for a NPC, it uses the priority to determine which goal to run.
However, you may want the priority to be dynamic, depending upon
the character's location, surroundings, or health. The AI
system automatically calls AIGoalPriorityAdjust() for each
goal and adds
the number it returns to the goal's score. Most goals don't
bother with this function, but some need to.
</p>
<p>
In the case of the cleptomaniac NPC, if oAIGoalPickUpAnything's
AIGoalPriorityAdjust() returns 20 (or any number >= 11), then
the oAIGoalPickUpAnything goal will temporarily have a higher
priority than oAIGoalFollowAPath. Therefore, write code in
oAIGoalPickUpAnything.AIGoalPriorityAdjust() that <bold>checks
the room for objects to pick up</bold>. If it finds any it
can return 20. If it doesn't find any it will just return 0.
</p>
<p>
If you make these changes the NPC will wander around its route
and pick up any objects along the way.
</p>
<p>
You can use multiple goals with dynamic priorities for lots of
things:
</p>
<ul>
<li>
The "drink healing potion" goal would normally have a low
priority that would increase if the character was wounded.
</li>
<li>
The "run away" goal could likewise have a low priority that
increases if the character is wounded.
</li>
<li>
A "find the store and eat goal" could increase its priority
based upon the hunger of the character.
</li>
<li>
Etc.
</li>
</ul>
<br/><section><p><bold><big>Temporarily suspending goals</big></bold></p></section>
<p>
An NPC wandering around the world and picking up anything
can also be achieved by
using <bold>AIGoalNextSuspend()</bold>. A goal which temporarily
suspends itself waits a given amount of time before waking up
and doing processing. The "pick up anything" goal could wake up
every 5-10 seconds, and see if any objects were around. If there
weren't any it would go back to sleep, suspending itself for
another 5-10 seconds. If it found an object it would initiate
the <bold>oAIGoalObjectGetDrop</bold> goal to pick up it up.
</p>
<br/><section><p><bold><big>Programmatically adding new goals</big></bold></p></section>
<p>
So far I've been using <bold>pAIGoalsEmpty</bold> to add goals
to the AI's list of goals. If you wish to have some other
code add a new goal, you can call <bold>oNPC.AIGoalAdd()</bold> to
add one or more goals to the list.
</p>
<p>
Remember, that when a goal is added the highest priority ones
will run first, while lower priority goals will wait until
they're highest priority. (Unless, of course, the goal supports
AIGoalPriorityAdjust()).
</p>
<p>
You can use this to your advantage. For example: A NPC has the
oAIGoalFollowAPath goal running. However, if another character
speaks to the NPC, then your code to add the goal
oAIGoalHavingConversation with a <bold>higher</bold> priority
than oAIGoalFollowAPath. This would cause the NPC to stop
walking and talk to whomever has asked the question. If the NPC
weren't asked a question for 20 seconds, or if the other
character walks away, the oAIGoalHavingConversation goal could
exit by returning AIGoalNextFinished(), and the NPC would
procede to follow its path.
</p>
<p>
cCharacter objects, as well as any other object with cAI as a superclass
also supports the following methods:
</p>
<ul>
<li>
<bold>AIGoalAdd()</bold> - As mentioned above. Add a goal to
the AI's list of goals.
</li>
<li>
<bold>AIGoalFind()</bold> - Find a goal in the AI's list.
</li>
<li>
<bold>AIGoalNumber()</bold> - Return the number of goals.
</li>
<li>
<bold>AIGoalOverride()</bold> - This is a method that you write
yourself. It lets you create a custom personality for NPCs, so
that they use different goals than the norm. For example: If you
want a character that uses different attack aiming tactics than normal,
you would have the AIGoalOverride() look for
references to the default aim handler, oAIGoalCombatAimChoose,
and return your own goal, such as oAIGoalCombatMYAimChoose.
</li>
<li>
<bold>AIGoalQuery()</bold> - Get information about a goal,
such as its callback, timing, or whether it's suspeneded until
a sub-goal finishes.
</li>
<li>
<bold>AIGoalRemove()</bold> - Remove a goal from the AI's list.
</li>
</ul>
<br/><section><p><bold><big>Summary</big></bold></p></section>
<p>
Goals are the basic mechanism for getting a NPC to "do stuff"
like walk around, fight, etc. You can have goals run sub-goals,
as well as running several goals at once. By combinging sub-goals
and multiple goals (potentially with dynamic priorities) you can
create a wide variety of behaviours for your AIs.
</p>
<p>
For a complete lists of pre-programmed
goals, look for objects with the "oAIGoal" prefix.
</p>
Provides an overview of goals that can be used to move NPCs around.
<section><p><bold><big>Movement goals</big></bold></p></section>
<p>
The AI library includes a number of goals designed for
NPC movement. Here's a quick overview: (For more inforamtion about
the goals look in the documentation specific to the goal
object.)
</p>
<ul>
<li>
<bold>oAIGoalMoveFollowCircuit</bold> - With this goal, you
provide a set of destinations, such as Room A, Room F, and
Room Q, and the AI automatically wanders from Room A to Room F,
to Room Q, and then back to Room A. The path between A and F,
F and Q, and Q and A is automatically calculated.
This goal use useful for <bold>guards</bold> and other NPCs
with set movments.
</li>
<li>
<bold>oAIGoalMoveFollowObject</bold> - NPCs using this goal
will follow an object around, wherever it is in the world.
It can be used to <bold>have monsters chase after fleeing
characters, for pets, or to produce monsters that
stalk characters</bold>.
</li>
<li>
<bold>oAIGoalMoveFollowPath</bold> - This goal causes an AI
to follow an exact path between rooms. All rooms must be connected.
You probably won't use it; instead, look at oAIGoalMoveFollowCircuit
or oAIGoalMoveToRoom.
</li>
<li>
<bold>oAIGoalMoveFollowPersonalPC</bold> - NPCs follows
whatever PC is assigned to its pPersonalNPCFor property.
</li>
<li>
<bold>oAIGoalMoveRandomCircuit</bold> - This is like
oAIGoalMoveFollowCircuit except that the immediate destination
is randomly chosen from any of the rooms in the list.
Use this for <bold>unpredictable guards</bold> who check out
rooms, but not in any specific order.
</li>
<li>
<bold>oAIGoalMoveLeadObject</bold> - You can use this goal,
in conjuction with oAIGoalMoveToRoom, to have the NPC lead
a character along a path. If the character falls behind or
wanders off the path the NPC can (optionally) chase the
character down and ask the character to follow.
</li>
<li>
<bold>oAIGoalMoveToAdjacentRoom</bold> - This goal is a sub-goal
of all the movement goals. It causes a NPC to move to a
room that's adjacent to the one it's in. In the process it
will open (and unlock doors), and get up if it's knocked down.
</li>
<li>
<bold>oAIGoalMoveToObject</bold> - The AI will located an
object (by cheating) and head towards the room that contains
the object, like oAIGoalMoveToRoom. However, if the object
moves in the meantime, the object will identify the move and
adjust its course. Use this for <bold>panicked villagers
looking to find the town's guards</bold>.
</li>
<li>
<bold>oAIGoalMoveToOrigin</bold> - Invoking this goal will
cause the NPC to return to wherever it was created. This
is a useful goal for getting <bold>fleeing NPCs to return
to where they belong</bold>.
</li>
<li>
<bold>oAIGoalMoveToRoom</bold> - The AI will walk to the
specified room.
</li>
<li>
<bold>oAIGoalMoveWanderAroundMap</bold> - Using this, the NPC
will wandering around any room it can get to in a map (or
maps). Use this for <bold>wandering animals and monsters</bold> that
have no fixed location.
</li>
<li>
<bold>oAIGoalMoveWanderNearby</bold> - AI's with this goal will
wander around the starting room, but never very far from
it. You can use it for <bold>spawned monsters in dungeons</bold> that
should basically stick to their spawning point.
</li>
<li>
<bold>oAIGoalWaitForObject</bold> - Causes the AI to wait around
for an object to appear in the room. This is useful for "waiting
for the cows to come home" or cGuardExitExtra - waiting to make sure
the exit isn't left unguarded.
</li>
</ul>
<p>
Some movement-related goals are used by the movement goals:
</p>
<ul>
<li>
<bold>oAIGoalObjectLock</bold> - The AI will lock or unlock
an object, such as a door or chest.
</li>
<li>
<bold>oAIGoalObjectOpen</bold> - This causes the NPC to open
or close a door or container. Optionally, the NPC can unlock
or lock the container.
</li>
<li>
<bold>oAIGoalStandUpSitDown</bold> - Causes the NPC to stand
up or sit down.
</li>
<li>
<bold>oAIGoalEmote</bold> - Causes a NPC to emote.
</li>
</ul>
Describes how the combat AI works.
<section><p><bold><big>Combat AI goals</big></bold></p></section>
<p>
The AI that handles combat is a bit tricky, so this section
will be a tad long...
</p>
<br/><section><p><bold><big>Enemies list</big></bold></p></section>
<p>
Every AI (derived from cAI) remembers a list of dangerous enemies,
as well as friends that will help in combat. (This list is different
than the like/dislike list also supplied for AI's.)
</p>
<p>
Characters that a NPC meets are rated from 10.0 to -10.0 on the
enemies list. Values between 10.0 and 1.0 mean that the character
is an enemy that will attack the NPC. .999 and -.999 is a neutral
character whose intentions are unknown. -1.0 to -10.0 is a trusted
friend that will help defend the NPC in combat.
</p>
<p>
When a NPC first encounters a character, a call
to <bold>AIEnemiesListGuess()</bold> is called to see if the
NPC considers the character an enemy. The default behavior
for this function is to:
</p>
<ol>
<li>
If the AI has <bold>pAIEnemyOfAllPCs</bold> then the AI will
automatically be an enemy of any character controlled by
a player. This is a good setting for a monster.
</li>
<li>
If the AI was <bold>spawned by the same room</bold> as another AI then
it will be a friend with the other AI.
</li>
<li>
If the AI is the same <bold>class</bold> as the other AI
it's an automatic friend too. Thus, if a number of tigers
are spawned in different rooms of the wilderness, they
will act as friends.
</li>
</ol>
<p>
You can provide your own AIEnemiesListGuess() function for
an AI.
</p>
<p>
The enemies rating is affected by what the AI sees:
</p>
<ol>
<li>
When a <bold>NPC is attacked</bold>, the attacker is almost invariably added
to the NPC's enemies list.
See <bold>cAI.PerceiveAttack</bold> for the code.
</li>
<li>
When the <bold>NPC see a friend attacked</bold>,
the attacker is likewise added (but not always).
See <bold>cAI.PerceiveAttack</bold> for the code.
</li>
<li>
If a <bold>character
attacks a NPC's enemy</bold>, they are (usually) made a friend.
See <bold>cAI.PerceiveAttack</bold> for the code.
</li>
<li>
If an AI starts yelling out for help
using <bold>AICombatCallForHelp()</bold>, the
code in <bold>cAI.PerceiveSpeak()</bold> may cause any
AI's that hear the yell to become enemies of the attackers.
</li>
</ol>
<p>
You can programatically set and read the enemies' list values
using the following methods:
</p>
<ul>
<li>
<bold>AIEnemiesCalculateTheOdds()</bold> - Used by AIs to
determine if they're likely to win a battle of if their
enemies are likely to win. It's based on AIEnemiesListUpdate().
You probably won't need to use this method directly.
</li>
<li>
<bold>AIEnemiesListGet()</bold> - Returns the enemies rating,
from 10 to -10, for a character. If the character has never
been met it returns NULL.
</li>
<li>
<bold>AIEnemiesListGuess()</bold> - Call this if no rating
is set. It will return a rating to begin with, or Undefined
if there's no opinion whatsoever. Calling ToNumber (
AIEnemiesListGuess()) will always return a number, since
Undefined will be converted to 0.
</li>
<li>
<bold>AIEnemiesListSet()</bold> - Sets a new rating for the
enemy or friend.
</li>
<li>
<bold>AIEnemiesListUpdate()</bold> - Finds all the characters
in a room and checks their enemies' list score. If it's
unknown, then it calls AIEnemiesListGuess() to fill them in.
This method can optionally return a list of enemies, friends,
or both enemies and friends.
</li>
</ul>
<br/><section><p><bold><big>Perceiving a possible conflict</big></bold></p></section>
<p>
AI's check for enemies in the room when:
</p>
<ul>
<li>
The AI <bold>walks into a new room</bold>.
This code is on <bold>cAI.PerceiveInNewRoom().</bold>
</li>
<li>
When a <bold>character walks into the AI's room</bold>.
This code is on <bold>cAI.PerceiveActorEnter().</bold>
</li>
<li>
When an AI sees <bold>one character attacking another</bold>.
This code is on <bold>cAI.PerceiveAttack().</bold>
</li>
</ul>
<p>
When danger might just have appeared, the AI code
calls <bold>AIPotentialConflict()</bold>. This checks for any
enemies in the room.
</p>
<p>
If there are enemies in the room, the code adds several
goals to the AI's list. The goals used are
in <bold>pAIGoalsConflict</bold>. If the goal is already in
the AI's list then it is <bold>not</bold> added a second
time.
</p>
<br/><section><p><bold><big>Default combat goals</big></bold></p></section>
<p>
The default goals that are added by <bold>pAIGoalsConflict</bold> are:
</p>
<ol>
<li>
<bold>oAIGoalCombatRetreat</bold> - This causes the AI to test
out it's odds of winning every once in awhile. If it thinks its
odds are still good the AI suspends the retreat goal for another
10 seconds. If there are not enemies left, the retreat goal
finishes.
</li>
<p>
If the goal decides that the character is in danger,
based on a call to <bold>AIEnemiesCalculateTheOdds()</bold>,
then the AI will run away. <bold>pAICombatBravery</bold> is
used to determine how much the odds have to be stacked against
the AI before it runs, as well
as <bold>pAICombatWoundsRetreat</bold> to see if
it will selfishly abandon it's friends when its wounded.
If <bold>pAICombatRunDrop</bold> is set to TRUE, the AI will
drop an item to distract its attackers.
If <bold>pAICombatRunCallForHelp</bold> is set to TRUE the
AI will yell for help as it runs.
If <bold>pAICombatRunForGuard</bold> is set to TRUE, the AI
will run to the nearest guard and ask it for help. (This
one is particularly useful for villagers, which should run at
the slightest sign of danger and seek a guard.)
</p>
<li>
<bold>oAIGoalCombatCallForHelp</bold> - This causes the NPC
to call out for help once in awhile. AI's listen
for calls for help in their <bold>PerceiveSpeak()</bold> methods.
If they detect a call, they determine if the calling NPC
is a friend, enemy, or neutral. If it's a friend, they
may run to the friend's aid
if <bold>pAICombatCallForHelpRespondsFriends</bold> is not 0.
Likewise, if they hear an enemy calling for help, they may
decide to join in the kill.
The <bold>pAICombatCallForHelpRespondsEnemies</bold> property
affects this chance.
</li>
<p>
If you don't wish a NPC to call for help, override
its <bold>pAIGoalsConflict</bold> so it doesn't include the goal.
</p>
<li>
<bold>oAIGoalAttackEnemies</bold> - This goal causes the NPC
to attack any enemies in the room. It picks an enemy, by
various means, and attacks with a weapon. The code also
has the AI ready a weapon or potentially pick one up.
</li>
<li>
<bold>oAIGoalMoveChaseEnemies</bold> - If all the enemies
in a room are killed, oAIGoalAttackEnemies will finish.
The next goal in line causes the NPC to chase after enemies
in neighboring rooms, just in case some of the enemies
ran away. It finishes when no more enemies
are left in any neighboring rooms.
</li>
<li>
<bold>oAIGoalLootDeadBodies</bold> - Once all the enemies
are gone, this goal will have the NPC loot all the dead bodies.
It picks up the most valuable items first. It will hold onto
them for 30-60 minutes, after which point they're automatically
deleted. That means that characters who are killed by NPCs have
30-60 minutes to find the NPC and kill it, or they won't
get their gear back.
</li>
<li>
<bold>oAIGoalMoveToPreCombat</bold> - Finally, after all the
bodies have been looted, oAIGoalMoveToPreCombat causes the
NPC to return to the room it was in before combat began.
Once there, this goal finishes and the AI continues its
normal routine.
</li>
</ol>
<br/><section><p><bold><big>Default goals</big></bold></p></section>
<p>
When a NPC is first created, it automatically adds the following
goal, as specified in <bold>pAIGoalsFirstActions</bold>.
</p>
<ul>
<li>
<bold>oAIGoalObjectEquipAll</bold> - Causes the NPC to equip
any weapons and armor that it has. This ensures it will be
ready for combat when the time comes.
</li>
</ul>
<br/><section><p><bold><big>List of other combat-related goals</big></bold></p></section>
<p>
Of course, you can
change <bold>pAIGoalsConflict</bold> and <bold>pAIGoalsFirstActions</bold> to
whatever goals you wish.
</p>
<p>
Some other combat-related goals that I haven't discussed yet
are:
</p>
<ul>
<li>
<bold>oAIGoalAttackEnemy</bold> - Has the NPC take a swing
at an enemy. This is called from oAIGoalAttackEnemies.
</li>
<li>
<bold>oAIGoalCombatAim</bold> - Call this to have the
NPC change his aim (body part) location to a specific value.
</li>
<li>
<bold>oAIGoalCombatAimChoose</bold> - Call this to have the
NPC change his aim (body part) location automatically, based on
various personality properties.
</li>
<li>
<bold>oAIGoalCombatDefend</bold> - Call this to have the
NPC change his defense location to a specific value.
</li>
<li>
<bold>oAIGoalCombatDefendChoose</bold> - Call this to have the
NPC change his defense location automatically, based on
various personality properties.
</li>
<li>
<bold>oAIGoalCombatEnemy</bold> - Call this to have the
NPC change target a new enemy, based on a passed-in parameter.
</li>
<li>
<bold>oAIGoalCombatEnemyTargetChoose</bold> - Call this to have the
NPC change his targeted enemy, based on
various personality properties.
</li>
<li>
<bold>oAIGoalCombatDodgeParry</bold> - Call this to have the
NPC change his dodge/parry to a specific value.
</li>
<li>
<bold>oAIGoalCombatDodgeParryChoose</bold> - Call this to have the
NPC change his dodge/parry, based on
various personality properties.
</li>
<li>
<bold>oAIGoalCombatRetreatCallForHelp</bold> - This goal
causes the NPC to keep calling out for help until the goal
is killed. It's added and killed when the NPC retreats
in oAIGoalCombatRetreat.
</li>
<li>
<bold>oAIGoalObjectEquip</bold> - The NPC will equip
or unequip a single
object. It's called by oAIGoalObjectEquipAll.
</li>
<li>
<bold>oAIGoalObjectGetDrop</bold> - The combat AI uses this
goal to pick up weapons it wishes to equip, to loot bodies,
or to drop treasure in the hopes of distracting an attacker.
</li>
<li>
<bold>oAIGoalObjectGetWeapon</bold> - The AI will pick up
a weapon from the ground (or a dead body) if it's better than
its current weapon, and if it's usable by the NPC. This
is used by oAIGoalAttackEnemies if the AI's weapon breaks
or gets dropped.
</li>
</ul>
<br/><section><p><bold><big>Combat AI properties</big></bold></p></section>
<p>
Many of the combat-related goals rely on properties that affect
how the AI reacts in combat:
</p>
<ul>
<li>
<bold>pAICombatAttackEnemySpeed</bold> - How rapidly the AI attacks
compared to the ideal attack speech for his weapon.
</li>
<li>
<bold>pAICombatAttackEnemySpeedVar</bold> - Amount of variation in
the AI's attack speed.
</li>
<li>
<bold>pAICombatAimLocation</bold> - Location where the AI tends to aim for.
</li>
<li>
<bold>pAICombatAimSwitch</bold> - Likelihood of the AI aiming for
a different body part.
</li>
<li>
<bold>pAICombatDefendBodyPart</bold> - Preferred defence location.
</li>
<li>
<bold>pAICombatDefendJustHit</bold> - How likely it is that the AI
will defend a body part that was just hit.
</li>
<li>
<bold>pAICombatDefendSwitch</bold> - Likelihood of changing defence locations.
</li>
<li>
<bold>pAICombatEnemyTargetAttacker</bold> - Likelihood that the AI
will attack the last character that attacked it.
</li>
<li>
<bold>pAICombatEnemyTargetSame</bold> - How likely the AI will keep
attacking the same enemy and not switch.
</li>
<li>
<bold>pAICombatEnemyTargetWeaker</bold> - Causes the AI to
attack weaker opponents in preference to stronger opponents.
</li>
<li>
<bold>pAICombatEnemyTargetWounded</bold> - Likelihood that the AI
will attack the most wounded character.
</li>
</ul>
<br/><section><p><bold><big>Extra tips</big></bold></p></section>
<ul>
<li>
Because each NPC will have their own combat tactics, you might
wish to provide a <bold>AIGoalOverride()</bold> method that
overrides some standard combat goals,
such as oIAIGoalCombatDefendChoose, with a custom goal. For example:
You could use this to make the NPC only defend one part of his
body.
</li>
</ul>
How to provide an AI with a daily schedule.
<section><p><bold><big>AI daily schedules</big></bold></p></section>
<p>
If you want your AI to have a daily schedule, some automatic
scheduling exists.
</p>
<ul>
<li>
To have the NPC work in a location,
set <bold>pAIScheduleLocationWork</bold>. If you want something
other than a 9-5 job, set <bold>pAIScheduleTimeWork</bold>.
</li>
<li>
To have your NPC return home and sleep,
set <bold>pAIScheduleLocationLive</bold>. The sleeping
hours can be set with <bold>pAIScheduleTimeSleep</bold>.
</li>
<li>
To have the NPC recreate (hang out) somewhere,
set <bold>pAIScheduleLocationRecreate</bold>. The time
can be set with <bold>pAIScheduleTimeRecreate</bold>.
</li>
</ul>
<br/><section><p><bold><big>More advanced scheduling</big></bold></p></section>
<p>
The chances are that you'll want your NPC to do more than just
walk to work, walk home, and sleep. To control more advanced schedule options:
</p>
<ol>
<li>
Come up with a <bold>lower-case name</bold> for an activity that the
NPC regularly does, such as "attendmooselodge" to have the
NPC attend a Moose Lodge meeting.
</li>
<li>
Create an <bold>AIScheduleEnum()</bold> method for the AI that
fills in ScheduleList with information about "attendmooselodge", such
as the time and the priority. (You can use this method to make sure
your NPC only attends the meetings once a week.)
</li>
<li>
Create an <bold>AIScheduleLocation()</bold> method that
also traps the "attendmooselodge" and returns the room where the moose
lodge meetings take place.
</li>
<li>
If you want your NPC to be able to answer the question,
"What are you doing?", then write <bold>AIScheduleWhatDoing()</bold> to
trap "attendmooselodge".
</li>
<li>
If you want your NPC to do any tasks while at the lodge (based on cAIGoal),
then you'll need to write a <bold>AIScheduleGoal()</bold> method. You
can control what the NPC does when its there, what it does before going there,
and what it does when it's finsihed.
</li>
<li>
If you have several characters who are a member of the Moose Lodge,
you have another choice: <bold>Create an oFactionMooseLodge</bold> based
on cFaction, and <bold>impliment the AIScheduleEnum(),
AIScheduleLocation(), and AIScheduleGoal()</bold> methods in the faction.
That way, all members of oFactionMooseLodge will show up to the meeting!
</li>
</ol>
<p>
Note: The scheduling features described here only work
if the NPC has the oAIGoalSchedule running. This will be run by default.
However, if you override the default, you may need to include
oAIGoalSchedule in the goals that you include in the overridden version.
</p>
<br/><section><p><bold><big>Foraging - cAIForage</big></bold></p></section>
<p>
A more complex schedule, foraging, is included in
the <bold>cAIForage</bold> class. This lets you create monsters that wander
around looking for food, or NPCs which pick up tin cans as they see them.
</p>
<p>
To create a monster (cMonster) or character that forages:
</p>
<ol>
<li>
<bold>Create your monster or character as usual</bold>, which will be based
on cMonster or cCharacter, and which are ultimately
based on cAI.
</li>
<li>
<bold>Add the cAIForageExtra</bold> superclass <bold>above</bold> the cMonster
or cCharacter class. Placing it above is important sinec cAIForageExtra
overrides some default behavior.
</li>
<li>
Set <bold>pAIForageExtraWant</bold> to the class of object the AI wants.
If you don't set it, it will default to "cFood".
</li>
<li>
Fill is <bold>pAIForageExtraEat</bold> to indicate if the foraging creature
will eat whatever it finds, or keep it in its possession. You can create
more complicated behaviors by wiring your own AIForafeExtraForageAct().
</li>
<li>
Set <bold>pAIScheduleLocationWork</bold> to a list of rooms where the NPC
forages, or which are destinations for foraging (and the AI will forage on
the way).
</li>
<li>
Set <bold>pAIForageExtraLocationWorkItinerant</bold> to cause the AI
to move sequentially from work room to work room, or to wander between all
of them.
</li>
<li>
Set <bold>pAIScheduleTimeWork</bold> to the hours that the AI forages.
</li>
</ol>
<p>
That's it. Your character will wander around picking up (and potentially) eating
the appropriate objects. Plus, PCs can give the NPC the right sort of objects
and make the NPC more friendly.
</p>
<p>
You may also wish to modify the following properties and methods:
</p>
<ul>
<li>
<bold>pAIForageExtraAmountWant</bold> and <bold>pAIForageExtraByValue</bold> cause the AI to "go home" if
it has foraged enough.
</li>
<li>
<bold>pAIForageExtraDropGoalPriority</bold> and <bold>pAIForageExtraRoomGoalPriority</bold> control
whether the AI stops to forage in the middle of combat.
</li>
<li>
<bold>pAIForageExtraGiveLike</bold> and <bold>pAIForageExtraGiveStopAttack</bold> affect how much the NPC likes
a PC that provides the foraged items... such as giving food to a foraging monster.
</li>
<li>
<bold>pAIScheduleIdleWork</bold> is used to describe the character
foraging. Defaults for monsters eating are already provided in cMonster.pAIScheduleIdleWork.
</li>
<li>
<bold>pAIForageExtraGiveNoThanksNarrate, pAIForageExtraGiveNoThanksSpeak,
pAIForageExtraGiveThanksNarrate, pAIForageExtraGiveThanksSpeak,
pAIForageExtraShowNoWantNarrate, pAIForageExtraShowNoWantSpeak,
pAIForageExtraShowWantNarrate, and pAIForageExtraShowWantSpeak</bold> all
control the AI's narrated and spoken reaction.
</li>
<li>
<bold>pAIScheduleWhatDoingWork</bold> lets the NPC say what it's looking
for when a player asks.
</li>
<li>
<bold>AIForageExtraForageAct()</bold> can be used to provide more complex
foraging activities than just eating and picking up objects.
</li>
</ul>
<br/><section><p><bold><big>Guarding an exit - cGuardExitExtra</big></bold></p></section>
<p>
To make a NPC guard an exit (and prevent players from leaving via the exit),
create a character (based on cCharacter or whatever):
</p>
<ol>
<li>
Add the <bold>cGuardExitExtra</bold> super-class to the object.
</li>
<li>
Fill in <bold>pAIScheduleLocationWork</bold> with the room that the guard
is protecting.
</li>
<li>
<bold>pAIScheduleTimeWork</bold> should be filled in with the hours that
this guard keeps. If the guard isn't there all the time then make sure to
have other guards show up, or players only have to wait for the
guard to leave.
</li>
<li>
<bold>pGuardExitExtraBlockDirection</bold> should be filled with the
direction that the guard prevents players from going.
</li>
<li>
<bold>pGuardExitExtraCanPassProperty</bold> the the property that will
be set on the PC that indicates that the PC is allowed to pass.
</li>
</ol>
<p>
You might also wish to set:
</p>
<ul>
<li>
<bold>pGuardExitExtraBlockNarrate</bold> for a narration of how the guard
blocks the PC from going in the blocked direction.
</li>
<li>
<bold>pGuardExitExtraBlockSpeak</bold> is spoken by the guard when he
blocks the PC.
</li>
<li>
<bold>pGuardExitExtraFavor</bold> controls whether the player can get
friendly with the guard and ask for a favor.
</li>
<li>
<bold>pGuardExitExtraFavorGranted</bold> is what the guard says when it
grants the favor of passage.
</li>
<li>
<bold>pGuardExitExtraWaitForObject</bold> ensures that the guard waits
around until another guard shows up.
</li>
</ul>
<br/><section><p><bold><big>Beggars - cBeggarExtra</big></bold></p></section>
<p>
You can make a NPC that begs for food, drink, money, or whatever using
the cBeggarExtra class.
</p>
<ol>
<li>
Add the <bold>cBeggarExtra</bold> super-class to the object, on top
of cRaceElf, or whatever.
</li>
</ol>
<p>
That's it! The NPC will beg for food, drink, and money whenver a player enters
the room, or it enters a room with a player.
</p>
<p>
You might also wish to:
</p>
<ol>
<li>
Add a <bold>schedule</bold> to the NPC so it wanders around and begs.
</li>
<li>
Change <bold>pAILikeEquip</bold> to control what comments it makes about what
the player is carrying.
</li>
<li>
<bold>pAIOfferCanBribe</bold> defaults to TRUE, but if you don't want the
beggar to accept money then set this to FALSE.
</li>
<li>
Change <bold>pAIOfferForget</bold> to adjust how frequently gifts can
be given to the beggar.
</li>
<li>
Change <bold>pAIOfferMaxAmount</bold> to determine how much the beggar will accept.
</li>
<li>
<bold>pAIOfferMinLike</bold> controls how much the NPC must like the player before
accepting gifts.
</li>
<li>
<bold>pAIValueObject</bold> defines what objects the beggar likes. The default is
for food and drink.
</li>
<li>
<bold>pBeggarExtraMinTime</bold> affects the number of seconds that must ellapse
between each "beg", so that beggars don't beg too often.
</li>
<li>
<bold>pBeggarExtraSpeak</bold> is a list of phrases that the beggar will speak.
You'll need to change this if you modify <bold>pAIValueObject</bold>.
</li>
<li>
<bold>pBeggarExtraSpeakScriptPriority</bold> controls the priority of the begging script,
ensuring that the beggar doesn't beg when running away from a monsters.
</li>
</ol>
Details how an AI remembers information about player characters.
<section><p><bold><big>AI memory of characters - cAIMemory</big></bold></p></section>
<p>
In most MMORPGs/MUDs/IF software, AIs (if they exist) remember
very little about a player character. <bold>Circumreality AIs remember
a lot of information about the player characters they meet.</bold>
</p>
<p>
At the heart of an AI's memory is the <bold>cAIMemory</bold> class,
which is a superlcass of <bold>cAI</bold> and <bold>cFaction</bold>.
</p>
<br/><section><p><bold><big>Basic cAIMemory methods</big></bold></p></section>
<p>
The cAIMemory class maintains a database of all the player
characters it has met, stored in <bold>pAIMemory</bold>. For
each character, it stores several hundred pieces of "information",
such as whether or not the AI likes the character, the last
time they met, what the character's favorite color is, etc.
</p>
<p>
Each piece of information is labled with a lower case
string, such as "favoritecolor", and is stored as a sub-list.
If your code changes elements within the sub-list, the AI
will remember the information.
</p>
<p>
Every AI (derived from cAI) remembers a list of dangerous enemies,
as well as friends that will help in combat. (This list is different
than the like/dislike list also supplied for AI's.)
</p>
<p>
To access an AI's memory about a character's favorite color,
call <bold>vFavoriteColor = oAI.AIMemoryGet (Actor,
"favoritecolor", FALSE);</bold> Actor is the player character's
object. "favoritecolor" is the piece of information to access.
FALSE indicates that if the memory doesn't exist then don't
bother to create it. When the method returns, vFavoriteColor
will be a list, with (hypothetically) the first element of the
list set to the AI's memory of the character's favorite
color. If the AI had never stored such a memory NULL will
be returned from AIMemoryGet().
</p>
<p>
An AI's memory is based on the following (fundamental) methods:
</p>
<ul>
<li>
<bold>AIMemoryActorExist()</bold> - Tests to see if the AI
remembers anything at all about the actor.
</li>
<li>
<bold>AIMemoryActorRemove()</bold> - Causes the AI to forget
everything it knows about the actor.
</li>
<li>
<bold>AIMemoryGet()</bold> - Gets (or adds) a memory to the
AI, as discussed above.
</li>
<li>
<bold>AIMemoryRemove()</bold> - Causes the AI to forget
a specific piece of information it knows about the actor,
such as the actor's favorite color.
</li>
</ul>
<p>
An AI manages its database, occasionally deleting old data.
You should be aware that:
</p>
<ul>
<li>
AIs will remember every player character they meet. They <bold>won't
ever forget a player character,</bold> unless the player character
is deleted from the database, gDatabaseActors. This ensures
that players won't ever feel cheated by NPCs forgetting them.
</li>
<li>
AIs will also remember other NPCs. They may, however, <bold>forget
about the NPCs if they are deleted</bold>, even if the NPCs
are backed up to a database.
</li>
<li>
AIs will only store about <bold>200 pieces of information</bold> on a character before
deleting old ones. The limit is set by gAIMemoryTrim.
</li>
<li>
If a character hasn't been ecountered for <bold>more than
two weeks</bold> by the AI, the memory limit is set to gAIMemoryTrim / 2.
</li>
<li>
<bold>Important:</bold> If you want a character (that uses cAIMemory)
in a multiplayer world to <bold>remember across reboots</bold> then you
need to also base the character off cSaveToDatabase (see the turtorial).
</li>
</ul>
<br/><section><p><bold><big>Advanced memory</big></bold></p></section>
<p>
You might also wish to do the following:
</p>
<ul>
<li>
Sometimes you will want to have one NPC in two or more different rooms at
once. This often happens when you create one copy of a village that exists
before an orc attack, and another version after the orc attack. You may
want a few NPCs to survive the orc attack. Thus, they need to be in two
places at once. To allow for this, create two copies of the NPC. However,
you'll need to have their memories of player characters stay in sync.
To do this, have one of NPCs alias its memory to the other NPC
by using <bold>pAIMemoryAliasTo</bold>.
</li>
</ul>
A class that can be used to create factions and guilds for PCs and NPCs.
<section><p><bold><big>06. Factions - cFaction</big></bold></p></section>
<p>
A <bold>faction</bold> is a group of characters, either NPCs or PCs.
Factions are also known as guilds, but are not limited to
official organizations. Members of a village or town could all
belong to a faction "MemberOfTownXXX". Likewise, members of
a race or culture could all belong to a faction.
</p>
<p>
Making NPCs a member of one or more factions is useful for
the following reasons:
</p>
<ul>
<li>
NPCs will be able to <bold>derive knowledge</bold> that's "known" to the
faction. For example, the villagers of Hermansville (a faction)
all know where the temple is in Hermansville, and all know
about the goblin raiders to the north.
</li>
<li>
When a <bold>player character befriends a NPC in a faction</bold>, he
ever-so-slightly increases the reaction of all members of
the faction. Thus, is a player makes friends with half the NPCs
in town, the other NPCs will be friendly also. This works
in reverse.
</li>
<li>
<bold>NPCs can have "enemy" factions.</bold> If a player character befriends
an enemy faction (such as "Enemies of Hermansville"),
he will not get as good of a reaction (from "Villagers of
Hermansville").
</li>
</ul>
<p>
Players may wish to be part of a faction for the following reasons:
</p>
<ul>
<li>
<bold>Standard MUD/MMORPG guid features</bold>, such as guild E-mail.
</li>
<li>
If a PC is a member of a faction, and he makes a good impression
on an NPC, <bold>the NPC's reaction will improve slightly for
all members of the faction.</bold> Thus, if you're a member
of a guild, your actions affect how all the other guild members
are seen, positively or negatively.
</li>
</ul>
<br/><section><p><bold><big>Creating a faction</big></bold></p></section>
<p>
To create a faction:
</p>
<ol>
<li>
<bold>Create an object based on cFaction.</bold> Since cFaction
is based on cAIMemory, the faction's information will automatically
be saved across reboots.
</li>
<li>
Make sure the object <bold>is an object</bold>, and not a class.
</li>
<li>
Fill in the faction's <bold>pNLPNounName</bold> and <bold>pNLPParseName</bold>,
as usual.
</li>
<li>
<bold>pFactionSize</bold> should be filled with the guestimated
number of members of the faction. The faction size controls how
much a NPC's reaction to the faction is affected by its
individual members. Thus, if a player is a member of a faction,
and makes a good impression on a NPC, the NPC's reaction
to the faction will only increase by 1/pFactionSize.
</li>
</ol>
<br/><section><p><bold><big>Advanced factions</big></bold></p></section>
<p>
You may also wish to fill in the following faction information:
</p>
<ul>
<li>
<bold>pAIConvScripts</bold> dictates what conversations scripts are
common to all members of a faction.
</li>
<li>
<bold>pAIConvStories</bold> lists all stories known by faction members.
</li>
<li>
<bold>pAIFactionAntiFactions</bold> provides an easier way for one
faction to dislike another.
</li>
<li>
<bold>pAIFactoids</bold> lists factoids known by all faction members.
</li>
<li>
<bold>pAIKnowledgeConversationStateExtreme</bold> lists conversation
states that have a large impact on faction members. For example: Talking
about "fishing" to members of the fisherman's guild.
</li>
<li>
<bold>pAIKnowledgeKnow</bold> is a list of knowledge that faction
members will know.
</li>
<li>
<bold>pAILikeDefault</bold> is used by AILikeFirstImpressions().
</li>
<li>
<bold>pAILikeEquip</bold> is used by AILikeFirstImpressions().
</li>
<li>
<bold>pAILikeForget</bold> controls how quickly factions forget about
player characters.
</li>
<li>
<bold>pAILikeGender</bold> and <bold>pGender</bold> are used by AILikeFirstImpressions().
</li>
<li>
<bold>pAILikeRace</bold> is used by AILikeFirstImpressions().
</li>
<li>
<bold>pAILikeReputation</bold> controls how much a player's reputation
affects faction members.
</li>
<li>
<bold>pAILikeRoom</bold> causes the NPC to like the player more
if the NPC is in a specific room.
</li>
<li>
<bold>pAILikeSkills</bold> is used by AILikeFirstImpressions().
</li>
<li>
<bold>pAnonymous</bold> causes the faction to be known to only
a few NPCs.
</li>
<li>
If you se <bold>pIsInvisible</bold> to TRUE then players won't be
able to see the faction when they type, "What is my reputation?"
</li>
<li>
Set <bold>pAIFactionIsHobby</bold> to TRUE to use the faction object
as a NPC hobby, such as fishing, bowling, or Star Trek fan.
</li>
<li>
<bold>AILikeEquip()</bold> determines what kind of equipment faction
members like, such as black robes, etc.
</li>
<li>
<bold>AILikeFirstImpressions()</bold> controls how much faction members
like other characters.
</li>
</ul>
Remember a player character's reputation.
<section><p><bold><big>Reputations</big></bold></p></section>
<p>
As discusses in the factions tutorial, a player character's reputation
precedes him because of the memory stored in the cFaction object.
</p>
<p>
You might also wish a faction-independent method for storing
a reputation, such as how heroic the character is, or perhaps
his wealth.
</p>
<p>
The faction-independent reptutation system works as follows:
</p>
<ol>
<li>
Come up with a <bold>lower-case language-independent</bold> string
to represent the character's reputation, such as "goodfisherman" to
indicate how high the player's reputation is based on his fishing
skills. (Make sure that you don't have any spaces.)
</li>
<li>
You will need to make a global, <bold>gReputationNameGoodFisherman</bold> with
a language-dependent name of the reputation, like "Good fisherman".
</li>
<p>
This global will be accessed by calls
to <bold>ReputationToName()</bold>, which is used when the
player types, "What is my reputation?"
</p>
<li>
As the player character completes quests (or whatever), you can
increase (or decrease) the reputation by
calling <bold>ReputationSet()</bold>, passing in "goodfisherman" and
a value from -10 (lower the reputation) to 10 (increase the reputation).
</li>
<li>
To make NPCs that are impressed by a reputation, provide
a <bold>pAILikeReputation</bold> property for the NPC.
</li>
<li>
Likewise, to make all NPCs in a faction impressed by a reputation,
set the <bold>cFaction.pAILikeReputation</bold>.
</li>
<li>
You may also wish to write your
own <bold>AILikeReputation()</bold> method for NPCs or their factions.
</li>
</ol>
<p>
That's all! As a player's "goodfisherman" reputation improves, NPCs
will like (or trust) the player character more.
</p>
Talks about APIs use to get and set how much an AI likes/dislikes and trusts/mistrusts a character.
<section><p><bold><big>AI like and trust of characters - cAI</big></bold></p></section>
<p>
As players wander through an interactive fiction title, they
will encounter NPCs. In most text IF and MUDs, NPCs instantly
forget about PCs as soon as the PCs leave the room. Not so
with Circumreality. NPCs remember a variety of information about the PCs
in the cAIMemory class.
</p>
<p>
Two of the most commonly remembered pieces of information are
whether or not the NPC liked the PC, and whether or not the NPC
trusts the PC. "Like" can be used to determine how much information
the NPC will reveal to the PC. "Trust" indicates what type
of tasks the NPC will give to the PC.
</p>
<p>
Both the like and trust memories are stored as values, ranging
from -10 to 10. Here is a sample of what the values mean:
</p>
<table width=100%>
<tr>
<td/>
<td><bold>Like</bold></td>
<td><bold>Trust</bold></td>
</tr>
<tr>
<td>+10</td>
<td>
The NPC likes the PC a lot, and will do anything for the PC.
</td>
<td>
The NPC trusts the PC with his/her life.
</td>
</tr>
<tr>
<td>+6</td>
<td>
The NPC would consider the PC a friend.
</td>
<td>
The NPC trusts the PC with important tasks.
</td>
</tr>
<tr>
<td>+3</td>
<td>
The NPC thinks of the PC as a friendly acquaintance.
</td>
<td>
The NPC will give the PC minor tasks whose failure
won't signficantly hurt the NPC.
</td>
</tr>
<tr>
<td>0</td>
<td>
Neutral
</td>
<td>
Neutral
</td>
</tr>
<tr>
<td>-3</td>
<td>
The NPC dislikes the PC.
</td>
<td>
The NPC might give the PC a task, just to see what happens,
but won't expect it to be completed.
</td>
</tr>
<tr>
<td>-6</td>
<td>
The NPC considers the PC an enemy.
</td>
<td>
The NPC doesn't trust the PC as far as he can throw him.
</td>
</tr>
<tr>
<td>-10</td>
<td>
Hatred.
</td>
<td>
The NPC thinks the PC is completely incompetent.
</td>
</tr>
</table>
<p/>
<p>
To find out how much a NPC likes or trusts a PC (or other NPC),
call oNPC.<bold>AILikeGet()</bold>. This will return a list
with the first element being the like value, and the second
with the trust value.
</p>
<p>
You can adjust how much a NPC likes or trusts a PC (or other
NPC) by calling oNPC.<bold>AILikeSet()</bold> whenever
the PC performs an action that gives a positive or negative
impression of the PC to the NPC. The values passed into AILikeSet()
for like/trust depend upon how significant the player's actions
are:
</p>
<table width=100%>
<tr>
<td/>
<td><bold>AILikeSet() Like</bold></td>
<td><bold>AILikeSet() Trust</bold></td>
</tr>
<tr>
<td>+10</td>
<td>
PC does something for the NPC that the NPC has always desired,
but not even the NPC's best friends have done it.
For example: Giving the NPC $1,000,000.
</td>
<td>
The PC saves the NPC's life, or completes a very important
task.
</td>
</tr>
<tr>
<td>+6</td>
<td>
The PC does something very nice for the NPC.
</td>
<td>
The PC completes a major task for the NPC.
</td>
</tr>
<tr>
<td>+3</td>
<td>
The PC does something nice for the NPC.
</td>
<td>
The PC completes a minor task.
</td>
</tr>
<tr>
<td>+1</td>
<td>
The PC smiles at the PC, or some other polite formality.
</td>
<td>
The PC pays a bill, or some other small task.
</td>
</tr>
<tr>
<td>-1</td>
<td>
The PC doesn't smile at the PC, or some other small social mistake.
</td>
<td>
The PC is late for an appointment.
</td>
</tr>
<tr>
<td>-3</td>
<td>
The PC comes off as being unintentionally rude.
</td>
<td>
The PC misses an appointment.
</td>
</tr>
<tr>
<td>-6</td>
<td>
The PC is intentionally rude or mean.
</td>
<td>
The PC fails to complete an important task.
</td>
</tr>
<tr>
<td>-10</td>
<td>
The PC commits an awful offense.
</td>
<td>
The PC attacks the NPC.
</td>
</tr>
</table>
<p/>
<br/><section><p><bold><big>AILikeSet() intricacies</big></bold></p></section>
<p>
AILikeSet() is not a simple function that just instantly changes
the NPC's view of the PC. It handles quite a few smaller details:
</p>
<ul>
<li>
Calling AILikeSet() will increase or decrease the PC's
like/trust based on the value passed in.
It will <bold>not</bold> change
the NPC's like/trust immediately to the new values, but
will <bold>gradually increase/decrease the NPC's like/trust each time
AILikeSet() is called.</bold> Therefore, for a PC to make a NPC a friend,
the PC will need to be repeatedly nice to a NPC.
</li>
<li>
AILikeSet() <bold>will not adjust a NPC's like/trust higher than
the like/trust parameter</bold> (if the parameter is a positive value),
or lower than the like/trust parameter (if the parameter is
negative). This means that smiling (a +1.0 like parameter) will
only get a player so far, and smilling a thousand times will
not make the player best friends with the NPC. To become best
friends the PC will have to do several best-friends activities
first.
</li>
<li>
Some NPCs will have their like/trust adjusted more quickly
than others, depending upon
their <bold>pAILikeChangable</bold> attribute. If the values
are high, the NPC will quickly take a liking (or dislking) to
a PC. If they're low, the NPC is slow to change its mind.
</li>
<li>
If the NPC belongs to any factions (which is probable),
then <bold>AILikeSet() will subtly affect all NPCs in
the same faction.</bold> Word gets around, and being likable
or trustworthy
to a few villagers in town will gradually improve a PC's like/trust
to all viallager.
</li>
<li>
If the PC belongs to any factions (which is probable),
then <bold>AILikeSet() will subtly affect the NPC's reaction
to other members of the faction.</bold> If a PC is a member
of the "XYZ Guild" and makes a good impression on the NPC,
the other members of the XYZ Guild will get a better
response. This works the opposite way too; making a bad
impression reflects on the entire guild.
</li>
<li>
Likewise, <bold>any factions that the NPC belongs to will form
an opinion about any factions that the PC belongs to.</bold> If
a NPC from XYZ Guild upsets a NPC in Hermansville, then
all NPCs in Hermansville will have (mildly) negative reactions
to all members of the XYZ Guild.
</li>
<li>
NPCs (and factions) gradually forget about offenses and favors
that a PC (or faction) does. The speed of this forgetfullness
(or forgiveness) is controlled by <bold>pAILikeForget</bold>.
</li>
</ul>
<br/><section><p><bold><big>AILikeGet() intricacies</big></bold></p></section>
<p>
AILikeGet() also has some intricate behavior that you should
be aware of, especially when the NPC first meets the PC:
</p>
<ul>
<li>
Some NPCs are more likely to "follow the crowd" while others
are more independent thinkers. This personality trait manifests
itself when a NPC has met several PCs from a faction (XYZ Guild),
and then starts liking/trusting (or disliking/mistrusting) all
members of the guild based on the few meetings. If the
NPC's <bold>pAILikeIndividual</bold> is high, the NPC will
assume each PC is different and won't stereotype the PCs
in XYZ Guild based on other members of XYZ Guild. If pAILikeIndividual
is low, the NPC assumes that all members of XYZ Guild are
more or less the same as far as likability and trustworthiness goes.
</li>
<li>
If the NPC has never met the PC, the like/trust values
default to <bold>pAILikeDefault</bold>. Some NPCs are naturally
friendly while others may naturally be untrustworthy.
</li>
<li>
The NPC can also form an opinion based upon what the
PC is carrying,
using <bold>pAILikeEquip</bold> and <bold>pAILikeEquipValue</bold>. Some AI's
might like/trust wealthy characters, ones with weapons, or
ones wearing red shoes.
</li>
<li>
If the NPC is in the same faction as the PC, such as
them bothing coming from the same village,
then <bold>pAILikeFaction</bold> affects how much the
NPC's like/trust is affected by common factions.
</li>
<li>
<bold>pAILikeGender</bold> lets a NPC be more/less
friendly/trusting with members of the same/opposite gender.
</li>
<li>
<bold>pAILikeRace</bold> will cause a NPC to like/dislike
and trust/mistrust PCs from other races. Elves might dislike
orcs, and mistrust dwarves.
</li>
<li>
<bold>pAILikeRoom</bold> causes the NPC to like/trust the
PC more if the NPC is in a specific room, and feels more
comfortable there.
</li>
<li>
<bold>pAILikeSkills</bold> can be used to make NPCs like/trust
charismatic PCs, strong PCs, or PCs with degrees in
quantum mechanics.
</li>
<li>
If the NPC's factions list, <bold>pFactions</bold>, includes
"anti-factions", then the more the PC is liked by the anti-faction,
the more the PC will be disliked by the NPC.
</li>
</ul>
<br/><section><p><bold><big>Emotes based on like/trust</big></bold></p></section>
<p>
When a PC says or does something that causes a NPC's perception
of the PC to change, AILikeSet() is called. One of the parameters
allows the NPC to emote based on how much the like/trust changed.
For example: If the PC says something nice and improves their "like"
score with the NPC, the NPC could emote a brief smile, while a
small social mistake might cause a decrease in "like" and make
the NPC purse its lips slightly.
</p>
<p>
To do this, AILikeSet() calls <bold>AILikeEmoteChanged()</bold> with
modified versions of the like/trust parameter. The default method
includes a variety of short smiles, lip pursing, and flashes
of anger. You may wish to replace this functionality for some NPCs...
One NPC could clear his throat to indicate displeasure. Another
could mutter something under his breath.
</p>
<p>
A NPC's like/trust is also shown when the NPC sees a PC
enter the room. This calls <bold>AILikeEmoteFirstMeet()</bold>.
If a PC is universally disliked within the town he will get
glares as he walked through the streets.
</p>
<p>
Similarly, if the NPC walks into a room with other PCs, the NPC will
find the most liked/trusted (or least liked/trusted) PC in
the room and call <bold>AILikeEmoteFirstMeet()</bold> to
emote.
</p>
<p>
AILikeEmoteFirstMeet() is also called when a conversation between
a NPC and a PC is started. The AI's reactions will be more
severe than if the AI is just passing the PC on the street.
</p>
<p>
As with AILikeEmoteChanged(), you may wish
to override <bold>AILikeEmoteFirstMeet()</bold> as appropriate
for the NPC.
</p>
<br/><section><p><bold><big>Personality traits</big></bold></p></section>
<p>
You may have noticed, but I just mentioned about ten properties
that are "personality traits", such as how quickly the AI forgets
and forgives, whether it likes men more than woman, etc.
More are yet to come.
</p>
<p>
You can set the properties for NPCs to fine-tune their
personality. However, if you <bold>do not specify</bold> the
personality properties, random and reasonable values
will be generated when the NPC is created.
</p>
AIs can have emotions for happy/sad and anger/fear.
<section><p><bold><big>Emotions - cAI</big></bold></p></section>
<p>
AI's store two emotions: <bold>Happy/sad</bold> and <bold>Angry/afraid</bold>. Each
emotion ranges from -10 (very sad or very afraid) to +10 (very happy
or very angry).
</p>
<p>
You can get the AI's emotions using <bold>AIEmotionsGet()</bold>.
</p>
<p>
The emotions can be adjusted or set
with <bold>AIEmotionsSet()</bold>. This either sets the emotions
directly, or it adjusts the emotions, making the NPC happier, sadder,
angrier, or more afraid. The amount that a NPC's emotions
are changed is affected by <bold>pAIEmotionsChangable</bold>,
allowing for some NPCs to be more excitable than others.
</p>
<p>
Emotions gradually return to 0 over time. (Actually, they
eventually return to the AI's <bold>pAIEmotionsDefault</bold> over
time.) The speed of this return is controlled
by <bold>pAIEmotionsCalmDown</bold>.
</p>
<p>
You may have the AI's emotions changed based on conversations
the PCs are having with the AI, or good/bad news that the AI
hears, etc.
</p>
<p>
An AI's emotions are automatically adjusted during combat.
When the AI determines what its odds are for winning, this
is expressed on the AI's face as either anger (confident of
winning) or fear (almost ready to run).
If an AI sees an enemy killed or knocked unconscious it becomes
happier, and vice versa for friends.
</p>
This tutorial provides the details about how conversations with NPCs work.
<section><p><bold><big>NPC conversations - cAI</big></bold></p></section>
<p>
In Circumreality, players converse with NPCs using the chat window, the
same way that players talk with other players. The NPCs use
simple natural language processing (NLP) to understand what
the player is asking for.
</p>
<br/><section><p><bold><big>Programatically starting up a conversation</big></bold></p></section>
<p>
When an AI hears speech (PerceiveSpeak()), or sees an emote
(PerceiveEmote()), or the PC identifies himself (PerceiveIdentifySelf()),
it's first task is to determine if the PC is talking to the AI
or another PC or NPC in the room. To do this, the AI
calls <bold>AIConversationIsTalkingToMe()</bold>. This method
uses various tricks to determine if the PC is talking with the AI.
If it is, a conversation is started up (see below) and the
method returns TRUE. If not, the method returns FALSE.
You will probably <bold>not</bold> need to call AIConversationIsTalkingToMe()
though.
</p>
<p>
If AIConversationIsTalkingToMe() starts up a conversation,
it calls <bold>AIConversationStart()</bold>. This method adds
an entry to <bold>pAIConversations</bold> so the AI knows
what characters it's talking to. It also causes
AILikeEmoteFirstMeet() to be called so that players can tell
how much the AI likes them. Finally, it adds
the <bold>oAIGoalConversation</bold> goal to the AI at
priority level 10. This causes the AI to stop doing what it
was doing until the conversation is finished (unless there
is a higher priority goal). The conversation will be finished
when the PC says "Goodbye" (or the equivalent), or leaves the
room.
</p>
<p>
You may wish to call AIConversationStart() if you wish your AI
to start up a conversation with a PC on its own. (See below.)
</p>
<p>
When the AI walks into a room, or when the AI sees the player
character walk into its room, <bold>AIConversationWantToStart()</bold> is
called. This lets the AI start up a conversation with PCs.
For example: An inn keeper might ask the player if they'd
like a room tonight without the player's ever approaching
the inn keeper. The default method will
have AI's who see well-liked PCs stop and say hello.
</p>
<p>
You may wish to write your own AIConversationWantToStart() for
chatty NPCs like inn keepers, friendly NPCs who know the PC,
or beggars. You will need to call AIConversationStart() to
start up the conversation though.
</p>
<br/><section><p><bold><big>Conversing</big></bold></p></section>
<p>
When a PC speaks, emotes, or introduces itself to an AI, the
speech (or emote or introduction) is passed
onto <bold>PerceiveConversation()</bold>. The method isn't
called immediately, but has a 1 second delay. This makes for
more realistic response times, as well as ensuring two AI's
in conversation don't get into an infinite loop.
</p>
<p>
The PerceiveConversation() method does this following:
</p>
<ol>
<li>
Sets up all the <bold>rule-sets</bold> to be used by the NLPParse() function.
For more information on parsing and the NLPParseXXX() functions,
see the tutorials on command-line parsing.
</li>
<li>
Calls <bold>NLPParse().</bold>
</li>
<li>
Based on the parse results, it calls
into <bold>ParseC_XXX()</bold> to determine if the parse makes sense.
Command-line parsing calls into Parse_XXX() methods.
</li>
<li>
The ParseC_XXX() method with the highest score is chosen,
and its <bold>ParseActC_XXX()</bold> method is called to have
the AI react to what the player said.
</li>
<li>
The AI may respond to the player's question, and in turn
ask one of it's own by calling <bold>AIConversationAskQuestion()</bold>. For
example, the AI may ask the PC his name.
</li>
</ol>
<p>
Now for some more details on this process:
</p>
<br/><section><p><bold><big>Rule sets</big></bold></p></section>
<p>
For the natural language processor to work, it must have
a number of <bold>"rule sets" loaded into a "parser".</bold> In the case
of conversations, the parser name used is gConversationParser.
The rule sets can be customized for each NPC, but the
default behaviour is to include the following rule sets:
</p>
<ul>
<li>
<bold>rParserCommon</bold> - These are rules that are common
to both the command-line parser and the conversations. They
do parse converstions like "colour" to "color", and
"we're" to "we are".
</li>
<li>
<bold>rParserConversation</bold> - Parsing rules that all
AI's understand. These include "What time is it?" and "Do
you know where NAME lives?".
</li>
<li>
<bold>Pronouns</bold> - Rules that convert pronouns, such
as "it" and "him", to object IDs. This allows conversations
to use the pronouns. For more information on
pronouns in a conversation
see <bold>AIConversationPronounSet()</bold> and <bold>AIConversationPronounSetString()</bold>.
</li>
<li>
<bold>NPC and speaker possessions</bold> - The names of all
objects held by the NPC and the speaker (but not those in
containers). This allows conversations to reference objects
held by the NPC or the player.
</li>
<li>
<bold>Names that the NPC knows</bold> - A list of all the
PC names that the AI has learned when the characters
identify themselves. The rules convert the names into
the character's object ID. These are the names from CustomNameGet()
and CustomNameSet().
</li>
<li>
<bold>Names of all NPCs on the map</bold> - A list of all
NPCs in the NPC's original map, so that their names are
converted to object IDs. It's useful for villages and cities (which
are all one one map), so that NPCs will know about other
NPCs in the city. See <bold>pAnonymous</bold> for information
on making a NPC unknown to other NPCs.
</li>
<li>
<bold>Names of all rooms on the map</bold> - A list of all
rooms in the NPC's original map, so that their names are
converted to object IDs. It's useful for villages and cities (which
are all one one map), so that NPCs will know where the "inn" is,
and where the "church" is. See <bold>pAnonymous</bold> for information
on making a NPC unknown to other NPCs.
</li>
<li>
<bold>Names of all zones, regions, and mapps</bold> - Lets the NPC answer
questions about specific zones, regions, and maps.
</li>
<li>
<bold>Names of all factions</bold> - Lets the NPC answer
questions about specific factions.
</li>
<li>
<bold>Names of all skills</bold> - Lets the NPC answer
questions about specific skills.
</li>
<li>
<bold>pAIConversationParser</bold> - This property may
provide additional parsers for the NPC's AI. The extra
parsers can, in turn, add new rule sets. (See below.)
</li>
<li>
<bold>Factions</bold> - If the NPC is a member of any factions,
the NPC's factions' AIConversationParser() method will be
called. This can add rule sets of its own, which are specific
to a faction. For example: Members of the "Leatherworkers guild" might
include rule sets that allow players to ask about leatherworking.
</li>
<li>
<bold>AIConversationParser()</bold> - NPCs can provide their
own AIConversationParser() methods, which can add new rule
sets. (See below.) For example: AI's from the cMerchant class
might include rule sets for "How much does XXX cost?" and "I
want to buy XXX."
</li>
</ul>
<p>
cAI.PerceiveConversation() adds these parser's by calling
into the AI's <bold>AIConversationParser()</bold>. This method
adds many of the above rules into the gConversationParser parser.
It also calls <bold>oParserConversation.AIConversationParser()</bold>,
which adds the rest. This object supplies most of the conversation
code and methods that are common to all AIs, so that each AI
doesn't have to keep track of the hundreds of conversation methods,
such as ParseC_XXX() and ParseActC_XXX(). It provides a similar
role to oParserVerb for commands. More rule sets are also
added by calling AIConversationParser() for each of the factions
that the AI is a member of, allowing the AI to "know" information
specific to the faction. Since AIConversationParser() is
layerable, sub-classes of the AI, such as cMerchant, can
also provide extra rule sets.
</p>
<p>
When AIConversationParser() is called in an object, it does
the following:
</p>
<ol>
<li>
<bold>Identify the rule sets</bold> it wishes to activate,
such as <bold>rParserCommon</bold> from above.
</li>
<li>
<bold>See if the rule set is already loaded into the parser</bold> by
calling <bold>NLPRuleSetExists()</bold>.
</li>
<p>
(In order to prevent
resources from piling up, rule sets will be deleted from time to
time, so it's always a good idea to make sure the rule set
still exists, even if you're sure it was created for the last
conversation.)
</p>
<li>
If the rule set exists, the method should
call <bold>NLPRuleSetEnableSet()</bold> to make sure the rule
set is active.
</li>
<p>
Before PerceiveConversation() calls the AIConversationParser()
methods, it will have <bold>disabled all of the rule
sets</bold> in gConversationParser.
</p>
<li>
If the rule set does not exist, create it and
call <bold>NLPRuleSetAdd()</bold>, followed
by <bold>NLPRuleSetEnableSet()</bold>.
</li>
<li>
If the object provides any ParserC_XXX() methods, which
it probably does, then <bold>add the object to the ParserObjects
list</bold> that's passed into the method. (See below.)
</li>
</ol>
<br/><section><p><bold><big>Finding the best parse</big></bold></p></section>
<p>
Once AIConversationParser() has been called, and all the rule
sets have been added, PerceiveConversation()
calls <bold>NLPParse()</bold> to produce a list of possible
parses for what the player said.
</p>
<p>
The <bold>first word</bold> of each hypothesis is examined:
</p>
<p>
If it begins with a "`", then ParseC_XXX() is called for each
of the objects added to ParserObjects by AIConversationParse().
XXX is the string after the "`". Thus, if the character said,
"My name is Fred", one of the hypothesis would be "`identifyself Fred".
This would cause ParseC_IdentifySelf() to be called for
each of the objects in ParserObjects. (More about this later.)
</p>
<p>
If the first word has no "`", then the method(s) called are
determined by the current conversation state.
(See <bold>AIConversationStateSet()</bold>). The method
used will be "ParseC_State_XXX", where XXX is replaced with
the state name. Thus, if the NPC asked the player, "What is your
name?", it could set the converstion state to "AskedName". Then,
if the player replied, "My name is Fred", this would result in
a "`identifyself Fred" parse, that would call ParseC_IdentifySelf().
However, if the user replied with "Fred", the first word
of the parse wouldn't have a "`", so ParseC_State_AskedName() would
be called. (If the NPC had asked for the PC's favorite color,
it could use the "AskFavoriteColor" state, so as to not
accidentally think that the PC's name was "red" or that his
favorite color was "Fred".)
</p>
<p>
The ParseC_XXX() methods accept the following parameters:
</p>
<ul>
<li>
<bold>Actor</bold> - The AI being spoken to.
</li>
<li>
<bold>Speaker</bold> - The character doing the speaking.
</li>
<li>
<bold>CommandTokens</bold> - A list of words spoken. Some of the
words may be converted to objects. Thus, if the user said,
"I like your lantern", this might be ["`ilike", oLantern],
whereas "My name is Fred" would be ["`identifyself", "Fred"].
</li>
<li>
<bold>BestParse</bold> - This is the best parse for the CommandTokens
so far. If the ParseC_XXX() method comes up with a higher score,
it should overwrite the elements of BestParse(). See below.
</li>
<li>
<bold><italic>Returns</italic></bold> - None.
</li>
</ul>
<p>
The ParseX_XXX() method does the following:
</p>
<ol>
<li>
It <bold>examines the CommandTokens</bold> to see if can
make any sense of them.
</li>
<li>
If it <bold>can't make any sense of the tokens</bold> then
the method should just return without changing BestParse().
</li>
<li>
If the tokens do make sense, then the method needs
to <bold>calculate a score</bold> for how much they make sense.
Use 1.0 for a statement that makes perfect sense,
such as ["`identifyself", "Fred"]. If it makes less sense,
such as ["`identifyself", "a", "new", "person", "here"] then
return a lower value, like 0.5. If it makes very little
sense then use an even lower number.
</li>
<li>
If the <bold>score is more than BestParse[0]</bold>, then you'll need to
replace the contents of BestParse. If it isn't then
just return. (BestParse[0] will be Undefined if there hasn't
been a good parse yet.)
</li>
<li>
Replace <bold>BestParse[0] with the score</bold> you calculated.
</li>
<li>
<bold>BestParse[1] should contain the conversation state(s)</bold> that
this response is valid for. This is either a single lower-case string,
or a list of lower-case strings. They can contain
wildcards. (See <bold>ConversationStateCompare()</bold>.)
</li>
<p>
If the conversation states in BestParse[1] do <bold>not</bold> match
any of the current conversation state the the AI is listening for
then the score will be automatically reduced. This way, if
a player's response is ambiguous, the AI can use the expected
conversation state to disambiguate. Example: If the AI asks the
player a question and the player answers, "Yes, I do", resulting
in in ["`answeryes"], the BestParse[1] state will allow the
AI to determine if the "Yes" belonged to "Do you like me?"
("askiflikeme" conversation state) or "Do you like eating turkey?"
("askifliketurkey" conversation state).
</p>
<p>
Furthermore, if the player responds with a question or answer
that isn't in the AI's current conversation state, this will
annoy the AI slightly and reduce the AI's like/trust for
the PC. A change in topic is determined by comparing the
BestParse[1] state with the AI's converastion state
set in <bold>AIConversationStateSet()</bold>.
</p>
<li>
<bold>BestParse[2] should be filled with a callback
that acts on the speech</bold>. Usually, this will
be "this.ParseActC_XXX", where XXX is the first word
in the parse, such as "this.ParseActC_IdentifySelf".
</li>
<li>
<bold>BestParse[3] and higher</bold> are passed into
the BestParse[2] callback. Their meaning is up to the
BestParse[2] callback.
</li>
</ol>
<p>
After all the ParseC_XXX() methods have been called, the
one that filled in <bold>the highest BestParse[0] will be kept</bold>.
NOTE: The values of BestParse[0] are adjusted down if
the conversation states in BestParse[1] do not match
the AI's current conversation state,
from <bold>AIConversationStateGet()</bold>. The BestParse[0]
score is also multiplied by the score produced by
the rule-set parsing.
</p>
<br/><section><p><bold><big>Responding to the parse</big></bold></p></section>
<p>
If no parse was successful, then <bold>AIConversationDontUnderstand()</bold> is
called. This will cause the AI to answer with,
"Sorry, I don't understand.", or something to the effect.
</p>
<p>
Otherwise, the callback from BestParse[2] will be called.
It should accept the following parameters:
</p>
<ul>
<li>
<bold>Actor</bold> - The AI being spoken to.
</li>
<li>
<bold>Speaker</bold> - The character doing the speaker.
</li>
<li>
<bold>ParseList</bold> - The values from BestParse[3] and
higher. The meaning depends upon how the method wishes
to interpret them.
</li>
<li>
<bold><italic>Returns</italic></bold> - See below.
</li>
</ul>
<p>
The ParseActC_XXX() method should do the following:
</p>
<ol>
<li>
<bold>Have the NPC speak or act</bold> based on how it
interprets the speech.
</li>
<li>
The NPC might wish to call <bold>AILikeSet()</bold> if
what the player says causes the NPC to change his opinion
of the player.
</li>
<p>
Tip: If something that the player says positively
impaces the AI's view of the PC, make sure to use AIMemoryGet() and
AIMemorySet() to remember the event. Otherwise, players will discover
that every time the compliment a NPC about their hair, the
NPC's like/trust will go up. You'll find players repeated complimenting
a NPC about its hair until the NPC is super-friendly.
</p>
<li>
If the conversation sentence mentions an object that might
be used as a pronoun later, make sure to
call <bold>AIConversationPronounSet()</bold>.
</li>
<li>
If the AI's converation state won't change based on the sentence,
then <bold>return NULL</bold>. This is particularly useful
for emotes, since many times an emote will just be ignored
by the AI, and it will sit waiting for some speech.
</li>
<li>
If the <bold>AI always responds to this parse with
a question,</bold> then <bold>code the question</bold> into this method and
call <bold>AIConversationStateSet()</bold> to set the new state.
Then, <bold>return TRUE</bold>.
</li>
<p>
Example: If this method handles the player asking, "Where is
the cave of Wigeywom?" and the NPC's reaction to this is
always, "Why do you want to know?", then this response should
be coded in, and the new conversation state should be set to
"npc_asks_why_want_to_know_wigeywom".
</p>
<li>
If the AI has <bold>no particular follow-up response</bold> then
return the new conversation state. This can either be a single
lower-case string, or list of lower-case strings. Normally,
the like/trust penalty for the PC jumping to a different
topic is [-0.5, 0]. If you wish a different pentaly, then
return a list of states, but set the first element of the
list to your [Like, Trust] change.
</li>
<p>
For example: The NPC asks the PC, "What's your name?". This
method interpret's the player's answer of "My name is Fred."
Since nothing logically follows, returning a new state
will allow the AI's intelligence to select a new topic
of conversation by calling <bold>AIConversationQuestionAsk()</bold>.
See below for details.
</p>
</ol>
<br/><section><p><bold><big>Conversation questions</big></bold></p></section>
<p>
Conversations are not just an interrogation where the player
asks the NPC a series of questions and the NPC dutifully answers.
NPCs have their own agendas... When a conversation is first
begun, the NPC AI code initializes a list of "questions" that the
NPC wants to ask the PC.
</p>
<p>
These "questions" can also include statements. Some examples
of questions are:
</p>
<ul>
<li>
<bold>Greeting</bold> - When a conversation first starts the
NPC will say "Hello" (or similar) to the PC.
</li>
<li>
<bold>What is your name?</bold> - If the player has not
given his name, the NPC will ask for it. (Depending upon the
NPC's personality.)
</li>
<li>
<bold>My name is X.</bold> - The NPC may wish to identify itself
to the player.
</li>
<li>
<bold>Rumors</bold> - If there's a hot rumor going around,
or even just the latest sports trivia, the NPC may find a good
opportunity to relate it.
</li>
<li>
<bold>Friendly questions</bold> - The NPC may ask some friendly
questions, such as, "Where do you come from?", "Do you have
any family nearby?", etc.
</li>
<li>
<bold>Interrogating questions</bold> - The NPC may wish to learn
more about the player, and might have questions to ask
like, "To what duke does your allegiance lie?".
</li>
</ul>
<p>
To programatically add a question to the NPC's list,
call <bold>AIConversationQuestionAdd()</bold>. You'll need to
pass in a few parameters. Some parameters to note are:
</p>
<ul>
<li>
<bold>Priority</bold> - Higher priority questions are more likely
to be asked.
</li>
<li>
<bold>ConversationState</bold> - The NPC will try to bring
up questions that are appropriate to the current conversation
state. If the player brings up fishing, the AI is more likely
to mention the latest rumor about the giant turtle in the lake.
If the conversation states don't match then the effective
priority will be about 1/4 of the priority value.
</li>
<li>
<bold>Callback or speak string</bold> - If the question is chosen
to be spoken, then a callback will be called. (See below.) If
you don't need a callback, then just pass in a string to be
spoken.
</li>
<p>
The callback accepts the following parameters:
</p>
<ul>
<li>
<bold>Actor</bold> - NPC/AI object.
</li>
<li>
<bold>Speaker</bold> - Character (PC) that the AI is speaking to.
</li>
<li>
<bold>Parameter</bold> - A parameter passed in
when AIConversationQuestionAdd() is called.
</li>
<li>
<bold><italic>Returns</italic></bold> - The callback needs to
return the new conversation state(s) to use after the
question (or rumor) has been spoken. This is either a lower-case
string, or a list of lower case strings. Normally, the non-sequiter
penality for [Like, Trust] is [-0.5, 0]. If you wish to change this,
then return a list of conversation states, with the first element
being the [Like, Truest] change for a non-sequiter.
</li>
</ul>
</ul>
<p>
<bold>You may wish to call AIConversationQuestionAdd() in some of
the ParseActC_XXX() methods</bold> if the player speaks something
that trigger's the NPC's memory or curiosity. For example: If the
player mentions "The order of the rising baloon", the NPC may
become suspicious of the PC and wish to ask questions that
allay or verify the suspicions. One way to do this would be
to use AIConversationQuestionAdd() to add a number of questions
about "The order of the rising baloon".
</p>
<p>
You can also call <bold>AIConversationQuestionRemove()</bold> to
remove a question from the list.
</p>
<p>
Most questions are added in
the <bold>AIConversationQuestionFill()</bold> method. This method
(part of the cAI class) is called whenever a conversation is
started up. It is passed +1 is the AI initiates the conversation,
and -1 if the PC initiates the conversation. (It's also called
if all the AI's questions have been used up and it needs new ones.
A value if 0 is then passed in.)
</p>
<p>
The default implimenation of AIConversationQuestionFill() calls
the same method in all the AI's factions, as well as those
objects listed in pAIConversationParser. It also calls
oParserConversation.AIConversationQuestionFill(). The method
is layerable, so sub-classes can include their own questions.
For example: You could have a cMobileLikesToTellJokes that would
occasionally tell a joke using AIConversationQuestionFill().
</p>
<p>
oParserConveration's version of AIConversationQuestionFill() will
set up some default "questions" that are common to all NPCs.
This includes a "question" that causes the NPC to say "Hello" (or
similar) and ask the player's name (depending on the AI's
personality properties).
</p>
<p>
The added <bold>questions are used when a ParseActC_XXX() method
returns a conversation state or list of conversation states.</bold> (But
not if the callback returns NULL or TRUE.)
To select a question to ask, <bold>AIConversationQuestionAsk()</bold> is
called.
</p>
<p>
This method finds the question with the highest score,
given the current conversation state. If the score is less than
a value calculated from the
AI's <bold>pAIConversationTalkativeness</bold> personality property
then the AI won't say anything. (You might be able to make the
talkativeness property for some AIs vary depending upon how
drunk they are.) Otherwise, the AI will remove the question from
the list (so it's not spoken again) and then execute the callback,
or speak the string.
</p>
<p>
In some cases, <bold>automatically removing the question may cause
some problems for a conversation</bold>. For example: If the AI asks
the PC for his name, but the PC evades the answer, the AI won't
ask for the PC's name until the next conversation. You may want
some AIs to keep asking the player character's name until the AI gets
an answer. To do this, you can play the following trick:
</p>
<ol>
<li>
Have the question callback clear the list of questions
by calling <bold>AIConversationQuestionRemove(Actor, NULL)</bold>.
</li>
<li>
The next time the AI has the opportunity to ask a question,
it will realize that there are no questions left. This
will cause it to call <bold>AIConversationQuestionFill()</bold>.
</li>
<li>
Have the AI's AIConversationQuestionFill() code test to make
sure that the question was answered. <bold>If it wasn't, add it
back in with a very high priority</bold> (such as 4.0).
</li>
</ol>
<br/><section><p><bold><big>Built-in parsers and questions</big></bold></p></section>
<p>
oParserConversation includes many built-in parsers and questions
that the AI will ask. Some of them include:
</p>
<ul>
<li>
<bold>ParseC_AskAIName</bold> allows the player to ask the
AI its name.
</li>
<li>
<bold>ParseC_Emote</bold> deals with emotes from the player.
</li>
<li>
<bold>ParseC_Goodbye</bold> lets the player say goodbye to the NPC.
</li>
<li>
<bold>ParseC_Hello</bold> allows the player to say hello.
</li>
<li>
<bold>ParseC_IdentifySelf</bold> is called when the player
identifies himself, so says, "My name is XXX."
</li>
<li>
<bold>ParseC_State_NameOnly</bold> is activated when the AI
asks the player his name. This allows a one or two word response.
</li>
<li>
<bold>QuestionC_AskPCName</bold> causes the AI to ask
the player's name.
</li>
<li>
<bold>QuestionC_GoAway</bold> tells the player to go away
and ends the conversation.
</li>
<li>
<bold>QuestionC_Goodbye</bold> causes the AI to say
goodbye and ends the conversation.
</li>
<li>
<bold>QuestionC_Hello</bold> lets the AI say hello.
</li>
<li>
<bold>QuestionC_SayName</bold> causes the AI to say
it's name.
</li>
</ul>
<br/><section><p><bold><big>Odds and ends</big></bold></p></section>
<ul>
<li>
The <bold>"anything"</bold> conversation state is used when
the AI is expecting any (normal) topic. The AI will expect this
state after the AI and PC have said hellos.
</li>
</ul>
Factoids make it easy to create a knowledge base for NPCs.
<section><p><bold><big>Factoids - cFactoids</big></bold></p></section>
<p>
TIP: There are <bold>easier ways</bold> for NPCs to answer questions.
See pAIQuestionsAndAnswers, or AIListenForEnum(), AIListenForIsValid(),
and AIListenForAct().
</p>
<p>
Factoids, based on <bold>cFactoid</bold>, make it easy to create a knowledge
base for NPCs. A factoid is an object that stores a question or
statement that the user speaks, such as "Where does King Rehald live?",
along with a response, "Kind Rehald lives in Burandon." Factoids actually store
a lot more information than the question and response, but those
are the basics.
</p>
<p>
Each faction (cFaction) maintains a list of factoid objects
known by the faction, and which are ultimately known
by the individual members of the faction.
Individuals can also have their own list of factoids, outside
of what they know from any factions they belong to.
</p>
<br/><section><p><bold><big>Creating a factoid</big></bold></p></section>
<p>
To create a factoid that handles a simple question and answer:
</p>
<ol>
<li>
<bold>Create a new object</bold> based on <bold>cFactoid</bold>. The
object's parent/container should be NULL.
</li>
<li>
Fill in <bold>pFactoidParse</bold> with the parse string
that causes the factoid to be spoken... This is the phrase
that must be spoken by the player.
</li>
<p>
At its simplest, pFactoidParse is something like,
["Where does King Rehald live?"]. However, this requires that
the player types in an exact string and doesn't take full
advantage of the parser's abilities.
</p>
<p>
A better solution would be ["`where", oKingRehald], which
can take advantage of a provided global and become,
[gFactionWhere, oKingRehald]. The rules in rParserConversation
convert "Where X?" questions into "`where X". By using
oKingRehald instead of "King Rehald", the parser interpreting
the factoid not only knows which object is being referred to,
but it knows all the object's parse names (from pNameRealParse),
so the user would be able to type "King Rehald", "Lord
Rehald", "Sirius Rehald IV", etc.
</p>
<p>
pFactoidParse supports multiple sentences too. For more information
see the documentation.
</p>
<li>
<bold>pFactoidSpeak</bold> needs to be filled in with the NPC's
response to the question, such as ["Kind Rehald lives in Burandon."].
</li>
<p>
pFactoidSpeak allows the NPC to do more than just speak. It can
cause it to whisper, yell, emote, or have the narrator speak.
For more information see the documentation.
</p>
<li>
For each faction that would know this information,
fill in the faction's <bold>pAIFactoids</bold>.
Obviously, any
subject of King Rehald would know this tidbit, as well as
neighbors and educated NPCs around the world. Example:
oFactionResidentOfBurandon.pAIFactoids = [ThisFactoid]; as
well as oFactionScholar.pAIFactoids = [ThisFactoid].
</li>
<p>
Alternatively, <bold>if you wish a NPC to know about a factoid</bold> independent
of the factions it belongs to, you need to change the
NPC's <bold>pAIFactoids</bold> to include the factoid object.
</p>
</ol>
<p>
That's all there is to factoids, at the basic level at least.
The only problem with them is that you'll need to create an
awful lot of factoids for a world populated with NPCs.
</p>
<br/><section><p><bold><big>Advanced factoid parsing</big></bold></p></section>
<p>
Factoids can handle more sophisticated parsing that what
I descibed:
</p>
<ul>
<li>
<bold>pFactoidParse</bold> can contain several different
parses, as I already mentioned. To do this, create a list
of parses, each parse being a sub-list. For example: [ [gFactoidWhere,
oKingRehald], ["where does", oKingRehald, "(live|reign)"] ].
You can even append a probability to each parse, just like
you set a probability in rule set resources.
</li>
<li>
There are several globals for who, what, why, where, how, etc.
with the names <bold>gFactoidXXX</bold>, where XXX is replaced
by "Who", etc. I suggest using to the globals to minimize
typing mistakes that will be difficult to find.
</li>
<li>
If you need to define additional rules that do not relate
immediately to the phrase, you can do so by
setting <bold>pFactoidParseSyn</bold>. For example, if
you wished to convert "the lord of the land" to "King Rehald"
then set pFactoidParseReword to [ ["the lord of the land",
"King Rehald"] ]
</li>
<li>
<bold>pFactoidParseReword</bold> is similar except that it
rewords an entire sentence.
</li>
</ul>
<br/><section><p><bold><big>Advanced factoid speech</big></bold></p></section>
<p>
There are many ways to control what is spoken, and other
NPC reactions:
</p>
<ul>
<li>
<bold>pFactoidSpeak</bold> can contain <SpeakScript> resources,
causing the NPC to whisper, emote, or include narration.
</li>
<li>
You can provide different versions of what gets spoken depending
upon the NPC's rank within the faction from which he inhereted
the factoid. This lets the common folk have one version of
events, while nobility might have another. To do this, create
a list with sub-lists for each of the alternative phrases.
Prefix each sub-list by the rank that the NPC needs to be in
order to speak that version of events.
</li>
<p>
Example: [ ["The king lives up in the palace."], [5, "King
Rehald reigns from the east wing of the Silver Palance."] ].
You don't need a number for the first sub-string since there
is no minimum rank. Any NPC up to rank 5 will speak the first
phrase. Any one with 5 or above will speak the second.
</p>
<li>
<bold>pConvElementConversationState</bold> controls what the conversation
state will be <bold>after</bold> the factoid has been spoken.
If you leave this undefined then the state will be set based
upon whatever objects can be found in pFactoidParse and
pFactoidPronoun. It also affects what the conversation state
the factoid will be brought up in.
</li>
<li>
<bold>pFactoidEmotionsSet</bold> will change the NPC's emotions
when they speak the factoid. For example: Asking a mother about
her lost child might make her sad.
</li>
<li>
<bold>pFactoidLikeSet</bold> affects how much the NPC's like/trust
of the PC changes after the PC asks the question. For example:
A slanderous comment about King Rehald might go over well with
some factions, but poorly with others.
</li>
<li>
<bold>pFactoidMemorySet</bold> can be used to set a
memory in the faction's or NPC's memory (using AIMemorySet()).
</li>
<p>
NOTE: Every time a NPC speaks a factoid, it remembers that it
spoke the factoid. The system does this to ensure that NPCs
don't speak the same factoid over and over, particularly
for questions. (See below.)
</p>
<li>
When factoids are spoken they try to keep track of pronoun
meanings just in case the player uses "he" or "it" in the
next sentence. If <bold>pFactoidPronoun</bold> has any objects,
they are used for pronouns. If the list is empty,
pronouns are derived from any objects listed
in pFactoidParse.
</li>
<li>
<bold>pFactoidConvTree</bold> will cause a conversation tree
to be started immediately after the factoid has spoken.
For more information about conversation trees, see the
cConvTree tutorial.
</li>
<li>
<bold>pFactoidQuestionFill</bold> lets the NPC's speaking of
a factoid spur on some other memories/comments that it
will add to the conversation if given a chance. Just fill
pFactoidQuestionFill with a single factoid object, or a list
of factoid objects. See below for more information about
questions.
</li>
<li>
Some factoids allow the NPC to change the conversation's
direction by using a question from the question list, while
other factoids require that the NPC wait for the player
to speak next. For example: If a factoid were, "The King lives
up in the palace.", the NPC might append a question/statement from
the question list, such as "I visited the palace once.". However, if
the factoid ended in a question then NPC would have to
shut up and wait for the PC to respond, such as "Do you want
to know where the king lives in the summer or winter?".
To prevent the NPC from asking a follow-on question,
set <bold>pFactoidWaitForResponse</bold> to TRUE. Leave it
undefined to allow a follow-on question.
</li>
<li>
If these properties don't meet your requirements for the
factoid, you can always write your
own <bold>FactoidAct()</bold> method, which is called
when the factoid is to be spoken.
</li>
</ul>
<br/><section><p><bold><big>Advanced factoid limits</big></bold></p></section>
<p>
Even though a NPC is a member of a faction that knows a factoid,
the individual NPC may not know the factoid. Or, the NPC may
not be willing to divulge the factoid to the PC:
</p>
<ul>
<li>
<bold>pFactoidSpeak</bold> can be used to limit the factoid's
knowledge to faction members of a specific rank. [ [5, "The king's
first name is Alendre."] ], limits the factoid to only
NPCs with a 5 or higher ranking in their faction. [ ["The emperor
has no clothes.", [4] ], causes all the peasants to realize that
the emperor has no clothes, but anyone with a 4 or higher rank
won't know the factoid.
</li>
<li>
<bold>pFactoidEmotionsRequired</bold> will cause the NPC to
only speak the factoid when he/she is very happy, very sad,
etc.
</li>
<li>
<bold>pFactoidFactionsRequired</bold> ensures that the NPC
will only speak the factoid to PCs who are a member of a specific
faction. (Or not a member.)
</li>
<li>
<bold>pFactoidHoldRequired</bold> will prevent the NPC from
speaking the factoid unless the PC is holding a specific object,
such as a piece of jewelry that idenfifies the PC as a friend.
(Or the reverse.)
</li>
<li>
<bold>pFactoidLikeRequired</bold> causes the NPC to only
speak the factoid if the NPC's like/trust for the PC is within
a given range.
</li>
<li>
<bold>pFactoidMemoryRequired</bold> tests a memory of the
NPC or the NPC's faction (see AIMemoryGet()) and uses it
to determine if the factoid will be revealed. For example: If
the PC hasn't declared allegiance to the king (which caused
a memory to be stored), then he won't be told about many factoids.
</li>
<li>
<bold>pFactoidRaceRequired</bold> will only let the NPC
speak a factoid if the PC is of a specific race. (Or not
of a specific race.)
</li>
<li>
If you wish to impose other tests, you can write
your own <bold>FactoidValid()</bold> method with your
own test. For example: You may wish a factoid discussing
a rumor about a werewolf to be spoken in the week preceding
a full moon.
</li>
</ul>
<br/><section><p><bold><big>Using factoids as conversation questions</big></bold></p></section>
<p>
Factoids can also be used for conversation questions that
are added using AIConversationQuestionAdd(). There
are two ways to add a factoid to the list of questions in
a conversation:
</p>
<ol>
<li>
If the NPC speaks on factoid, that may bring up other
memories/thoughts that is will express later, using
the question mechanism. To do this, set
the factoid's <bold>pFactoidQuestions</bold> to the questions/factoids
that will come to the NPC's mind.
</li>
<li>
If a factoid has <bold>pFactoidQuestionFill</bold> set then
it will automatically be added as a question when the conversation
begins.
</li>
</ol>
<p>
<bold>In both cases</bold>, factoids will <bold>not</bold> be
added as factoids if the fail to meet the critera for
the factoid (such as pFactoidLikeRequired), or the factoid
was already spoken to the PC by the NPC.
</p>
<p>
You can set the question priority by
using <bold>pConvElementPriority</bold>. As
with an question, the priority
is also affected by how well the factoid's state matches
the current conversation state. The state
is set using <bold>pConvElementConversationState</bold>. If
left blank, a state will be generated from any objects
listed in pFactoidParse and pFactoidPronoun.
</p>
Factoids make it easy to create a knowledge base for NPCs.
<section><p><bold><big>Knowledge and rumors - cKnowledge</big></bold></p></section>
<p>
As players wander around the world, their characters will learn various
bits of information, such as rumors ("The mayor is a member of an evil
witchcraft organization.") or facts that allow them access to
new maps ("Farmer John's farm is over yon hill.")
</p>
<p>
These facts are represented by <bold>cKnowledge</bold>-based objects,
much like factoids (based on cFactoid). In fact, you
can <bold>combine a cFactoid and cKnowlege</bold> object, so that
when a NPC speaks the factoid, the PC automatically remembers it
as a piece of knowledge. Or, you can <bold>combine a cConvStory and
cKnowledge</bold> object, causing the knowledge to be automatically
learned when the player hears a story.
If you do this, make sure <bold>cKnowledge is lower on the class list</bold> than cConvStory
or cFactoid.
</p>
<p>
During gameplay, the player can see what knowledge their character
knows by typing, "What do I know". They can also tell the knowledge to NPCs,
as well as telling NPCs the source of the knowledge... A witch hunter
might be interested in knowing that the mayor is a witch, but the mayor
would be more interested in knowing who told the player that he was
a witch.
</p>
<br/><section><p><bold><big>Creating a knowledge object</big></bold></p></section>
<p>
To create a knowlege object:
</p>
<ol>
<li>
Create an object, as normal, but base it off
of <bold>cKnowledge</bold>.
</li>
<p>
If you want the knowledge to automatically
be imparted to the PC when a NPC speaks a factoid, then also
base the object off of <bold>cFactoid</bold> or <bold>cConvStory</bold>,
and follow the factoid or story
creation process, as well as this one. Make sure the cKnowledge class
is lower priority than the cFactoid or cConvStory class.
</p>
<li>
The object should <bold>not be contained</bold> in anything.
</li>
<li>
Fill in <bold>pKnowledgeDescription</bold> with a description of the
knowledge as it will appear in the player's list. For example: "The
mayor is a member if a witchcraft cult."
</li>
<li>
Fill in <bold>pKnowledgeCategory</bold> with a string for the
category that the knowledge will appear under, such as "Mayor Daily".
</li>
<li>
<bold>pConvElementConversationState</bold> should be filled in with
a list of topics (lower-case string) describing the knowledge. This
is very important for when a player and NPC go back and forth
swapping stories and rumors.
</li>
</ol>
<p>
You might also wish to fill in the following:
</p>
<ul>
<li>
<bold>pIsInvisible</bold> will prevent the knowledge from being
displayed on the player's list, as well as prevent the player from speaking
the knowledge to NPCs. This is a useful way to ensure that the player hears
something only once, but which isn't important enough to see on the
player's knowledge list.
</li>
<li>
<bold>pKnowledgeCanRetell</bold> defaults to TRUE indicating that the player
can retell the knowledge to NPCs. If the knowledge isn't story-worthy,
then set this to FALSE.
</li>
<li>
<bold>pKnowledgeEmotions</bold> specifies how the NPC's emotions (happy/sad,
angry/afraid) will be affected when they hear the knowledge.
</li>
<li>
<bold>pKnowledgeLike</bold> specifies how the NPC's like/trust towards
the player will change when they hear the knowledge.
</li>
<li>
<bold>pKnowledgeSpeak</bold> can contain the string that
the player will speak when they reveal the knowledge to a NPC.
If you don't provide this the a sentence will automatically be
genereated from pKnowledgeDescription.
Example: "Did you know that Mayor Daily is a witch?"
</li>
<li>
<bold>pKnowledgeSpeakFromWhom</bold> is used if the
player mentions their source.
If you don't provide this the a sentence will automatically be
genereated from pKnowledgeDescription.
Example: "%1 (told/told/told) me that Mayor Daily
is a witch."
</li>
<li>
The <bold>KnowledgeSpeak()</bold> method generates the sentence
that the player speaks. Although unlikely, you might wish to
write your own method.
</li>
<li>
<bold>pKnowledgeResponseDontCare</bold> is filled with
a NPC's response if they don't care about the rumor. If not
filled in, a default response will be used.
Example: "Don't spread such rumors! You'll get in trouble."
</li>
<li>
<bold>pKnowledgeConversationStateExtreme</bold> affects how
much this knowledge affects "sensative" NPCs who list a corresponding
conversation-state topic in <bold>pAIKnowledgeConversationStateExtreme</bold>.
For example: If this is a sad story about a child's death, then NPCs
hearing it will be saddened. However, if the NPC happens to be the child's
parent, then there will be an extra effect.
</li>
<li>
<bold>pKnowledgeDeduce</bold> causes a new piece of knowledge to be
automatically deduced if the player learns two or more other
pieces of knowledge. This is useful for a detective title.
</li>
<li>
<bold>pKnowledgeAboutRelationships</bold> affects what relationship
connections are learned when the player character hears the knowledge.
</li>
<li>
<bold>pKnowledgeMystery</bold> causes the player's list of mysteries
to be added to when this knowledge object is added.
</li>
</ul>
<br/><section><p><bold><big>Learning knowledge</big></bold></p></section>
<p>
If you wish a player character to know about a knowledge object,
call <bold>Actor.KnowledgeAdd()</bold>. Alternatively, if
you <bold>combine a cKnowledge and cFactoid or cConvStory</bold> object, then the
knowledge will automatically be learned when a NPC speaks the factoid/story.
</p>
<p>
You can remove a knowledge object by
calling <bold>KnowledgeRemove()</bold>. <bold>KnowledgeQuery()</bold> tests
to see if a character knows about the object.
</p>
<br/><section><p><bold><big>Knowledge affecting access</big></bold></p></section>
<p>
If you look under the <bold>cMap</bold> tutorial, you'll see that a
knowledge object can be used to determine if a player can travel to a given
map. Thus, if the player knows where "John's farm" is, with a cKnowledgeJohnsFarm
object, then they can get to it.
</p>
<br/><section><p><bold><big>Telling NPCs knowledge in the form of stories and rumors</big></bold></p></section>
<p>
Players can tell NPCs knowledge they know. Most NPCs won't care about
the knowledge, but some NPCs will react positively, as in the case of
a witch hunter and the mayor. Or, a piece of knowledge could even be
a good joke that the player heard.
</p>
<p>
NPCs all have general (and automatic) responses to being told
knowledge. This reaction is affected by:
</p>
<ul>
<li>
If the NPC already knows the knowledge, or if the player has already
told them it, they may get peeved with the player.
The method, <bold>AIKnowledgeKnow()</bold> is used to determine if
the NPC already knows the object.
</li>
<li>
The more the NPC's <bold>pAIConvStoryPreferred</bold> correlates with
the knowledge's <bold>pConvElementConversationState</bold>, the
more effect that speaking the knowledge will have. If there's a negative
correlation due to oppostes (ConversationStateOpposites()) then
the effects might be reversed.
</li>
<li>
If a NPC has <bold>pAIKnowledgeConversationStateExtreme</bold>, and
one or more of the conversation-state topics is in common with the
knowlege object's pConvElementConversationState, then the NPC
may have a much stronger reaction.
</li>
<li>
The NPC's speech response will be controlled by a call
to <bold>cKnowledge.KnowledgeGenericResponse()</bold>. This method
returns a response based on how positive or negative the NPC's
reaction is, and what elements of the knowledge produced the
positive or negative response.
</li>
<p>
Having the NPC specfically state why they liked/disliked the knowledge
is important feedback to the player, so they can decide what type
of stories/rumors to tell in the future. You may need to augment
KnowledgeGenericResponse(), as per the method's documentation, if you
add new conversation-state topics to the knowledge object's
pConvElementConversationState. For example: If you create a "airplane"
topic, which means that the NPC likes hearing and talking about
airplanes, then you'll need some positive responses if the player's
stories includes airplanes, such as "I love to fly."
</p>
<p>
Not all NPC's provide regular
feedback. <bold>pAIKnowledgeSayFeedback</bold> controls how likely it
is for a NPC to provide feedback about what they liked or disliked.
</p>
</ul>
<p>
To have a NPC react to a <bold>specific piece of knowledge</bold> that's
spoken by a player, you have three options:
</p>
<ol>
<li>
Modify the NPC's <bold>pAIKnowledgeInterest</bold> to list the
various knowledge objects that interest the NPC, as well as the NPC's
responses.
</li>
<p>
You can also set a flag in pAIKnowledgeInterest to indicate that
if the player knows the knowledge, and the NPC hasn't already heard
it from the player, then a context menu item will appear in the
NPC's menu; this context menu makes it more obvious to players that
they should tell the NPC about the knowledge/rumor.
</p>
<li>
Write your own <bold>PerceiveKnowledgeSay()</bold> method for the NPC.
</li>
<li>
If PerceiveKnowledgeSay() doesn't provide enough flexibility,
you can <bold>write a cFactoid</bold> that is triggered by the player
speaking "`knowledgesay KNOWLEDGEOBJECT [SOURCEOBJECT]". You probably
won't have to do this. If you do, make sure you call AIMemoryGet() and
modify the appropriate flag so the AI know's it has already heard
the knowledge.
</li>
</ol>
<br/><section><p><bold><big>Miscellaneous knowledge</big></bold></p></section>
<p>
Some information isn't important enough to be spread as rumors, but
you still want players to "write it in their journal" for later.
To add a "journal" entry, call:
</p>
<ul>
<li>
<bold>KnowledgeMiscAdd()</bold> will add knowledge as a string.
</li>
<li>
<bold>KnowledgeMiscEnum()</bold> finds the list of string knowledge known
about a NPC.
</li>
</ul>
How to create pre-scripted conversations between two or more NPCs.
<section><p><bold><big>Pre-scripted conversations - cConvScript</big></bold></p></section>
<p>
You may wish your NPCs to occasionally have conversations with one
another. For example: Two friends might bump into one another at the
fish mongers, or a group of burglers might meet clandestinely the
night before a burglery to arrange who does what.
</p>
<p>
The easiest way to create a pre-scripted conversation is to
create an object based off <bold>cConvScript</bold>.
</p>
<br/><section><p><bold><big>Creating a conversation-script object</big></bold></p></section>
<p>
To create a conversation-script object:
</p>
<ol>
<li>
Create a <bold><ConvScript></bold> resource the scripts
out the conversation. You'll be able write lines for the NPCs,
as well as narration, emotes, and sounds.
</li>
<li>
Create an object, as normal, but base it off
of <bold>cConvScript</bold>. For this example, call
it oConvScriptMeetAtFishMongers.
</li>
<li>
The object should <bold>not be contained</bold> in anything.
</li>
<li>
Fill in <bold>pConvScriptResource</bold> with the name
of the resource you created, such as rConvScritMeetAtFishMongers.
</li>
<p>
If you want to generate the conversation on the fly,
write your own <bold>ConvScriptResource()</bold> method.
</p>
<li>
If your conversation takes place between three or more NPCs, or
only the NPC, then modify <bold>pConvScriptNPCs</bold> to indicate
the number of NPCs involved. (The default value is 2.)
</li>
<li>
When the two NPCs meet at the fish mongers,
call <bold>oConvScriptMeetAtFishMongers.ConvScriptStart()</bold>. That's
it! The two NPCs will meet and talk about the quality
of the fish, or whatever.
(Note: I'll got into an easier way of automatically starting
conversations later.)
</li>
</ol>
<p>
You may also wish to set the following properties and methods:
</p>
<ul>
<li>
<bold>pConvScriptAbortIfPCConv</bold>, which defaults to TRUE, causes
all NPC conversations to automatically stop if a player talks to one
of the NPCs.
</li>
<li>
<bold>pConvScriptAbortIfUnconscious</bold>, which defaults to TRUE,
causes the conversation to automatically stop if any of the NPCs
is unconscious or killed.
</li>
<li>
<bold>pConvScriptSecret</bold> will cause the NPCs to abruptly stop
the conversation if someone enters the room. They won't even automatically start
it unless they're alone.
</li>
<li>
<bold>ConvScriptCompleted()</bold> is called when the conversation
is completed. The default behaviour does nothing, but you could modify this
if you want your NPCs to act on the conversation. For example,
the NPCs might talk about the best choice of fish, and one of the NPCs
might be given the AI goal of buying that particular fish.
</li>
</ul>
<br/><section><p><bold><big>Automatic conversation scripts</big></bold></p></section>
<p>
Conversation scripts also provide a mechanism for making them "automatic" so
that you don't have to constantly check for the right conditions
and call ConvScriptStart(). Instead, conversation scripts can be set up
so they are automatically called whenever a NPC enters/leaves a room,
when a PC enters/leaves a room, or when the NPC is bored.
</p>
<p>
To have a NPC speak an automatic conversation script, you need to:
</p>
<ol>
<li>
Add the conversation script object to the
NPC's <bold>pAIConvScripts</bold> property.
</li>
<p>
If you want all NPCs in a specific faction to use the conversation
script, such as a secret greeting to one another, then
modify the faction's <bold>pAIConvScripts</bold> property.
</p>
<p>
Alternatively, if you want conversation scripts associated with
certain classes, such as all cRaceElf characters using a type
of conversation script, modify the <bold>AIConvScripts()</bold> method
for the class.
</p>
<li>
If the conversation requires more than two NPCs,
it must have <bold>pConvScriptNPCs</bold>. It must also have
a <bold>ConvScriptAutoPermutations()</bold> written. This method will
be called with a list of players and NPCs in the room. It must
return a list of all the "possible" conversations that could result.
Since there aren't that many possibilities with only two (or one) NPC,
the method is trivial and automatically handled. However, if you have
ten NPCs sitting in a room, the number of possible groups of three
NPCs to sing as a trio is astronomical.
</li>
<li>
You need to specify some constraints that limit what two NPCs will
join together. For example: While all NPCs might have your fish-monger
conversation at the ready, they'll only use it when they're in
the fish market. The following properties and methods help limit
the possibilities.
</li>
<p>
<bold>pConvScriptAutoFactions</bold> will limit the conversation so that
the main NPC (NPC #1) will only use the conversation with NPCs from
the specified faction(s).
</p>
<p>
<bold>pConvScriptAutoNPCs</bold> will limit the conversation so that
the main NPC (NPC #1) will only use the conversation with NPCs
listed in pConvScriptAutoNPCs. Example: Applying this to the fish monger
might mean that Betty would only talk about fish to a handful of
her friends.
</p>
<p>
<bold>pConvScriptAutoListenersExclude</bold> can be used to NOT play
the conversation if certain NPCs are in the room. This can be used to
ensures that rumors about NPC X aren't spoken while NPC X is in the room.
</p>
<p>
<bold>pConvScriptAutoKnowledge</bold> will only have the conversation
spoken if the player character <bold>doesn't</bold> know the specified
knowledge object. You would use this, for example, to have a pair of NPCs
speak a rumor only once in the precense of a specific
player. <bold>pConvScriptAutoKnowledgeFlip</bold> inverts the test,
causing the NPCs to speak the script only if the player has the given
knowledge.
</p>
<p>
<bold>pConvScriptAutoFracture</bold> will only allow the conversation
script to be spoken when the player is in the given fracture.
This is passed to IsInDifferentFracture() and FracturerQuery().
</p>
<p>
<bold>pConvScriptAutoPriority</bold> controls which automatic
conversation script has higher priority.
</p>
<p>
<bold>pConvScriptAutoRelationship</bold> lets the conversation script
only happen if a specific relationship exists.
</p>
<p>
<bold>pConvScriptAutoRequiresIntelligent</bold> ensures that conversations
only happen between intelligent creatures.
</p>
<p>
These properties won't be enough, though. You'll need
to write your own <bold>ConvScriptAutoValid()</bold> method for
many conversation scripts. The method is called whenever a conversation
script is "proposed" by <bold>ConvScripts()</bold> to see if it
would work in the given situation.
</p>
<li>
You will need to set the liklihood of the conversation happening
depending upon the circumstances. If you don't set at least one
of these properties to non-zero then your conversation will never
be randomly selected.
</li>
<p>
<bold>pConvScriptProbBored</bold> - The probability of the conversation
script being activated when the NPC is sitting around bored (with
the oAIGoalBoredConvScript activated). Example: Use this if the NPC
brings up the "Ever wonder what's up there?" when sitting outside
on a starry night.
</p>
<p>
<bold>pConvScriptProbNPCEnters</bold> - The probability of the conversation
script being activated when the NPC enters a room. Example: Use this if the NPC
says, "Hello everyone!" every time it enters the room.
</p>
<p>
<bold>pConvScriptProbNPC2Enters</bold> - The probability of the conversation
script being activated when a second NPC enters a room. Example: Use this if the
first NPC says, "Hi %2!" every time a second NPC enters the room.
</p>
<p>
<bold>pConvScriptProbNPC2Leaves</bold> - The probability of the conversation
script being activated when a second NPC leaves a room. Example: Use this if the
first NPC says, "That idiot has finally left." every time a second NPC leaves the room.
</p>
<p>
<bold>pConvScriptProbPCEnters</bold> - The probability of the conversation
script being activated when a PC enters a room. Example: Use this if the NPC
says, "Hey, it's the town hero!" every time the PC enters the room.
</p>
<p>
<bold>pConvScriptProbPCLeaves</bold> - The probability of the conversation
script being activated when a PC leaves a room. Example: Use this if the NPC
says, "Finally got rid of him!" every time the PC leaves the room.
</p>
</ol>
<p/>
<p>
Some more advanced settings that you may wish to modify:
</p>
<ul>
<li>
If your conversation requires that a PC be around, perhaps because the
NPCs are deriding the PC right in front of their face, then
set <bold>pConvScriptRequiresPC</bold> to TRUE. You <bold>don't</bold> have
to set this if
either <bold>pConvScriptAutoKnowledge</bold> or <bold>pConvScriptAutoFracture</bold> are
set.
</li>
<li>
To randomly start your own conversation,
call <bold>ConvScriptAutoStart()</bold> at an appropriate time.
</li>
<li>
<bold>gConvScriptRate</bold> controls the default conversation rate. It defaults
to one act every 3 seconds.
</li>
</ul>
<br/><section><p><bold><big>Playing conversation scripts through goals</big></bold></p></section>
<p>
If you want a conversation script to take place as part of an AI's goal,
then use <bold>oAIGoalConvScript</bold> to start the conversation.
</p>
How to create pre-scripted conversations between two or more NPCs.
<section><p><bold><big>Conversation trees - cConvTree</big></bold></p></section>
<p>
Conversation trees are a tride-and-true method for talking to NPCs,
and the standard NPC conversation system for most CRPGs. For those
of you not familiar with the term, you'll be familiar with the
scenario: The NPC says something like "What's your favorite color?", and
the player is given a menu of choices, such as "Red", "Green", or "Blue".
Once the player selects the color, the NPC makes a statement and (often)
a new question is asked by the NPC.
</p>
<p>
While Circumreality doesn't rely on conversation trees (most CRPGs do), it does
allow for them.
</p>
<br/><section><p><bold><big>Creating a conversation-tree object</big></bold></p></section>
<p>
To create a conversation tree (combination of question, player answers,
and NPC's responses to the answers), you need to create a conversation-tree
object. To create a conversation-tree object:
</p>
<ol>
<li>
Create an object, as normal, but base it off
of <bold>cConvTree</bold>. For this example, call
it oConvTreeFavoriteColor.
</li>
<li>
The object should <bold>not be contained</bold> in anything.
</li>
<li>
Fill <bold>pConvTreeQuestion</bold> with the question that the NPC
asks, such as "What's your favorite color?"
</li>
<li>
<bold>pConvTreeAnswers</bold> should be filled in with a list of
answers to the NPC's question, such as ["Red", "Green", "Blue"].
</li>
<li>
<bold>pConvTreeResponses</bold> is a list containing the responses
to each of the player's answers, such as ["Red? I hate red.",
"Green is nice.", "Blue is my favorite color."]
</li>
<li>
If you wish one or more answers to lead to another conversation
tree, then set <bold>pConvTreeResponsesNext</bold> to indicate
subsequent conversation trees.
</li>
<li>
Since the player's answers may change the NPC or other parts of
the game, you'll probably need to write your
own <bold>ConvTreeResponse()</bold> to handle and coding.
For example: If the player likes red, the NPC might give them
a red riding cloak; this needs to be handled by code.
</li>
</ol>
<p>
You may also wish to set the following properties and methods:
</p>
<ul>
<li>
Set <bold>pConvTreeMustAnswer</bold> to TRUE if the NPC requires
an answer from the player and won't be distracted by attempts
to change the subject.
</li>
<li>
<bold>pConvTreeQuestionRepeat</bold> is spoken if the player
doesn't answer the NPC's question properly and the NPC has to
repeat the question.
</li>
<li>
If you wish to customize pConvTreeQuestion or
pConvTreeQuestionRepeat based upon the current situation,
you can write your own <bold>ConvTreeQuestion()</bold> method.
</li>
<li>
You can allow for alternate phrasings of the answers, such as
"I like red best." by proving
a <bold>pConvTreeAnswersParse</bold> property.
</li>
<li>
Answers won't always be valid every time a conversation tree
pops up. For example: If the NPC asks the player, "What inn are
you staying at?", you could include answers for all the inns in
town. However, if the player character didn't know about some of
the inns, you might want to invalidate and hide some of the
answers. To do this, write your
own <bold>ConvTreeAnswerIsValid()</bold> method.
</li>
<li>
<bold>pConvTreeResponseNotAnswer</bold> controls what the NPC says
if the player tries to change the subject and avoid answering
the question.
</li>
<li>
<bold>pConvTreeResponseNotAnswerLike</bold> controls how much
the NPC's like/trust of the PC changes when the player tries
to change the subject.
</li>
<li>
<bold>pConvTreeImportantDecision</bold> will cause all the context-menu
options to be flagged so the player knows they're making an important decision.
</li>
<li>
If you invoke the converation tree using AIConversationConvTreeAdd()
then you may wish to write a <bold>ConvTreeIsValid()</bold> method
so that the conversation tree won't be brought up in the conversation
if it's no longer valid.
</li>
<li>
<bold>pConvElementConversationState</bold> is used when you call
AIConversationConvTreeAdd() to determine the conversation state after
the conversation tree finishes.
</li>
</ul>
<br/><section><p><bold><big>Activating a conversation tree</big></bold></p></section>
<p>
There are several ways that you can activate a conversation tree:
</p>
<ul>
<li>
Call <bold>oNPC.AIConversationConvTreeSet()</bold> to start the conversation
tree immediately.
</li>
<li>
<bold>oNPC.AIConversationConvTreeAdd()</bold> will add the conversation tree
to the NPC's list of "questions" that the NPC might choose to bring up, depending
upon where the conversation takes it. Unless you set the priority to a very
large value (greater than 1.0), there's no guarantee that the conversation
tree will be run immediately.
</li>
<p>
If you're providing a <bold>oNPC.AIConversationQuestionFill()</bold> method
for your NPC, which controls what questions the NPC asks, you can
also call AIConversationConvTreeAdd().
</p>
<li>
You can have a cFactoid invoke a conversation tree by
setting <bold>oFactoid.pFactoidConvTree</bold>.
</li>
</ul>
<br/><section><p><bold><big>Easy conversation trees</big></bold></p></section>
<p>
Often, you'll want NPCs to ask a series of questions to players as they get
to know them. The questions will start out easy, like "Who do you want to win the
world series?" and gradually work up to something more contentious, like
"Do you want the elves or the orcs to win the war?"
</p>
<p>
Creating this question ladder is easy, just fill in oNPC.<bold>pAIConvTrees</bold> with
the series of questions and acceptable responses.
</p>
Code that allows NPCs to tell stories to player.
<section><p><bold><big>Conversation stories - cConvStory</big></bold></p></section>
<p>
An important part of Circumreality gameplay is for non-player characters to
tell stories to the players. The stories not only provide knowledge
(cKnowledge) to the player characters, but they provide clues
about the NPC's personality. These clues help the player determine
what needs to be done to get on the NPC's good side.
</p>
<br/><section><p><bold><big>Creating a story object</big></bold></p></section>
<p>
Getting NPCs to speak stories is pretty easy, and involves the creation
of an object for each story:
</p>
<ol>
<li>
Create an object, as normal, but base it off
of <bold>cConvStory</bold>. For this example, call
it oConvStoryFishingTrip.
</li>
<li>
The object should <bold>not be contained</bold> in anything.
</li>
<li>
Fill <bold>pConvStorySpeak</bold> with the <CutScene> resource
for the story. If you just wish the NPC to speak the story, you
can also use a <SpeakScript> resource, or even a string.
If pConvStorySpeak is a list, then the story will
be <bold>multi-part</bold>, and require the player to speak,
"Please continue" after each part.
</li>
<li>
<bold>pConvStoryAskSpeak</bold> should be filled with a unique string
that the player can speak to the NPC and re-hear the story.
For example: "Could you tell me about your fishing trip to the Catskills?"
</li>
<li>
<bold>pConvStoryIsRumor</bold> should be set to TRUE if the story
is actually a rumor.
</li>
<li>
<bold>pConvElementConversationState</bold> is a list of lower-case
strings that described what the story is about. For example:
["fishing", "catskills", "holiday"]. Every story needs this
property since it allows the NPC to determine which stories to use
given the conversation context.
</li>
<p>
If your code access pConvElementConversationState, it should
call <bold>ConvElementConversationState()</bold> instead, since this
method appends "story" or "rumor" to the list based on
pConvStoryIsRumor.
</p>
<li>
You either need to <bold>sub-class your story object from cKnowledge
as well as cConvStory</bold> or,
set <bold>pConvStoryKnowledge</bold> to the associated knowledge
object. This means that every time a player hears a story/rumor, they
will be able to access it from their "What do I know?" list, as well
as tell it to other NPCs in story/rumor swaps.
</li>
</ol>
<p>
You may also wish to fill in the following properties or methods:
</p>
<ul>
<li>
<bold>ConvStorySpeak()</bold> and <bold>ConvStoryNumParts()</bold> can
be overridden so that the story changes depending upon the player.
</li>
<li>
<bold>pConvStoryWontSpeak</bold> is the text that the NPC speaks
if it isn't willing to tell the player the story just yet, perhaps
because the NPC doesn't like the player enough.
</li>
<li>
You can write your own <bold>ConvStoryWontSpeak()</bold> to decide
when the NPC will speak the story to the player. The
default method tests the properties: <bold>pConvStoryWontSpeakLike,
pConvStoryWontSpeakFaction, pConvStoryWontSpeakFracture,
and pConvStoryWontSpeakQuest</bold>.
</li>
<li>
<bold>pConvStoryAskSpeakParse</bold> provides for other ways to
ask the pConvStoryAskSpeak question.
</li>
<li>
<bold>pConvStoryAskSpeakAlwaysShow</bold> causes the pConvStoryAskSpeak
question to always be listed in the NPC's context menu, even if the
player hasn't heard the story.
</li>
<li>
<bold>pConvElementPriorty</bold> affects how badly the NPC wishes
to tell the story. Values range from 1.0 (very badly) to 0.0 (doesn't
wish to tell).
</li>
<li>
If <bold>pConvStoryExpectPCToReciprocate</bold> then after the NPC
finishes with the story/rumor, it will expect the player to follow
with their own story/rumor. Players will be shown their, "What do I know?"
list and be able to select a story to speak back.
</li>
<li>
Some NPCs just like to tell stories to NPCs. If
you set <bold>pConvStorySpeakLike</bold> then every time the NPC
gets to tell a story/rumor to a character, it will like/trust the
character more. Use this to create NPCs that just want a shoulder
to cry on, or someone to brag to.
</li>
</ul>
<br/><section><p><bold><big>Speaking a story</big></bold></p></section>
<p>
There are several ways that you can have a NPC speak a story:
</p>
<ul>
<li>
Call <bold>oNPC.AIConversationConvStorySet()</bold> to speak the story immediately.
</li>
<li>
Call <bold>oNPC.AIConversationConvStoryAdd()</bold> will add the story
to the NPC's list of "questions" that the NPC might choose to bring up, depending
upon where the conversation takes it. Unless you set the priority to a very
large value (greater than 1.0), there's no guarantee that the story
will be spoken immediately.
</li>
<p>
If you're providing a <bold>oNPC.AIConversationQuestionFill()</bold> method
for your NPC, which controls what questions the NPC asks, you can
also call AIConversationConvTreeAdd(). You probably won't need to; see below.
</p>
<li>
For each cCharacter and cFaction, you can
set <bold>pAIConvStories</bold> to a list of stories that the character (or
faction) knows. Characters will automatically know stories assigned to
their factions.
</li>
<p>
Using pAIConvStories will automatically add stories to the list of "questions"
when oNPC.AIConversationQuestionFill() is called, as well as provide
context menu options for the stories.
</p>
<li>
A call to <bold>ConvStorySelect()</bold> will select a story that seems
most appropriate to the NPC, given the current conversation state and
stories that the NPC has already spoken to the player.
</li>
</ul>
<br/><section><p><bold><big>Properties associates with NPCs</big></bold></p></section>
<p>
You may wish to set the following properites for every NPC:
</p>
<ul>
<li>
<bold>pAIConvStories</bold>, as previously stated, controls the stories
that the NPC knows. (It also knows the cFaction.pAIConvStories of any
factions to which it belongs.)
</li>
<li>
<bold>pAIConvStoryPreferred</bold> is a list of the topics (aka: a conversation
state) in stories that the NPC prefers to speak, as well as hear.
For example: If a NPC's pAIConvStoryPreferred is ["story", "fishing", "cars",
"holiday"], then the NPC will prefer to tell stories about fishing, cars,
and/or holidays. Likewise, they will prefer to hear such stories.
This property is very important because it <bold>defines much of
the NPC's personality.</bold>
</li>
<p>
A more flexible alternative to pAIConvStoryPreferred, it to
override the method, <bold>AIConvStoryPreferred()</bold>. The default
behavior just refers to pAIConversationStoryPreferred. However, if
you write your own code, you can specify that all dwarves like to hear
about "mining" and "gems", or that NPC's who are sad wish to hear "sad" stories.
</p>
<li>
<bold>pAIConvStoryStickToPreferred</bold> controls how much the NPC
sticks to its pAIConvStoryPreferred list during conversations. If this
is 1.0, the NPC will keep returning to fishing, cars, and holidays. If
this is 0.0, the NPC will easily follow the player's lead.
</li>
<li>
By default, if the NPC knows any stories or rumors, it will include
the context menu of "Do you know any good rumors/stories?" to save
players some typing. However, this context menu reveals to the player
the fact that the NPC knows some stories/rumors. If you wish
to hide the menu, then
set <bold>pAIConvStoryHideRumorMenu</bold> to TRUE.
</li>
</ul>
<br/><section><p><bold><big>Conversation states</big></bold></p></section>
<p>
As stated, the pConvElementConversationState property is very important for
stories, since it controls when a NPC will speak a story, and what
the NPC thinks about stories told to it by the player. The property
is a list of lower-case strings that describe the story, such as ["story",
"fishing", "holiday", "catskills"].
</p>
<p>
The NPC's AI determines which stories follow one another best by
calling <bold>ConvStoryConversationIntersect()</bold>, comparing
the existing conversation state to the story's conversation state.
</p>
<p>
Some converation state topics are opposites of one another. For example:
"rumor" is the opposite of story, and "work" is the opposite of "holiday".
NPCs take extra precaution to not speak a follow-on story with opposites
to the current conversation state. For example: A story about a holiday
followed by a story about work (holiday's opposite) is a social mistake
that's to be avoided. Players need to work within the "avoid opposites"
framework too.
</p>
<p>
To determine a conversation state's
opposites, <bold>ConversationStateOpposites()</bold> is called.
</p>
Easy way for NPCs to relay messages to players.
<section><p><bold><big>Conversation messages</big></bold></p></section>
<p>
Often times, you'll want NPCs to relay messages to PCs, such as
"Your mother called me and wants you to go home and eat dinner now,"
or "[Other PC] has been spreading bad rumors about you."
</p>
<p>
The hard way to provide this functionality is to write your
own AIConversationQuestionFill() that calls AIConversationQuestionAdd().
</p>
<p>
The easy way is to:
</p>
<ol>
<li>
Call <bold>AIConversationMessageAdd()</bold> when the NPC learns of
a message that it wants to tell the player.
</li>
<li>
That's it.
</li>
</ol>
<p>
If you want to remove a message from a NPC,
then call <bold>AIConversationMessageRemove()</bold>. For example:
When the player returns home from, then all the mothers in the neighborhood
that had been called with AIConversationMessageAdd() should
receive an AIConversationMessageRemove() call with the same
message string.
</p>
How to easily allow players to give NPC commands, ask them questions, or state facts to them.
<section><p><bold><big>Commands, statements, and questions</big></bold></p></section>
<p>
There is yet another way to make NPCs more conversive; this way is
designed so that you'll find it easy to create commands (that players can
give to the NPCs), questions (that players can ask), and
statements (that players can speak to NPCs). In most cases, you may
find this easier than using factoids (cFactoid), conversation
trees (cConvTree), or conversation stories (cConvStory).
</p>
<br/><section><p><bold><big>The easiest way</big></bold></p></section>
<p>
The easiest way for a NPC to answer questions is to:
</p>
<ol>
<li>
Fill in <bold>pAIQuestionsAndAnswers</bold> with the questions and
answers that the NPC knows.
</li>
<p>
Optionally, write your own <bold>AIQuestionsAndAnswers()</bold> method.
</p>
</ol>
<p>
Unfortuntely, this method is somewhat limit, so you'll
need something more flexible.
</p>
<br/><section><p><bold><big>A more flexible technique</big></bold></p></section>
<p>
To get a NPC to respond to a custom command, answer a specific question, or
understand a statement:
</p>
<ol>
<li>
<bold>Invent a lower-case alphabetic-only string</bold> that described
the phrase, such as "singsong" if you want players to ask NPCs
to sing a song.
</li>
<li>
Add a <bold>prefix</bold> to the string. Use "cmd" if it's a command,
"quest" for a question, or "state" for a statement. For the singing
example, this would create a name of "cmdsingsong".
</li>
<li>
Add <bold>AIListenForEnum()</bold> to the NPC. You'll need to
write this code so this appends the appropriate phrases (commands, questions,
or statements) to the list. For the singing example, this might
append, ["cmdsingsong", "Sing a song", "sing [a] song [to %1]"].
"cmdsingsong" is the name. "Sing a song" will appear on the NPC's context
menu so that players know they can ask the NPC to sing. "sing [a] song [to %1]"
is an additional parse that instructs the NPC to sing their song to
a specific character.
</li>
<p>
If you don't include the second element in the list, "Sing a song", then
the command <bold>won't</bold> appear on the NPC's context menu, requiring
users to type it in.
</p>
<li>
You'll need to add <bold>AIListenForIsValid()</bold> to validate
that the pameters for the %1 wildcard is correct. In this case,
if the (PhraseID === "cmdsingsong") then the method should verify that
there are no parameters, in the case of "Sing a song", or that there is
one parameter and it is another character, for "Sing a song to %1".
</li>
<li>
Finally, write a <bold>AIListenForAct()</bold> to actually
have the NPC sing a song.
</li>
<li>
If you only want the NPC to <bold>speak a phrase in reply</bold>, such as to the
question, "What is the meaning of life?", then you can get away with
only an AIListenForEnum() that fills the list in with ["questmeaningoflife",
"What is the meaning of life?", "[*4] meaning [of] life [*5]", "The meaning
of life is 42."]. The fourth parameter will be spoken if the question
is asked. The third parameter, "[*4] meaning [of] life [*5]", accepts
any sentence as long as it has "meaning of life" or "meaning life" in it.
Using this approach you won't need an AIListenForIsValid() or
AIListenForAct().
</li>
</ol>
<br/><section><p><bold><big>Advanced tips</big></bold></p></section>
<ul>
<li>
You can also add AIListenForEnum(), AIListenForIsValid(),
and AIListenForAct() to the NPC's <bold>cFaction</bold>, allowing
all members of the faction to respond to the phrase.
</li>
<li>
Alternatively, adding AIListenForEnum(), AIListenForIsValid(),
and AIListenForAct() to <bold>different NPC classes</bold>, such as cNPCSinger
or cNPCPhilisopher,
will enable all members of the class to react to the command, question,
or statement. The methods are all layerable, so a NPC derived
from cNPCSinger <bold>and</bold> cNPCPhilosopher will be able to
both sing and answer the meaning of life.
</li>
</ul>
<br/><section><p><bold><big>Give/offer (default command)</big></bold></p></section>
<p>
If a player gives an object to a NPC, this is translated into a "cmdoffer"
ParseID that's passed into AIListenForIsValid() and AIListenForAct().
Only one parameter is used, the object that's being given.
</p>
<p>
The default implimentation checks to see the real value of the gift, along with
the NPC's personal valuation (since some NPCs like specific types of objects,
such as chocolates or collectables). If the real valuation is too high for
the NPC, or the player have given the NPC too many gifts recently, then the
NPC will decline the gift. Otherwise, the gift will be accepted, and the
NPC's opinion of the player will be improved based upon the NPC's valuation
of the gift (not the real value).
</p>
<p>
Some properties that you might wish to modify are:
</p>
<ul>
<li>
<bold>pAIOfferCanBribe</bold> - If this is TRUE then the player can give
money to the NPC without offending the NPC. By default, this is FALSE.
</li>
<li>
<bold>pAIOfferEffectiveness</bold> - The amount that a NPC's like/trust
go up when it's given a gift is based upon the NPC's personal valuation of
the gift combined with the values in pAIOfferEffectiveness. Higher numbers
will make the NPC more susceptible to gift-giving and bribery.
</li>
<li>
<bold>pAIOfferForget</bold> - See below for details, but this controls
how frequently players are able to give NPCs gifts.
</li>
<li>
<bold>pAIOfferMaxAmount</bold> - This is the maximum (real) value of gifts
that the player can give the NPC per "week". (pAIOfferForget controls how
long a "week" is.) The value is tempered by how much the NPC likes the
PC; with smaller amounts being used by NPCs that like the player less.
If the player tries to give a gift that's more expensive
than this then the NPC will reject the gift.
</li>
</ul>
<p>
Because this behaviour is customized so frequently, there's a special
method that you may wish to modify, AIListenForCmdOffer().
</p>
<br/><section><p><bold><big>Show (default command)</big></bold></p></section>
<p>
If a player shows an object to a NPC, this is translated into a "cmdshow"
ParseID that's passed into AIListenForIsValid() and AIListenForAct().
Only one parameter is used, the object that's being given.
</p>
<p>
The default implimentation checks to see
the NPC's personal valuation of the object (since some NPCs like specific types of objects,
such as chocolates or collectables).
The NPC then responds based on its personal valuation.
</p>
<p>
Some properties that you might wish to modify are:
</p>
<ul>
<li>
<bold>pAIOfferCanBribe</bold> - NPCs that can be bribed will find
money more interesting that those that can't be bribed.
</li>
<li>
<bold>pAIOfferEffectiveness</bold> - This is used for "cmdoffer" (above),
and to determine how interested the NPC is in the object.
</li>
</ul>
<p>
Because this behaviour is customized so frequently, there's a special
method that you may wish to modify, AIListenForCmdShow().
</p>
<br/><section><p><bold><big>Can I have your OBJECT? (default command/question)</big></bold></p></section>
<p>
Players can ask NPCs to give them objects that the NPCs are carrying, by typing,
"Can I have your OBJECT".
</p>
<p>
To specify what objects the NPCs are willing to give:
</p>
<ol>
<li>
Set <bold>pAIGivable</bold> to the list of objects that the NPC will give. To ensure
that the NPC will have the objects for all players that come through a multiplayer
game, objects in pAIGivable will (by default) be automatically created in the NPC's
possession.
</li>
</ol>
<br/><section><p><bold><big>Default questions</big></bold></p></section>
<p>
NPCs understand dozens of default questions, such as "What race are you?",
"What guilds are you a member of?", etc.
</p>
<p>
You can fine-tune how readily the NPC answers these questions by
setting:
</p>
<ul>
<li>
<bold>pAIInfoReveal</bold> controls how tight-lipped the NPC is about
information. If you don't set this, the value will be randomly generated.
</li>
<li>
<bold>AIInfoRevealValue()</bold> is a layered method to see how tight lipped the AI
is about a specific type of information, such as the languages that it
speaks or its hobbies. This is called by AIInfoReveal() (see below) to
see whether a NPC will reveal specific information.
</li>
<li>
While you won't modify this, <bold>AIInfoReveal()</bold> is
a method to see how tight lipped the AI
is about a specific type of information, such as the languages that it
speaks or its hobbies. Call this to see if the AI would be willing
to reveal some information to the player.
</li>
</ul>
<p>
You may also wish to override some of the default responses:
</p>
<p>
You can add a layer to <bold>AIListenForAct()</bold>, as described above.
This will cause, for example, a specific question to a specific NPC
to be trapped. If you ask Fred, "Is Mike a member of any guilds?", Fred
will have a unique answer.
</p>
<p>
If you wish to have <bold>all NPCs</bold> that know Mike to provide
a custom response to, "Is Mike a member of any guilds?", then...
</p>
<ul>
<li>
Write you own <bold>AIInfoSpeak()</bold> associated with the object about
which the question is being asked. For this example, you would
write oMike.AIInfoSpeak().
</li>
<li>
And/or, set <bold>oMike.pAIInfoSpeakFuturePlansNPC</bold> to provide a default
response to the question, "What are Mike's future plans?"
</li>
<li>
And/or, set <bold>oMike.pAIInfoSpeakHowDoingNPC</bold> to provide a default
response to the question, "How is Mike doing?" or "How are you doing?"
</li>
<li>
And/or, set <bold>oMike.pAIInfoSpeakKnowAboutNPC</bold> to provide a default
response to the question, "What do you know about Mike?"
</li>
<li>
And/or, set <bold>oMike.pAIInfoSpeakOccupationNPC</bold> to provide a default
response to the question, "What is Mike's occupation?" or "What is your occupation?"
</li>
<li>
And/or, set <bold>oMike.pAIInfoSpeakWhereFromNPC</bold> to provide a default
response to the question, "Where is Mike from?"
</li>
<li>
And/or, set <bold>oMike.pAIInfoSpeakWhereLiveNPC</bold> to provide a default
response to the question, "Where does Mike live?" or "Where do you live?"
</li>
<li>
And/or, set <bold>oMike.pAIInfoSpeakWhyHereNPC</bold> to provide a default
response to the question, "Where is Mike here?"
</li>
<p>
The default location for where the character lives is
generated from <bold>pAIScheduleLocationLive</bold>.
</p>
<li>
And/or, set <bold>oMike.pAIInfoSpeakWhereWorkNPC</bold> do provide a default
response to the question, "Where does Mike work?" or "Where do you work?"
</li>
<p>
The default location for where the character works is
generated from <bold>pAIScheduleLocationWork</bold>.
</p>
<li>
Fill <bold>pAIInfoSpeakAdvice</bold> with the NPC's answer to "Do you
have any advice for me?"
</li>
<li>
Fill <bold>pAIInfoSpeakHappening</bold> with the NPC's answer to "What's
going on here?"
</li>
</ul>
How to easily incorporate chatter-bot functionality into your NPCs.
<section><p><bold><big>Chatterbot functionality</big></bold></p></section>
<p>
So far, all the NPC conversation tools that you've been shown are designed
to (a) create procedural responses to questions, like "Where is fred?", and
(b) create NPC-specific responses, such as "Why are you here?"
</p>
<p>
Sometimes, though, you may just need basic chatter-bot functionality.
The player types in a statement or question, and the NPC responds with
a statement (or a statement that ends in a question mark). In general,
the same response is provided by all NPCs, or at least, all NPCs
of a specific class (such as all elves).
</p>
<p>
To create a chatterbot:
</p>
<ol>
<li>
For a class of NPC, such as cElf, <bold>write a cElf.AIConvChatter()</bold> method.
This will fill in a list with [ResourceName, Resource]. In the case
of elves, this might be ["elf", rNLPRuleSetElfChatter].
</li>
<p>
Alternatively, you could fill in <bold>pAIConvChatter</bold>, but this isn't
recommended for classes of NPCs, because one class might overwrite another
class's pAIConvChatter if a NPC based on two classes, each with their own
chatter-bot rules.
</p>
<li>
<bold>Create a <NLPRuleSet> resource</bold> named rNLPRuleSetElfChatter (or
whatever you called it above.
</li>
<li>
In the rule set, <bold>create rules</bold> that convert sentences the player may speak
into parsed sentences of the format, "`speak WHATTOSPEAK".
</li>
<p>
For example: If you want players to ask, "What is the meaning of life?",
then create a rule that converts "what is the meaning of life" to
"`speak The meaning of life? 42, of course.".
</p>
</ol>
<p>
That's it. From now on, players will be able to ask all elves, "What is the
meaning of life?", and all elves will reply with the exact same answer,
"The meaning of life? 42, of course."
</p>
<p>
This simple method has several limitations: (a) You can't assign transplanted
prosody to the response, (b) You can't assign a <SpeakScript> to the response.
And (c) you can't customize it according to the NPC, PC, and situation.
</p>
<p>
A more sophisticated approach is:
</p>
<ol>
<li>
<bold>Create a rule</bold> that converts the english sentence to a parsed
sentence with the form, "`chatter `RESOURCENAME `SENTENCENAME". RESOURCENAME
is the name you used for the resource, above, "elfchatter". SENTENCENAME
uniquely identified the response for "What is the meaning of life?"
</li>
<p>
For example: Your rule set would convert "what is the meaning of life" to
"`chatter `elf `whatmeaningoflife".
</p>
<li>
<bold>Write a cElf.AIConvChatterAct()</bold> method. In the method, first
test that (ResourceName == "elf"), returning if it isn't. Then, test
that (SentenceName == "whatmeaningoflife"), returning "The meaning of life?
42, of course." if there's a match.
</li>
<li>
If the method <bold>returns the resource</bold> sAIConvChatterWhatMeaningOfLife,
then you can record transplanted prosody.
</li>
<li>
Or, the method <bold>could return a <SpeakScript> resource.</bold>
</li>
<li>
Or, the method <bold>could run any code it wishes</bold>, and have the elf break into
song, and then return TRUE to indicate that the AIConvChatterAct() method
handled all the speaking.
</li>
</ol>
The cMerchant class makes it easy to add a merchant to your world.
<section><p><bold><big>Merchants - cMerchant</big></bold></p></section>
<p>
Merchants are a common fixture in most worlds.
The <bold>cMerchant</bold> class was created to make implimenting
merchants easy.
</p>
<p>
Merchants can perform the following functionality:
</p>
<ul>
<li>
<bold>Sell</bold> equipment to player characters.
</li>
<li>
<bold>Buy</bold> equipment from player characters.
</li>
<li>
<bold>Change</bold> currency from one form to another (gold to copper).
</li>
<li>
<bold>Repair</bold> equipment.
</li>
<li>
<bold>Identify</bold> equipment.
</li>
<li>
<bold>Teach</bold> player characters skills.
</li>
<li>
<bold>Heal</bold> charaters.
</li>
<li>
<bold>Other</bold> actions; whatever the author thinks up.
</li>
</ul>
<p>
To create a merchant:
</p>
<ol>
<li>
<bold>Create</bold> your NPC as usual.
</li>
<li>
Add an extra "Super-class", <bold>cMerchant</bold>, on top
of (and with higher priority than) the NPC's basic class.
</li>
<li>
<bold>Specific properties</bold> are required depending upon exactly what
the NPC will sell. See below.
</li>
</ol>
<br/><section><p><bold><big>Selling equipment</big></bold></p></section>
<p>
If you wish your merchant to sell equipment:
</p>
<ol>
<li>
Fill in <bold>pMerchantForSale</bold> with a list of items that
the merchant is selling. For each item, an object class, the
number of items in stock, the restock rate, and the maximum
stock are stored. You can also customize the price for an item,
making it more or less expensive for a particular merchant.
</li>
<p>
You can leave this blank if you fill in pMerchantForSaleInit; see
below.
</p>
<li>
Since many merchants sell the same basic goods, such as bandages,
you can fill in <bold>pMerchantForSaleInit</bold> with a series
of callback functions that indicate "classes" of objects that
are sold. The fantasy library containes some
default callback functions, <bold>MerchantForSaleInitArmor(),
MerchantForSaleInitWeapons(), MerchantForSaleInitFirstAid(),
MerchantForSaleInitLighting(),
and MerchantForSaleInitContainers()</bold> which include default
armor, weapons, firt-aid equipment, lighting, and containers.
For each of these callbacks, you can scale the number of goods
a merchant stocks, how quickly the stock arrives, and how much
the merchant charges for the stock.
</li>
<p>
You can <bold>write your own callbacks</bold> if you wish.
</p>
</ol>
<p>
You might wish to change some other properties of the merchant:
</p>
<ul>
<li>
<bold>pAIValueObject</bold> will cause the merchant to value some
items more or less than others. This is another way of increasing
or decreasing the amount the merchant will charge.
</li>
<li>
<bold>gMerchantCurrencyError</bold> affects the amount that the
merchant will round a price to. The default is 2 (percent).
See <bold>CurrencyIAToCoin()</bold>
</li>
<li>
<bold>gMerchantCurrencyMax</bold> controls how many currencies the
merchant will include in a price. The default is 2, allowing
for prices like "2 gp, 1 sp", not "2 gp, 1 sp, 3 cp".
See <bold>CurrencyIAToCoin()</bold>
</li>
<li>
<bold>pMerchantLikeScale</bold> affects how much the merchant will
adjust his price based on how much the merchant likes (or dislikes) the
character he is selling to.
</li>
<li>
<bold>pMerchantLikeThreshhold</bold> will cause merchants to refuse
to sell (or do any transaction with) characters the merchant doesn't
like.
</li>
</ul>
<br/><section><p><bold><big>Buying equipment</big></bold></p></section>
<p>
If you wish the merchant to buy equipment from players:
</p>
<ol>
<li>
Set <bold>pMerchantBuyBack</bold> to TRUE so the merchant is allowed
to purchase used equipment.
</li>
</ol>
<p>
You may also wish to set the following values:
</p>
<ul>
<li>
<bold>pMerchantBuyBackNotNormallyStocked</bold> affects the price
the merchant is willing to pay for items that the merchant doesn't
normally stocked. If not specified, the number is randomly
generated.
</li>
<li>
<bold>pMerchantCash</bold> specified how much money the merchant
has, and ultimately how expensive of items the merchant can buy.
</li>
<li>
<bold>gMerchantBuyBackScale</bold> controls the price
that <bold>all</bold> merchants are willing to pay for used equipment.
This global will help you control your world's economy, affecting
how much money is introduced into the world by merchants.
</li>
<li>
When the merchant purchases an item that isn't normally
stocked, <bold>gMerchantBuyBackSellDown</bold> determines how long
the item will remain on the merchant's list before being "sold off"
and removed from the list.
</li>
<li>
<bold>pMerchantForSale, pMerchantLikeScale, pAIValueObject,
and pMerchantLikeThreshhold</bold> will also affect the merchant's
purchasing of used equipment.
</li>
</ul>
<br/><section><p><bold><big>Making change</big></bold></p></section>
<p>
By default, merchants are willing to exchange coins for larger
or smaller denominations with players. If you wish to
disable this:
</p>
<ol>
<li>
Set <bold>pMerchantChange</bold> to FALSE to prevent a merchant from
making change.
</li>
</ol>
<p>
You may also wish to set the following values:
</p>
<ul>
<li>
<bold>pMerchantLikeThreshhold</bold> will affect whether the merchant
will make change for the player.
</li>
</ul>
<br/><section><p><bold><big>Repairing objects</big></bold></p></section>
<p>
To allow a merchant to repair objects:
</p>
<ol>
<li>
Set <bold>pMerchantRepair</bold> to TRUE.
</li>
<li>
Modify the NPC's <bold>pSkills</bold> property so it includes the
appropriate repair skills. For example: A blacksmith would
have oSkillBlacksmith, while a leather worker would have oSkillLeatherWorking.
</li>
<li>
Make sure all the <bold>tools</bold> that the merchant needs to
repair are either held by the NPC or located in the same room. Merchants
can't repair without tools. They <bold>can</bold> get buy without
materials though.
</li>
</ol>
<p>
You may also wish to set the following values:
</p>
<ul>
<li>
<bold>pMerchantLikeThreshhold</bold> will affect whether the merchant
will make change for the player.
</li>
<li>
<bold>pMerchantCash</bold> affects how much cash the merchant has
on hand, and (more importantly) how much patronage will affect
how much the merchant likes the player's character. Spending lots
of money with a merchant make the merchant like the player character better.
</li>
</ul>
<br/><section><p><bold><big>Identifying objects</big></bold></p></section>
<p>
To allow a merchant to identify objects:
</p>
<ol>
<li>
Set <bold>pMerchantIdentify</bold> to TRUE.
</li>
<li>
Modify the NPC's <bold>pSkills</bold> property so it includes the
appropriate identification skills. For example: A botonist might
have oSkillBotony, while a magical scholar might have oSkillMagicHistory.
</li>
</ol>
<p>
You may also wish to set the following values:
</p>
<ul>
<li>
<bold>pMerchantLikeThreshhold</bold> will affect whether the merchant
will make change for the player.
</li>
<li>
<bold>pMerchantCash</bold> affects how much cash the merchant has
on hand, and (more importantly) how much patronage will affect
how much the merchant likes the player's character. Spending lots
of money with a merchant make the merchant like the player character better.
</li>
</ul>
<br/><section><p><bold><big>Bankers</big></bold></p></section>
<p>
To allow a merchant to be a banker:
</p>
<ol>
<li>
Set <bold>pMerchantBanker</bold> to 1. If you use a higher value then
the merchant will charge more for deposits than normal. Lower values (more
than 0) will resulting in cheaper banking.
</li>
</ol>
<p>
You may also wish to set the following values:
</p>
<ul>
<li>
<bold>gBankerPricePerVolume</bold> is the "average" that a banker
charges to store 1 liter of object volume.
</li>
<li>
<bold>gBankerMaxDeposit</bold> is the maximum number of items a
character can store in their bank accounts. This ensures that characters
don't hoard too much equipment.
</li>
<li>
<bold>pMerchantLikeThreshhold</bold> will affect whether the merchant
will take deposits from the character..
</li>
<li>
<bold>pMerchantCash</bold> affects how much cash the merchant has
on hand, and (more importantly) how much patronage will affect
how much the merchant likes the player's character. Spending lots
of money with a merchant make the merchant like the player character more.
</li>
</ul>
<br/><section><p><bold><big>Teach skills</big></bold></p></section>
<p>
To allow a merchant to teach skills to a player:
</p>
<ol>
<li>
Set <bold>pMerchantSkills</bold> to a list of skills that the merchant
teaches, along with the cost to learn the skill.
</li>
</ol>
<p>
You may also wish to set the following values:
</p>
<ul>
<li>
<bold>pMerchantLikeThreshhold</bold> will affect whether the merchant
will teach the character.
</li>
<li>
<bold>pMerchantCash</bold> affects how much cash the merchant has
on hand, and (more importantly) how much patronage will affect
how much the merchant likes the player's character. Spending lots
of money with a merchant make the merchant like the player character more.
</li>
</ul>
<br/><section><p><bold><big>Healer</big></bold></p></section>
<p>
To allow a merchant to heal the player's wounds:
</p>
<ol>
<li>
Use the <bold>cMerchantHealer</bold> class instead of the
cMerchant class.
</li>
<li>
You may want to adjust <bold>pMerchantHealer</bold> to control
how expensive/cheap the merchant is.
</li>
<li>
<bold>pSkills</bold> can be changed so the merchant has
a higher or lower oSkillFirstAid, which affects what kind of
healing is possible as well as the speed at which the healing
occurs.
</li>
</ol>
<p>
You may also wish to set the following values:
</p>
<ul>
<li>
<bold>gHealerPriceBandage, gHealerPriceSplint,
and gHealerPriceRegrow</bold> affect the typical cost of
getting an injury healed.
</li>
<li>
<bold>pMerchantLikeThreshhold</bold> will affect whether the merchant
will heal the character.
</li>
<li>
<bold>pMerchantCash</bold> affects how much cash the merchant has
on hand, and (more importantly) how much patronage will affect
how much the merchant likes the player's character. Spending lots
of money with a merchant make the merchant like the player character more.
</li>
</ul>
<br/><section><p><bold><big>Miscellanrous services</big></bold></p></section>
<p>
Healers are really based on the merchant's "Miscellaneous" services
option. If you wanted to create a barber, for example, you'd write
some code to allow for miscellaneous services:
</p>
<ol>
<li>
Write a <bold>MerchantMiscEnum()</bold> method for the merchant. This
method enumerates all the miscellaneous services the merchant offers.
</li>
<li>
Write a <bold>MerchantMiscDo()</bold> method, causing the merchant
to act on a purchased service.
</li>
<li>
Optionally, set <bold>pMerchantMiscCommand</bold> to the phrase
displayed in the merchant's context menu. The default is,
"Do you provide any services?". You could change this to,
"I want my hair cut."
</li>
<p>
For this to work, you must include the parser rules in the merchant
for "I want my hair cut" to either "`merchantforsale `misc" or
"`merchantforsale `hair". (See below.)
</p>
<li>
If you use the "`merchantforsale `hair" option (or another custom
tag), you'll need
to set <bold>pMerchantMiscCommandTag</bold> to "`hair".
</li>
</ol>
<p>
You may also wish to set the following values:
</p>
<ul>
<li>
<bold>pMerchantLikeThreshhold</bold> will affect whether the merchant
will provide a service for the character.
</li>
<li>
<bold>pMerchantCash</bold> affects how much cash the merchant has
on hand, and (more importantly) how much patronage will affect
how much the merchant likes the player's character. Spending lots
of money with a merchant make the merchant like the player character more.
</li>
</ul>
<br/><section><p><bold><big>Miscellanrous notes</big></bold></p></section>
<p>
You may also wish to modify the following:
</p>
<ul>
<li>
<bold>MerchantIsOpen()</bold> can be overridden to see if the
merchant is open for business.
</li>
</ul>
Creating relationships between your NPCs.
<section><p><bold><big>Relationships - cRelationship</big></bold></p></section>
<p>
In Circumreality, NPCs can have <bold>relationships</bold> with other NPCs. These might
include being a spouse, child, coworker, friend, etc. The relationshps
a NPC has affects how much one NPC knows about another, as well as how
much the NPC is willing to say about the other NPC.
</p>
<br/><section><p><bold><big>cRelationship object</big></bold></p></section>
<p>
Before specifying what relationships a NPC has, you must first specify
the types of relationships: spouses, parents, children, enemies, friends, etc.
</p>
<p>
The following relationships are already built into Circumreality, and handled by
the listed object:
</p>
<small><p align=center><table width=80%>
<tr>
<td><bold>Major relationship</bold></td>
<td><bold>Minor relationship</bold></td>
<td><bold>Object</bold></td>
<td><bold>Inverse</bold></td>
</tr>
<tr>
<td>"auntuncle"</td>
<td><italic>None</italic></td>
<td>oRelationshipAuntUncle</td>
<td>"niecenephew"</td>
</tr>
<tr>
<td>"child"</td>
<td><italic>None</italic></td>
<td>oRelationshipChild</td>
<td>"parent"</td>
</tr>
<tr>
<td>"coworker"</td>
<td><italic>None</italic></td>
<td>oRelationshipCoWorker</td>
<td>"coworker"</td>
</tr>
<tr>
<td>"employee"</td>
<td><italic>None</italic></td>
<td>oRelationshipEmployee</td>
<td>"employer"</td>
</tr>
<tr>
<td>"employer"</td>
<td><italic>None</italic></td>
<td>oRelationshipEmployer</td>
<td>"employee"</td>
</tr>
<tr>
<td>"enemy"</td>
<td><italic>None</italic></td>
<td>oRelationshipEnemy</td>
<td>"enemy"</td>
</tr>
<tr>
<td>"friend"</td>
<td><italic>None</italic></td>
<td>oRelationshipFriend</td>
<td>"friend"</td>
</tr>
<tr>
<td>"friend"</td>
<td>"good"</td>
<td>oRelationshipFriendGood</td>
<td>"friend"</td>
</tr>
<tr>
<td>"girlboyfriend"</td>
<td><italic>None</italic></td>
<td>oRelationshipGirlBoyFriend</td>
<td>"girlboyfriend"</td>
</tr>
<tr>
<td>"girlboyfriend"</td>
<td>"hidden"</td>
<td>oRelationshipGirlBoyFriendHidden</td>
<td>"girlboyfriend", "hidden"</td>
</tr>
<tr>
<td>"grandchild"</td>
<td><italic>None</italic></td>
<td>oRelationshipGrandChild</td>
<td>"grandparent"</td>
</tr>
<tr>
<td>"grandparent"</td>
<td><italic>None</italic></td>
<td>oRelationshipGrandParent</td>
<td>"grandchild"</td>
</tr>
<tr>
<td>"niecenephew"</td>
<td><italic>None</italic></td>
<td>oRelationshipNieceNephew</td>
<td>"auntuncle"</td>
</tr>
<tr>
<td>"parent"</td>
<td><italic>None</italic></td>
<td>oRelationshipParent</td>
<td>"child"</td>
</tr>
<tr>
<td>"rival"</td>
<td><italic>None</italic></td>
<td>oRelationshipRival</td>
<td>"rival"</td>
</tr>
<tr>
<td>"sibling"</td>
<td><italic>None</italic></td>
<td>oRelationshipSibling</td>
<td>"sibline"</td>
</tr>
<tr>
<td>"spouse"</td>
<td><italic>None</italic></td>
<td>oRelationshipSpouse</td>
<td>"spouse"</td>
</tr>
</table></p></small>
<br/><section><p><bold><big>cRelationship properties</big></bold></p></section>
<p>
Every relationship object (associated with a relationship) has a list
of properties that describe the "typical" relationship:
</p>
<ul>
<li>
<bold>pNLPNounName</bold> and <bold>pNLPParseName</bold> are used to
name the relationship, such as "grandmother".
</li>
<li>
<bold>pRelationshipInfluence</bold> simulates the NPC telling its
relations about what a good/bad guy the player is. If this is a large number
then any like/trust impression that the player character makes on the
NPC also has a large impact on the NPC's relations. Thus, a "spouse" relationship
would have a much larger impact than a "coworker" relationship. This
number can be negative for an "enemy" relationship where making friends with
one NPC causes the NPC's enemies to become unfriendly towards the player.
</li>
<li>
<bold>pRelationshipInverse</bold> indicates the opposite relationship.
Thus, if NPC A is a "employer" to NPC B, then NPC B must be an
"employee" to NPC A.
</li>
<li>
<bold>pRelationshipLike</bold> indicates how much the NPC likes/trusts his
relations. "friend" might have a [1, 1] indicating theat if NPC A is a friend
of NPC B then, in general, NPC A's like/trust of NPC B is +1.0 higher than
one would normally expect. Conversely, "enemy" might be [-6, -6], indicating
a default like/trust of 6-points below.
</li>
<li>
<bold>pRelationshipString"</bold> is the lower-case string for the relationship,
such as "spouse".
</li>
<li>
<bold>pRelationshipTalkAbout</bold> controls how willing the NPC is to divulge
personal information about the relation. A husband may be wary about divulging
personal information about his wife, and have high values, like [4, 4]. However,
NPCs are willing to say anything (bad) they know about enemies,
with a [-4, -4], score.
</li>
<li>
Setting <bold>pHidden</bold> to TRUE will cause the relationship to
be secret. It won't be spoken of. The only way the player can learn it is
by observation.
</li>
</ul>
<br/><section><p><bold><big>Minor relationships</big></bold></p></section>
<p>
So far, I've just been discussing "major" relationships. Although no
"minor" relationships are built in, you may wish to add some. Minor
relationships are variations on the major ones. For example: You could
have a "loving" (minor relationship) "spouse" (major relationship). The loving
spouse might have a higher pRelationshipLike.
</p>
<p>
As you may have noticed, there is an important naming schemes for
relationship objects. All relationship objects are named with a prefix
of "oRelationship", followed by the major relationship, and then followed
by the minior relationship, if one exists. Thus, the spouse relationship
object is "oRelationshipSpouse", while the loving-spouse object
is "oRelationshipSpouseLoving".
</p>
<p>
This naming scheme is critical since it's used
by <bold>RelationshipToObject()</bold> to convert a major (and minor)
relationship string into a relationship object.
</p>
<br/><section><p><bold><big>Creating your own relationship object</big></bold></p></section>
<p>
To create your own relationship object, such as that of a "priest" to
his "follower"(s):
</p>
<ol>
<li>
Create an object based off <bold>cRelationship</bold>.
</li>
<li>
The object should <bold>not be contained</bold> in any other object.
</li>
<li>
Make sure to <bold>name it "oRelationshipXXXYYY"</bold>, where XXX is the
major relationship, and YYY is the minor relationship, if there is one.
Thus, your new relationship object would be "oRelationshipPriest".
</li>
<li>
Make sure to fill in all <bold>the relationship properties</bold>, as listed
above.
</li>
<li>
If the relationship has an inverse, <bold>create
the inverse object too.</bold> Thus, if you created
oRelationshipPriest, you'd also need to create oRelationshipFollower.
</li>
<li>
If you have created a <bold>minor relationship</bold>, such as oRelationshipFollowerFanatic,
then make sure you also have an object that will handle
the major relationship, such as oRelationshipFollower.
</li>
</ol>
<br/><section><p><bold><big>Assigning relationships to a NPC</big></bold></p></section>
<p>
When you want to assign one or more relationships to a NPC, do the following:
</p>
<ul>
<li>
Set the NPC's <bold>pAIRelationships</bold> to indicate what relationships
the NPC has. For example: [ [oNPCWife, "spouse"], [oNPCChild, "child"] ] causes
the NPC to be married and have one child.
</li>
<p>
Make sure that the <bold>NPC's inverse relationship</bold> is included in
the pAIRelationships of the NPC's relations. For example: You would need
to set oNPCChild.pAIRelationships to [[oNPCFather, "parent"]].
</p>
<li>
Additionally, you could write your
own <bold>AIRelationships()</bold> method. This might be useful to ensure
that all members of a town have a relationship with the town mayor, etc.
</li>
<li>
You can also create relationship by adding a pAIRelationships
to <bold>cFaction objects</bold>.
</li>
</ul>
<br/><section><p><bold><big>Inquiring about NPC relationships</big></bold></p></section>
<p>
If you're writing code and wish to find out what kinds of relationships
a NPC has with other NPCs, you can do the following:
</p>
<ul>
<li>
Call the NPC's <bold>AIRelationships()</bold> and search through the list
yourself.
</li>
<li>
Call the NPC's <bold>AIRelationshipExists()</bold> to see if the NPC
has a relationship with a specific NPC. If the NPC does, then a list of the
relationship objects will be returned. Alternatively, the NPC may merely
know the other NPC because they're from the same town, causing a number (in
this case 2.0) to be returned.
</li>
<p>
The NPC's "home town" is automatically determined based on the NPC's
creation room. If you wish to specify a specific map (or maps) then
set <bold>pAIHomeMap</bold>, or write your
own <bold>AIHomeMap()</bold> property.
</p>
</ul>
<br/><section><p><bold><big>Player database NPC relationships</big></bold></p></section>
<p>
As playes meet NPCs, a database about the NPC is automatically saved, allowing
the player to then view relationship graphs of the NPC. Most of the
relationship information is automatically stored, so you won't need to
worry about it. However, in the event that you do need to modify the
relationshipd database:
</p>
<ul>
<li>
The database is stored in the player
character's <bold>pKnowledgeRelationships</bold> property. You shouldn't
need to access this directly.
</li>
<li>
You can find remembered relationship information specific to a NPC
by calling the PC's <bold>KnowledgeRelationshipsGet()</bold>.
</li>
<li>
To have the player's character remember relationship information,
call <bold>KnowledgeRelationshipsSet()</bold>. You can store information
about the fact that a relationship exists between two NPCs, the
NPC's last like/trust for the player, and the NPC's last known location.
</li>
</ul>
<br/><section><p><bold><big>Automatic conversations between relationships</big></bold></p></section>
<p>
You can have automatic conversations happen between two NPCs given a specific
relationship. This is handy to have friends say hello, enemies sneer at one
another, etc.
</p>
<p>
To do this, your relationship object should have:
</p>
<ul>
<li>
<bold>pAIConvScripts</bold> filled in with one or more conversation scripts
that might occur.
</li>
</ul>
<p>
Each cConvScript specific to the relationship needs to have:
</p>
<ul>
<li>
<bold>pConvScriptAutoRelationship</bold> set to the relationship object.
</li>
<li>
<bold>pConvScriptNPCs</bold> set to 2, since relationships only exist
between two NPCs.
</li>
<li>
<bold>pConvScriptAutoPriority</bold> should be lowered (below 50) so that
other non-autmatic conversation scripts will take priority.
</li>
<li>
Other <bold>cConvScript</bold> properties as appropriate.
</li>
<li>
You may wish to modify the <bold>pConvScriptResource</bold> resource so
that the relationship between the two NPCs is automatically exposed.
That way, players that see the conversation happen will learn of
the relationship.
</li>
</ul>
Favors, the conversational-game equivalent of "loot".
<section><p><bold><big>Favors</big></bold></p></section>
<p>
Once players have gotten successfully friendly with NPCs (by giving them
gifts, doing quests for them, etc.), the PCs can ask the NPCs
for <bold>favors</bold>.
</p>
<p>
"Favors" are the equivalent to "loot" in the "kill 10 rats" game. It acts
as an incentive/reward, and also enables new gameplay. NPCs can grant
a variety of favors:
</p>
<ul>
<li>
<bold>goodword</bold> - The NPC puts in a good word to one of its
relations, providing the player with a "leg up" when interacting with
the relation (NPC).
</li>
<li>
<bold>knowledge</bold> - The NPC will tell the player a tasty rumor,
which the player can then use to get procede elsewhere in the game.
</li>
<li>
<bold>money</bold> - This is a bit mundane, but players can ask
for a bit of extra cash.
</li>
<li>
<bold>object</bold> - The NPC gives the player an object, like a key (that
provides access to another part of the world), and a letter of reference
to another NPC.
</li>
<li>
<bold>reputation</bold> - The PC's reputation (such as how heroic the PC is)
can be enhanced, affecting how other NPCs react to the PC.
</li>
<li>
<bold>skill</bold> - The NPC trains the PC in a new skill, such as a long-forgotten
language that's needed to translate an important document.
</li>
<li>
<bold>speak</bold> - The NPC speaks some words of wisdom (such as hints) to
the player.
</li>
<li>
<bold>story</bold> - The NPC tells the PC a special story.
</li>
<li>
<italic>custom</italic> - Whatever you wish to code.
</li>
</ul>
<p>
To ask for a favor, the player types "Can you do me a favor?"
This <bold>automatically</bold> appears on the NPC's context menu.
</p>
<p>
After asking for a favor, the player will be presented with a list of favors,
and will be able to choose one.
</p>
<p>
After the favor is granted, the player <bold>won't</bold> be able to ask
for another favor for aroud half a day (of real time), and until the player
has rebuilt some of the like/trust lost by asking for the favor.
</p>
<br/><section><p><bold><big>Coding</big></bold></p></section>
<p>
To get a NPC to provide favors, you need to do the following:
</p>
<ol>
<li>
<bold>Nothing!</bold> Some defaults already exist for:
</li>
<p>
"goodword" favors are automatically created for all of the NPC's positive (non-enemy)
relations so long as the player knows about these relations. (See KnowledgeRelationshipsGet()).
</p>
<p>
A "money" favor is automatically created that defaults to giving pAIFavorMoney inflation-adjusted
units.
</p>
<p>
Some objects, based on pAIGivable, are automatically added by AIFavor().
</p>
<li>
Having said that, you'll probably want to modify <bold>pAIFavor</bold> to provide some
custom favors.
</li>
</ol>
<p>
You may also wish to modify:
</p>
<ul>
<li>
<bold>pAIFavorAsk</bold> is the question that the player asks the NPC to
be granted a favor. This defaults to "Could you do me a favor?"
</li>
<li>
<bold>pAIFavorLike</bold> controls how many favors a NPC will grant, as well as how
much the NPC must like/trust the player to grant them.
</li>
<li>
<bold>pAIFavorLikeCost</bold> affects how much the NPC's like/trust goes down
after a favor has been asked.
</li>
<li>
<bold>pAIFavorQuestion</bold> is what the NPC speaks after the player
has asked for a favor.
</li>
<li>
<bold>pAIFavorTime</bold> specified the number of real days that the player must
wait before being able to ask the NPC for another favor.
</li>
<li>
<bold>AIFavorDoCustom()</bold> can be used to write your own custom favor
behaviors, such as, "Can you quack like a duck for the next two hours?"
</li>
</ul>
<p>
If you wish a NPC to grant a favor outside of the default context menu,
then you may wish to call AIFavorDo() and AIFavor().
</p>
Provides for basic fantasy RPG genre, such as one with Elves and Dwarves.
This is a library that includes basic fantasy RPG genre elements, including various races, languages, etc. You can use it to base your world off of, or merely as an example.
The fantary library requires the Basic RPG library (and also Basic IF library) to work. You should set the Basic Fantasy library to a higher priority than the Basic RPG library.
How to easily create books.
<section><p><bold><big>Books, letters, and signs - cBook and friends</big></bold></p></section>
<p>
Circumreality includes a cBook object that makes it easy to create your
own books. To create a book:
</p>
<ol>
<li>
<bold>Create an object</bold> as normal.
</li>
<li>
Sub-class it off of <bold>cBook</bold>.
</li>
<li>
Set <bold>pNLPNounName</bold> and <bold>pNLPParseName</bold> if
you wan't to use a more creative name for your object than
just "book".
</li>
<li>
Set <bold>pBookCover</bold> to the title of the book.
</li>
<li>
Set <bold>pBookPages</bold> to the text (and/or graphics) to
be displayed on each page.
</li>
<li>
Set <bold>pBookLanguage</bold> to the language that the book
is written in. This defaults to "common".
</li>
</ol>
<p>
That's it.
</p>
<br/><section><p><bold><big>Advanced features</big></bold></p></section>
<p>
You may also wish to set the following properties:
</p>
<ul>
<li>
<bold>pBookAutoFirstPage</bold> causes the book to automatically
flip back to the first page after awhile. Use this for public
books so players find them at the start page.
</li>
<li>
<bold>pBookBindingDepth, pBookBindingRotate, pBookBindingWidth,
pBookLayoutCover, pBookLayoutPageLeft, pBookLayoutPageOffset,
and pBookLayoutPageRight</bold> should
be changed if you change the <bold>size or shape</bold> of
pBookVisualClosed, pBookVisualOpen,
and/or pBookVisualPage.
</li>
<li>
<bold>pBookCanTear</bold> controls whether a book can be
torn up or not.
</li>
<li>
<bold>pBookFont</bold> and <bold>pBookFontCover</bold> change the
font used by the book.
</li>
<li>
<bold>pBookFontFaceUnknownLanguage</bold> is used to use an
unreadable font (such as "Symbol") when a player character doesn't
understand the language that the book is in.
</li>
<li>
<bold>pBookNoCover</bold> and <bold>pBookNoCoverViewSingly</bold> turn
the book into a series of pages.
</li>
<li>
<bold>pBookPageLayoutNumberingLeft and
pBookPageLayoutNumberingRight</bold> control where the page numbers go.
</li>
<li>
<bold>pBookPageLayoutText</bold> controls where the text goes on the page.
</li>
<li>
<bold>pBookPageOpenTo</bold> affects the page that the book is opened to.
This must <bold>always</bold> be an even number.
</li>
<li>
<bold>pBookPagesCutScenes</bold> is a list of cut scenes resources for
each page, causing the "reading" of the book to play a cut scene.
</li>
<li>
<bold>pBookVisualClosed, pBookVisualOpen, and pBookVisualPage</bold> let
your change the color, size, and shape of the book.
</li>
</ul>
<p>
You may wish to write your own methods:
</p>
<ul>
<li>
<bold>BookPage()</bold> just returns a page from pBookPages by default.
You probably won't have to override this, but you might think of a reason.
</li>
<li>
<bold>BookPageEncode()</bold> makes it easy to automatically "encode"
a page so that the players have to decipher it. (Aka: a puzzle)
</li>
<li>
<bold>BookPageLayout()</bold> can be overridden to control the
layout of a specific page.
</li>
<li>
<bold>BookPageLayoutNumbering()</bold> affects where and how the
page numbering appears.
</li>
<li>
<bold>BookPageNextPrevious()</bold> is called when the player flips
to the next/previous page. If you wanted to booby-trap your book,
you could have it explore when the right page is flipped to.
</li>
<li>
<bold>BookPageRead()</bold> is called when a player asks to read
the page. By default this reads a cutscene from pBookPagesCutScenes.
</li>
<li>
<bold>BookPages()</bold> returns the number of pages in the book.
</li>
<li>
<bold>BookPageTouch()</bold> is called if a player touches a page
in the book.
</li>
<li>
<bold>BookVisualGetNoText()</bold> returns the visual of the book
without any text super-imposed on it.
</li>
</ul>
<br/><section><p><bold><big>Books are containers</big></bold></p></section>
<p>
Books are containers, and based off of cContainer. They can't hold
very much in them though, just other pieces of paper.
</p>
<p>
If an object (piece of paper) is placed in the book, and the
page is flipped (using BookPageNextPrevious()), then the
object will be hidden by setting its pHidden to TRUE. The
object will also have its pSearchBookPage set to the page
where the object was placed.
</p>
<p>
When a player flips back to that page, the object will be
unhidden, and the player will be notified of the object.
</p>
<p>
SearchInsideThis() is overridden for books so that a "Search the book"
command won't find anything. Instead, players will have to flip
through individual pages and see if anything falls out.
</p>
<br/><section><p><bold><big>Letters and bits of paper</big></bold></p></section>
<p>
You can create letters and bits of paper by:
</p>
<ol>
<li>
Create an object based on <bold>cPaperSheets</bold> if you wish
it to be displayed like a book with sheets on the left
and right, but with no cover,
or <bold>cPaperSheet</bold> to display a single sheet at a time.
</li>
<li>
<bold>Fill in properties</bold> as per cBook, except don't
worry about the cover.
</li>
</ol>
<br/><section><p><bold><big>Signing books (and letters) in cBook</big></bold></p></section>
<p>
You can allow players (or NPCs) to sign books and letters by:
</p>
<ol>
<li>
Set <bold>pBookSignaturesMax</bold> to the maximum number of signatures
allowed in the book. This defaults to 0.
</li>
</ol>
<p>
You might also wish to modify the following:
</p>
<ul>
<li>
You might wish to set <bold>pBookSignaturesPerPage</bold> and <bold>pBookSignaturesLastPage</bold> to
control how many signatures are on a page.
</li>
<li>
<bold>pBookSignatures</bold> can be pre-loaded with signatures.
</li>
<li>
You may wish to set <bold>pCanManipulateWhenHeldByOther</bold> to TRUE
so that PCs (or NPCs) can see, manipulate, and sign the book even if its
held by another PC (or NPC). This setting is particularly useful for petitions
where a PC can merely show it to another PC and not have to give it.
</li>
<li>
<bold>pBookSignaturesNPCs</bold>, <bold>pBookSignaturesPCs</bold>, and <bold>pBookSignaturesClass</bold> affect
whether players and/or NPCs are allowed to sign the book.
</li>
<li>
<bold>pBookDefaultAIShowLike</bold> and <bold>pBookDefaultAIShowLikeByClass</bold> control
how much a NPC must like the PC before they're willing to sign.
</li>
<li>
<bold>pBookDefaultAIShowIfSignLike</bold> and <bold>pBookDefaultAIShowIfNotSignLike</bold> control
how much the NPC's opinion of the PC is changed by being asked to sign.
</li>
<li>
<bold>pBookDefaultAIShowIfSignSpeak</bold> and <bold>pBookDefaultAIShowIfNotSignSpeak</bold> are
spoken before the NPC signs, or if the NPC decides not to sign.
</li>
<li>
To produce special reactions, write your
own <bold>DefaultAIShow()</bold> and <bold>DefaultAIOffer()</bold> methods.
</li>
</ul>
<br/><section><p><bold><big>Signs</big></bold></p></section>
<p>
You can create signs by:
</p>
<ol>
<li>
Create an object based on <bold>cSign</bold>.
</li>
<li>
Set <bold>pBookPages</bold> to a list with only one
item (aka: page), the writing on your sign.
</li>
<li>
<bold>Fill in other properties</bold> as per cBook, except don't
worry about the cover.
</li>
</ol>
Library with basic classes and sample monsters.
This library contains the basic cMonster class, along with some sample monsters. Use this for monsters, rather than cCharacter, because it creates a character that just attacks player characters and isn't terribly sociable.
Library of extra objects, such as furniture and food items.
This is a library of extra objects, such as furniture and food items.
This library provides sample code for a small world. You may wish to look at this code to see how to create a world.
This is a library for a small test world. It's included so you can look at the code to see how to create a world. If you already know how to create a world then you probably don't want this included in your project.
To get the test world to work:
1) Make sure you have installed CircumReality into c:\program files\mXac\CircumReality. If you installed to a different location then the resources won't load properly.
2) Select Misc, Compile.
3) Select Misc, Test Compiled code
4) A window will appear showing all the players that are logged in and playing at the moment; of course, this will be empty.
5) In the directory where you have saved your project (that you're working on now), you'll see a file, YourProjectName.crk. "YourProjectName" will be replaced by the filename you used for this project file.
6) Open the client, CircumReality.exe, and when selecting the world to play in, select YourProjectName.crk from whatever directory it's save in. (You can also hand this out to your friends so they can log in too.)
7) When you play using the YourProjectName.crk world-link, you'll be asked for an IP address to use. You can find the IP address by looking at the title-bar of the server window, the one that lists all current players. It'll show your current IP, or 127.0.0.1 if you're not currently connected to the internet.
8) That's all you need to get started.
This library contains many important functions
for the language.
The standard library contains many basic
definitions for the language, including built-in methods for strings, lists,
and those common to all objects. It also includes commonly used functions like
rand(), sin(), etc.
As a general rule, you need to include this
library in your project.
None
None
Returns a list of classes that this object is a
subclass of.
This returns a list of classes that this object
is a subclass of.
For example, if "dollarBill" is a
subclass of "money" and "paper" then DollarBill.ClassEnum()
returns ["DollarBill", "money", "paper"].
|
Parameter
Name |
Description |
|
Return
value description |
List of classes that the object is a member
of. |
Overrides: Call
only the highest priority method
Common to all objects
Returns TRUE of an object is a member of the
given class.
This returns TRUE of an object is a member of
the given class.
For example, if "dollarBill" is a
subclass of "money" and "paper" then
DollarBill.ClassQuery("money") returns TRUE.
|
Parameter
Name |
Description |
|
Class |
This is the class to look query. It must be a
string. |
|
Return
value description |
TRUE is the object is a subclass of the class, or FALSE if it isn't. |
Overrides: Call
only the highest priority method
Common to all objects
Duplicates the object.
This duplaces the object.
Example:
string = "Hello Mike!";
string2 = string.Clone();
string.StringAppend ("test");
Results:
string == "Hello Mike!test";
string2 == "Hello Mike!"
|
Parameter
Name |
Description |
|
Return
value description |
The cloned object. |
Overrides: Call
only the highest priority method
Common to all objects
Automatically run immediately after an object is
created.
If an object supports the Constructor() method
then when the object is created, the Constructor() method will automtically be
called.
If an object has one or more superclasses with
Constructor()'s the superclass's constructors will be called first, working its
way up the chain to the object's Constructor().
NOTE: The object should NOT delete itself in its
own Constructor().
|
Parameter
Name |
Description |
|
Return
value description |
None |
Overrides: Call
all the methods, from lowest to highest priority – don’t stop
Common to all objects
Automatically run immediately after an object is
reloaded.
If an object supports the Constructor2() method
then when the object is reloaded from a saved session (NOT created), the
Constructor2() method will automtically be called.
If an object has one or more superclasses with
Constructor2()'s the superclass's constructors will be called first, working
its way up the chain to the object's Constructor2().
NOTE: The object should NOT delete itself in its
own Constructor2().
|
Parameter
Name |
Description |
|
Return
value description |
None |
Overrides: Call
all the methods, from lowest to highest priority – don’t stop
Common to all objects
Returns the object that contains this object.
This returns the object that contains the
current object.
For example: If the "money" object is
contained within the "wallet" object it will return the wallet
object.
|
Parameter
Name |
Description |
|
Return
value description |
The object that contains this object, or NULL
if it isn't contained. |
Overrides: Call
only the highest priority method
Common to all objects
Moves the object into another object.
This moves the object so it's contained by
another object.
For example:
money.ContainedInSet (wallet);
return money.ContainedInGet();
This resturns "wallet".
|
Parameter
Name |
Description |
|
MoveTo |
The object to move this one into. This can
either be an object or NULL (which will cause the object to not be contained
in any other object.) |
|
Return
value description |
TRUE is the move succedes, FALSE if it fails (perhaps because the
container object no longer exists.) |
Overrides: Call
only the highest priority method
Common to all objects
Returns a list of objects that this object
contains.
This returns a list of objects that are
contained by this object.
For example:
money.ContainedInSet (wallet);
return wallet.ContainsEnum();
This returns [money].
|
Parameter
Name |
Description |
|
Return
value description |
List of objects that this object contains. The
list may be empty if the object contains nothing. |
Overrides: Call
only the highest priority method
Common to all objects
Deletes the object along with all its contained
objects.
This deletes the object along with all the
object it contains, and the ones they contain.
If you just with to delete the object by itself
(and leave the contained objects around) then call "delete
<object>".
|
Parameter
Name |
Description |
|
Return
value description |
TRUE is deleting succeded, FALSE if it failed. |
Overrides: Call
only the highest priority method
Common to all objects
Automatically run immediately before the object
is destroyed.
If an object supports the Destructor() method
then when the object is deleted, the Destructor() method will automtically be
called.
If an object has one or more superclasses with
Destructor()'s the object's destructor will be called first, working its way
down to the superclasses.
When an application is quickly shut down, the
destructors of the remaining objects may not be called (to speed up shutdown.)
NOTE: The object should NOT delete itself in its
own Destructor().
|
Parameter
Name |
Description |
|
Return
value description |
None |
Overrides: Call
all the methods, from highest to lowest priority – don’t stop
NOT Common to all objects
Adds a new layer to an object.
This adds a new layer to an object. The index of
the layer (for purposes of LayerGet(), etc.) will depend upon the priority.
Adding a new layer will add all of the methods
of the class to the object. It will NOT add the properties of the class,
although if the class provides any property get/set code, that will be added.
Example:
Object.LayerAdd ("LayerName",
"LayerClass", 43.54);
|
Parameter
Name |
Description |
|
LayerName |
String to identify the layer. The name is only
to make it easier for the application to monitor the layers. |
|
LayerClass |
String representation of the layer class. If the class isn't valid the
layer will not be created. |
|
LayerPriority |
Priority for the layer. Layers are ordered
such that the ones with the highest priorities have their methods take
precedence over the others. |
|
Return
value description |
TRUE if it succedes. FALSE if there's an error (such as a bad layer
class). |
Overrides: Call
only the highest priority method
Common to all objects
Gets information about a layer.
This returns information about a layer: its
name, class, and priority.
Example:
return Object.LayerGet (0);
Returns ["LayerName", "LayerClass",
43.54].
|
Parameter
Name |
Description |
|
LayerNum |
Layer number, from 0 to LayerNumber()-1. |
|
Return
value description |
This returns a list. List[0] is the layer name. List[1] is the layer's
class, as a string. List[2] is the layer's rank; higher ranks will always
appear first on the list. |
Overrides: Call
only the highest priority method
Common to all objects
Adds an individual method to a layer.
This adds an individual method to a layer.
Applications can use this to dynamically create methods for objects.
Example:
Object.LayerMethodAdd (0,
"MyNewMethod", Trace);
This adds a new method to layer 0, called
"MyNewMethod". When the method is called, it really calls into the
code of the function, "Trace".
|
Parameter
Name |
Description |
|
LayerNum |
Layer number, from 0 to LayerNumber()-1. |
|
NewMethodName |
The name of the new method. This must be a string (with a valid method
name) or a method. |
|
CallThis |
This is the code that will be run when the
method is called. It must either be the name of an existing function or
method. If it's a method, the method must exist in the object; it can come
from another object if "Object.Method" is used to specify the
object. |
|
Return
value description |
TRUE if the method was added, FALSE if there's an error. |
Overrides: Call
only the highest priority method
Common to all objects
Returns a list of methods in the layer.
This returns a list of methods in the layer.
If it is enumerating class-defined methods then
it only enumerates the public ones. If enumerating invidually added methods
then it enumerates all of them.
Example:
return Object.LayerMethodEnum(0, TRUE);
|
Parameter
Name |
Description |
|
LayerNum |
Layer number, from 0 to LayerNumber()-1. |
|
EnumClass |
If TRUE then enumerate all the public methods from the layer's class.
If FALSE then enumerate all individually added methods (using
LayerMethodAdd()). |
|
Return
value description |
List of methods, or NULL if an error occurs. |
Overrides: Call
only the highest priority method
Common to all objects
Removes an individual method to a layer.
This removes an individual method to a layer.
Applications can use this to dynamically remove methods for objects.
Example:
Object.LayerMethodRemove (0,
"MyNewMethod");
The removes "MyNewMethod" which was
added by calling LayerMethodAdd().
|
Parameter
Name |
Description |
|
LayerNum |
Layer number, from 0 to LayerNumber()-1. |
|
RemoveMethodName |
This removes an individual method to a layer. Applications can use
this to dynamically remove methods for objects. Example: Object.LayerMethodRemove (0, "MyNewMethod"); The removes "MyNewMethod" which was added by calling
LayerMethodAdd(). |
|
Return
value description |
TRUE if the method was removed, FALSE if
there's an error. |
Overrides: Call
only the highest priority method
Common to all objects
Returns the number of layers in an object.
This returns the number of layers in an object.
|
Parameter
Name |
Description |
|
Return
value description |
The number of layers in an object. |
Overrides: Call
only the highest priority method
Common to all objects
Adds code for getting or setting a property to a
layer.
This adds code for getting or setting an
individual property to a layer. Applications can use this to dynamically create
code to intercept property get and set for an object. (If no code exists then
the property is accessed directly.)
Example:
Object.PropertySet ("MyNewProp", 0);
Object.LayerPropGetSetAdd (0,
"MyNewProp", GetFunc, SetFunc);
After this is called, any attempts to change
"MyNewProp" will be passed to SetFunc(), and any attemps to get the
value will be passed to GetFunc().
|
Parameter
Name |
Description |
|
LayerNum |
Layer number, from 0 to LayerNumber()-1. |
|
PropertyName |
The name of the property. This must be a string. If the method already exists in the layer's individual methods then an
error will be returned. |
|
GetCode |
This is the code that
will be run when the property is read from. It must either be the name of an
existing function or method. If it's a method, the method must exist in the
object; it can come from another object if "Object.Method" is used
to specify the object. If this is NULL the
property will be accessed directly when it's read. |
|
SetCode |
This
is the code that will be run when the property is set. It must either be the
name of an existing function or method. If it's a method, the method must
exist in the object; it can come from another object if
"Object.Method" is used to specify the object. If this is NULL the property will be accessed
directly when it's written. |
|
Return
value description |
The number of layers in an object. |
Overrides: Call
only the highest priority method
Common to all objects
Returns a list of properties supported by the
layer.
This returns a list of properties supported by
the layer.
If it is enumerating class-defined properties
then it only enumerates the public ones. If enumerating invidually added
properties then it enumerates all of them.
Example:
return Object.LayerPropGetSetEnum(0, TRUE);
|
Parameter
Name |
Description |
|
LayerNum |
Layer number, from 0 to LayerNumber()-1. |
|
EnumClass |
If TRUE then enumerate all the public properties from the layer's
class. If FALSE then enumerate all individually added properties (using
LayerPropGetSetAdd()). |
|
Return
value description |
List of properties (as strings), or NULL if an
error occurs. |
Overrides: Call
only the highest priority method
Common to all objects
Removes an individual property from a layer.
This removes an individual property from a
layer. Applications can use this to dynamically remove property get/set code
for objects.
Example:
Object.LayerPropGetSetRemove (0,
"MyNewProp");
The removes "MyNewProp" which was
added by calling LayerPropGetSetAdd().
|
Parameter
Name |
Description |
|
LayerNum |
Layer number, from 0 to LayerNumber()-1. |
|
PropertyName |
The name of the property to remove. This must be a string. |
|
Return
value description |
TRUE if the property was removed, FALSE if
there's an error. |
Overrides: Call
only the highest priority method
Common to all objects
Removes a layer from an object.
This removes a layer from an object. Removing
the layer will also remove methods provided for in that layer.
|
Parameter
Name |
Description |
|
LayerNum |
Layer number, from 0 to LayerNumber()-1. |
|
Return
value description |
TRUE if the layer was removed, FALSE if there was an error. |
Overrides: Call
only the highest priority method
Common to all objects
Appends a list to the end of another.
This appends a list's elements to the end of the
current list. Replacement is IN PLACE, causing the original list to be
modified.
Example:
list = [1,2,3,[5,6]];
string.ListAppend ([8,9]);
The resulting list is [1,2,3,[5,6],8,9]
|
Parameter
Name |
Description |
|
Insert |
(Optional) What to append. |
|
Return
value description |
The list. |
Overrides: Call
only the highest priority method
Common to all objects
Appends one or items to the end of the string list.
This concatenates one or more items onto the end
of the current list. Any lists added are created as sub-lists. The current list
is modified in place.
Example:
var list = [1,2,3];
list.ListConcat (4, [5,6]);
results in:
list = [1,2,3,4,[5,6]]
|
Parameter
Name |
Description |
|
Concat |
First tiem to concatenate to the list. More
than one item can be added. |
|
Return
value description |
The list. |
Overrides: Call
only the highest priority method
Common to all objects
Inserts a list before the element of another.
This inserts a list's elements before an element
in the current list. Replacement is IN PLACE, causing the original list to be
modified.
Example:
list = [1,2,3,[5,6]];
string.ListInsert ([8,9], 1);
The resulting list is [1,8,9,2,3,[5,6]]
If AddSubLists is TRUE then the result would be:
[1,[8,9],2,3,[5,6]]
|
Parameter
Name |
Description |
|
Insert |
(Optional) What to insert. |
|
StartIndex |
(Optional) Element to insert before. If not specified then this will
be 0. |
|
AddSubLists |
(Optional) If this is TRUE then if the Insert
parameter is a list the list will be added as a sublist. If FALSE (or
undefined) then if Insert is a list its individual elements will be added. |
|
Return
value description |
The list. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Merges the second list onto the first.
This merges a second list onto the end of the
first. The current list is modified in place.
Example:
var list = [1,2,3];
list.ListConcat ([5,6]);
results in:
list = [1,2,3,5,6]
|
Parameter
Name |
Description |
|
Concat |
List that gets merged onto the end of the
current list. If this is not a list then the item will be merged into the
list. |
|
Return
value description |
The list. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Returns the number of items in the list.
This returns the number of items in the list.
Example:
var list = [1,2,3,[5,6]];
return list.ListNumber();
Returns 4.
|
Parameter
Name |
Description |
|
Return
value description |
The number of items in the list. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Prepends a list to the beginning of another.
This prepends a list's elements to the start of
the current list. Replacement is IN PLACE, causing the original list to be
modified.
Example:
list = [1,2,3,[5,6]];
string.ListPrepend ([8,9]);
The resulting list is [8,9,1,2,3,[5,6]]
|
Parameter
Name |
Description |
|
Insert |
(Optional) What to prepend. |
|
Return
value description |
The list. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Randomizes the orders of the elements in the
list.
This randomizes the order of the elements in the
list. The list is modified in place.
NOTE: This uses the Random() function for generating
the random numbers. If your code has been relying on a random seed this will
change the seed.
Example:
var list = [1,2,3,[5,6]];
list.ListRandomize();
|
Parameter
Name |
Description |
|
Return
value description |
The list. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Removes one or more items from the list.
This removes one or more items from the list.
The current list is modified in place.
Example:
var list = ["a", "hello",
"c", "e"];
list.ListRemove (1, 3);
results in:
list = ["a", "e"];
|
Parameter
Name |
Description |
|
StartIndex |
First item (by index) to remove from the list. |
|
EndIndex |
(Optional) One more than the last item to remove from the list. If
this is left blank then EndIndex is set to StartIndex+1, which causes it to
only delete StartIndex. |
|
Return
value description |
The list. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Replaces a portion of one list with another.
This replaces a portion of the current list with
another. Replacement is IN PLACE, causing the original list to be modified.
Example:
list = [1,2,3,[5,6]];
string.ListReplace ([8,9], 1, 2);
The resulting list is [1,8,9,3,[5,6]]
|
Parameter
Name |
Description |
|
ReplaceWith |
(Optional) What to replace the range with. |
|
StartIndex |
(Optional) Starting index to replace. If not specified then this will
be 0. |
|
EndIndex |
(Optional) Ending index to replace. If not
specified this will be the end of the list. |
|
Return
value description |
The list. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Reverses the elements in the list.
This reverses the elements in the list. The list
is modified in place.
Example:
var list = [1,2,3,[5,6]];
list.ListReverse();
Results:
list = [[5,6],3,2,1]
|
Parameter
Name |
Description |
|
Return
value description |
The list. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Searches through a sorted list for an element.
This searches through a sorted list for an
element. It returns the index to the element, or -1 if it can't be found.
The list have been sorted using ListSort() with
the same SortingFunction that's passed in.
Example:
list = ["a", "e", g",
"z"];
return string.ListSearch ("e");
This returns 1.
|
Parameter
Name |
Description |
|
SearchFor |
The element to look for. |
|
SortingFunction |
(Optional) This is a function or method that will sort the elements.
The function or method accepts two parameters, A and B. It should return a
negative number it A appears before B in the list, 0 if A is the same as B,
and 1 if A appears after B in the list. If no sorting function is passed then the list will be sorted
alphabetically or numerically. Make sure that all the elements are the same
type; don't mix numbers with strings, etc. |
|
Return
value description |
If the element is found, this is an index into
the list. If more than one element matches than an arbitrary one will be
returned. If an element isn't found then -1 is returnd. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Searches through a sorted list for an element to
insert before.
This searches through a sorted list for an
element to insert before. It can be used to keep the sorted even when inserting
new items.
Example:
list = ["a", "e",
"g", "z"];
list.ListInsert ("h",
list.ListSearchToInsert("h"));
The new list will be ["a",
"e", "g", "h", "z"];
|
Parameter
Name |
Description |
|
SearchFor |
The element to look for. |
|
SortingFunction |
(Optional) This is a function or method that will sort the elements.
The function or method accepts two parameters, A and B. It should return a
negative number it A appears before B in the list, 0 if A is the same as B,
and 1 if A appears after B in the list. If no sorting function is passed then the list will be sorted
alphabetically or numerically. Make sure that all the elements are the same
type; don't mix numbers with strings, etc. |
|
Return
value description |
Index in the list to insert before in order to
keep the list sorted. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Copies a portion of the list into a new list.
This copies a portion of the list into a new
list.
Example:
list =
[1,2,3,[5,6]];
return list.ListSlice (1, -1);
Returns [2,3].
|
Parameter
Name |
Description |
|
StartIndex |
The index into the list that will become the
start of the new list. If this is negative, this is the index from the end of
the list. (ListNumber() + StartIndex). |
|
EndIndex |
(Optional) The index into the list beyond the end of the list. This
must always be 0 or a negative value, with 0 keeping from StartIndex to the
end of the list, -1 discarding the last element, -2 discarding the last two,
etc. If this isn't specified then 0 (the end of the list) will be used. |
|
Return
value description |
The sliced out list. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Sorts the list.
This sorts the list. Sorting is in place, so the
current list is changed.
Example:
list = [6,2,3,1,4];
string.ListSort();
The resulting list is [1,2,3,4,6]
|
Parameter
Name |
Description |
|
SortingFunction |
(Optional) This is a function or method that
will sort the elements. The function or method accepts two parameters, A and
B. It should return a negative number it A appears before B in the list, 0 if
A is the same as B, and 1 if A appears after B in the list. If no sorting function is passed then the list
will be sorted alphabetically or numerically. Make sure that all the elements
are the same type; don't mix numbers with strings, etc. |
|
Return
value description |
The list. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Copies a portion of the list into a new list.
This copies a portion of the list into a new list.
Example:
list = [1,2,3,[5,6]];
return list.ListSubList(1,3);
Returns [2,3].
|
Parameter
Name |
Description |
|
StartIndex |
The index into the list that will become the
start of the new list. |
|
EndIndex |
(Optional) The index into the list that will become end of the new
list. If this isn't specified then the end of the list will be used. |
|
Return
value description |
The sliced out list. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Calls a method in an object.
This calls a method in an object. It can be used
to call methods using a string instead of the method.
NOTE: This only calls public methods.
For example:
return AnObject.MethodCall ("AMethod",
3, 5);
|
Parameter
Name |
Description |
|
Method |
Method name. This is either a method name or a
string. |
|
Param1 |
Parameters passed into the method. |
|
Return
value description |
Whatever the method returns, or Undefined if
the method doesn't exist. |
Overrides: Call
only the highest priority method
Common to all objects
Returns a list of the methods supported by the
object.
This returns a list of methods supported by the
object.
NOTE: This only returns public methods.
For example:
return AnObject.MethodEnum ();
Returns [Method1, Method2, etc.]
|
Parameter
Name |
Description |
|
Return
value description |
List of the methods supported. This does not
include automatic methods, such as MethodEnum(). If no methods are supported
by the object it returns an empty list. |
Overrides: Call
only the highest priority method
Common to all objects
Returns TRUE if the object supports the method.
This checks to see if an object supports a
method and returns TRUE if it exists.
NOTE: This only checks public methods.
For example:
return AnObject.MethodQuery
("AMethod");
|
Parameter
Name |
Description |
|
Method |
Method name. This is either a method name or a
string. |
|
Return
value description |
TRUE if the method exists, FALSE if it doesn't. |
Overrides: Call
only the highest priority method
Common to all objects
Returns the name of an object.
This returns the name of an object. It can
called by any function or method.
It is automatically called when an object is
concatenated to a string so that a proper name can be concatenated. If unsupported,
the class that the object was originally created from will be used as the name.
|
Parameter
Name |
Description |
|
Return
value description |
Should return a string for the name of the
object. |
Overrides: Call
only the highest priority method
Common to all objects
Returns a list of the properties supported by
the object.
This returns a list of the properties supported
by the object.
NOTE: This only returns public properties.
For example:
return AnObject.PropertyEnum ();
Returns ["AProperty",
"Property2", etc.]
|
Parameter
Name |
Description |
|
Return
value description |
List of the properties supported. If no
properties are supported by the object it returns an empty list. |
Overrides: Call
only the highest priority method
Common to all objects
Gets an object's property using a string for the
property name.
The gets and object's property, using a string
for a property name.
NOTE: This only gets public properties.
For example:
AnObject.AProperty = 5;
return AnObject.PropertyGet
("AProperty");
Returns 5.
|
Parameter
Name |
Description |
|
Property |
Property name. This must be a string. |
|
Return
value description |
Value of the property, or Undefined if it wasn't supported by the
object. |
Overrides: Call
only the highest priority method
Common to all objects
Returns TRUE if the object supports the
property.
This checks to see if an object supports a
property and returns TRUE if it exists.
NOTE: This only checks public properties.
For example:
AnObject.AProperty = 5;
AnObject.PropertyRemove ("AProperty");
return AnObject.PropertyQuery
("AProperty");
Returns FALSE.
|
Parameter
Name |
Description |
|
Property |
Property name. This must be a string. |
|
Return
value description |
TRUE if the property exists, FALSE if it doesn't. |
Overrides: Call
only the highest priority method
Common to all objects
Removes an object's property.
The deletes the object's property, using a
string for a property name.
NOTE: This only removes public properties.
For example:
AnObject.AProperty = 5;
AnObject.PropertyRemove ("AProperty");
Results: AProperty is removed from the object.
|
Parameter
Name |
Description |
|
Property |
Property name. This must be a string. |
|
Return
value description |
TRUE if the property is removed, FALSE if it isn't found. |
Overrides: Call
only the highest priority method
Common to all objects
Sets an object's property using a string for the
property name.
The sets and object's property, using a string
for a property name. If the property doesn't already exist in the object it's
created.
NOTE: This only sets public properties.
For example:
AnObject.PropertySet ("AProperty", 5);
return AnObject.AProperty;
Returns 5.
|
Parameter
Name |
Description |
|
Property |
Property name. This must be a string. |
|
Value |
What value to set the property as. |
|
Return
value description |
Value of the property, or undefined if there
is an error (such as the property name already used by a different class of
identifier). |
Overrides: Call
only the highest priority method
Common to all objects
Appends a string to the current one.
This appends the Insert string at the end of the
current string. Insertion is IN PLACE, causing the original string to be
modified.
Example:
string = "Hello";
string.StringAppend (" ! ");
The resulting string is "Hello ! ".
|
Parameter
Name |
Description |
|
Insert |
The string to append. |
|
Return
value description |
The string. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Compares two strings.
This compares the string with the Compare
string. It returns 0 if the two strings are the same, a negative value if the
string (this) should be placed before Compare, and a positive value if it
should be placed after Comapre.
Example:
string = "abacus";
return string.StringCompare ("zebra");
Returns a negative number.
|
Parameter
Name |
Description |
|
Compare |
String to compare against. |
|
CaseSensative |
(Optional)
If TRUE is passed then the comparison will be case sensative; if FALSE it's
case insensative. If not specified then the comparison is case sensative. |
|
Return
value description |
It returns 0 if the two strings are the same,
a negative value if the string (this) should be placed before Compare, and a
positive value if it should be placed after Comapre. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Appends one or more strings to the end of the
string object.
This concatenates one or strings onto the end of
the current string. The current string is modified in place.
Example:
var string = "Hello";
string.StringConcat (" there ", 534,
".");
results in:
string == "Hello there 543."
|
Parameter
Name |
Description |
|
Concat |
First string (or other value) to concatenate.
More than one string can be added. |
|
Return
value description |
The string. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Replaces %1, %2, etc. in the string.
This searches through the string for
"%1", "%2", ... "%9" and replaces them with the
1st, 2nd, ... 9th parameter in the StringFormat() call.
StringFormat() is extremely useful for
fill-in-the-blank strings.
Example:
var string = "My %2 is %1.";
return string.StringFomat ("Mike",
"name");
return "My name is Mike."
|
Parameter
Name |
Description |
|
Param1 |
If %1 is found, it is replaced by Param1. |
|
Param2 |
If %2 is found, it is replaced by Param2. |
|
Return
value description |
New string that includes the replacements. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Creates a string from one or more characters or
character codes.
This creates a string from one or more
characters ('a', 'b', etc.) or character codes (numbers).
Example:
var string;
string = string.StringFromCar (163, '2', '0',
'0', '4');
results in, the Pound-sterling symbol followed
by "2004"
|
Parameter
Name |
Description |
|
char |
The first character or chracter value to be
placed in the string. More than one character can be used. |
|
Return
value description |
New string that includes the replacements. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Inserts one string into another.
This inserts the Insert string into the current
string. Insertion is IN PLACE, causing the original string to be modified.
Example:
string = "Hello";
string.StringInsert (" ! ", 1);
The resulting string is "H ! ello".
|
Parameter
Name |
Description |
|
Insert |
(Optional) The string to insert. |
|
StartIndex |
(Optional) Character index to insert before. If not specified then
this will be 0. |
|
Return
value description |
The string. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Returns the number of characters in the string.
This returns the number of characters in the
string.
Example:
string = "Hi there!";
return string.StringLength();
Returns 9.
|
Parameter
Name |
Description |
|
Return
value description |
Number of characters in the string. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Inserts a string at the beginning.
This inserts the Insert string at the beginning
of the current string. Insertion is IN PLACE, causing the original string to be
modified.
Example:
string = "Hello";
string.StringPrepend (" ! ");
The resulting string is " ! Hello".
|
Parameter
Name |
Description |
|
insert |
The string to insert. |
|
Return
value description |
The string. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Replaces a portion of one string with another.
This replaces a portion of the current string
with another. Replacement is IN PLACE, causing the original string to be
modified.
Example:
string = "Hello";
string.StringReplace (" ! ", 1, 2);
The resulting string is "H ! llo".
|
Parameter
Name |
Description |
|
ReplaceWith |
(Optional) What to replace the range with. |
|
StartIndex |
(Optional) Starting character to replace. If not specified then this
will be 0. |
|
EndIndex |
(Optional) Ending character to replace. If not
specified this will be the end of the string. |
|
Return
value description |
The string. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Finds an occurence of one string inside another.
This searches for the substring within the
original string. If it's found then it returns the index into the original
string where the substring begins, or -1 if it can't be found.
Example:
string = "Hi there!";
return string.StringSearch ("there");
Returns 3.
|
Parameter
Name |
Description |
|
SubString |
The string to search for. |
|
StartIndex |
(Optional) The character index, within SubString, to search from. If
this isn't specified then 0 will be used. |
|
CaseSensative |
(Optional) If TRUE is passed then the
comparison will be case sensative; if FALSE it's case insensative. If not
specified then the comparison is case sensative. |
|
Return
value description |
The index into the string where SubString appears. If SubString is not
found then -1 is returned. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Copies a portion of the string into a new
string.
This copies a portion of the stirng into a new
string.
Example:
string = "Hi there!";
return stirng.StringSlice (3,-1);
Returns "there".
|
Parameter
Name |
Description |
|
StartIndex |
The index into the string that will become the
start of the new string. If this is negative, this is the index from the end
of the string. (StringLength() + StartIndex). |
|
EndIndex |
(Optional) The index into the string beyond the end of the string.
This must always be 0 or a negative value, with 0 keeping from StartIndex to
the end of the string, -1 discarding the last character, -2 discarding the
last two, etc. If this isn't specified then 0 (the end of the string) will be
used. |
|
Return
value description |
The sliced out string. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Splits the string into multiple strings based on
the delimiter.
This splits the string into multiple strings
based on the delimiter (such as ',' or ' '). The new strings are added to a
list, which is returned.
Example:
string = "1,2,3,,,5,6,7";
return string.StringSplit (',');
Returns ["1", "2",
"3", "", "", "5", "6",
"7"].
|
Parameter
Name |
Description |
|
Delimiter |
The string or character used to separate the
entries. |
|
Return
value description |
The list containing the split-up string. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Copies a portion of the string into a new
string.
This copies a portion of the stirng into a new
string.
Example:
string = "Hi there!";
return stirng.StringSubString (3,8);
Returns "there".
|
Parameter
Name |
Description |
|
StartIndex |
The index into the string that will become the
start of the new string. |
|
EndIndex |
(Optional) The index into the string that will become end of the new
string. If this isn't specified then the end of the string will be used. |
|
Return
value description |
The sliced out string. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Converts the string to lower case.
Converts the string to lower case.
Example:
string = "Hello Mike!";
return string.StringToLower();
Returns"hello mike!".
|
Parameter
Name |
Description |
|
Return
value description |
Lower case version of the string. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Converts the string to upper case.
Converts the string to upper case.
Example:
string = "Hello Mike!";
return string.StringToUpper();
Returns"HELLO MIKE!".
|
Parameter
Name |
Description |
|
Return
value description |
Upper case version of the string. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Trims whitespace from the beginning and end of
the string.
This trims the whitespace from the beginning and
end of the string.
Example:
string = "
Hello Mike! ";
string.Trim();
String's new value is "Hello Mike!"
|
Parameter
Name |
Description |
|
TrimOnlyLeft |
(Optional) This this isn't specified then
whitespace on the left and right sides of the string is cleared. If TRUE then
only the left whitespace is cleared. If FALSE then only the right whitespace
is cleared. |
|
Return
value description |
The string. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Adds a new timer to the object.
This adds a new timer to the object. If the
timers in the object are suspended then this timer will be added in a suspended
state too.
Example:
Object.TimerAdd ("MyTimer", TRUE, 1.5,
MyMethod, Param1, Param2);
|
Parameter
Name |
Description |
|
Name |
The name of the timer. Usually this is a
string, but it can be any value. If the name is already used for a timer then
this call will fail and return FALSE. (NOTE: Name comparisons are CASE
SENSATIVE.) |
|
Repeat |
If TRUE then the timer keeps repeating until it is removed using
TimerRemove(). If FALSE then the timer only runs once before it is
automatically removed. |
|
Interval |
The number of seconds (or fraction) before the
timer goes off. If this is a repeating timer then this is also the number of
seconds between repetitions. |
|
Call |
This is the function or method to call when the timer goes off. |
|
CallParams |
(Optional) Zero or more parameters passed into
the function (or method) when the timer goes off. |
|
Return
value description |
TRUE if the timer was added, FALSE if an error occured. |
Overrides: Call
only the highest priority method
Common to all objects
Enumerates all the timers in an object.
This enumerates all the timers in an object. It
returns a list with the timers names.
Example:
Object.TimerAdd ("MyTimer", TRUE, 1.5,
MyMethod, Param1, Param2);
return Object.TimerEnum();
Returns ["MyTimer"].
|
Parameter
Name |
Description |
|
Return
value description |
List of all the timers in the object. This may
be an empty list if there are no timers. |
Overrides: Call
only the highest priority method
Common to all objects
Deletes a timer from an object.
This deletes a timer from an object.
Example:
Object.TimerAdd ("MyTimer", TRUE, 1.5,
MyMethod, Param1, Param2);
Object.TimerRemove ("MyTimer");
|
Parameter
Name |
Description |
|
Name |
The name of the timer, as passed into
TimerAdd(). This is a CASE SENSATIVE compare. |
|
Return
value description |
TRUE if the timer was found and removed. FALSE if the timer wasn't
found. |
Overrides: Call
only the highest priority method
Common to all objects
Deletes a timer from an object.
This deletes a timer from an object.
Example:
Object.TimerAdd ("MyTimer", TRUE, 1.5,
MyMethod, Param1, Param2);
Object.TimerRemove ("MyTimer");
|
Parameter
Name |
Description |
|
Name |
The name of the timer, as passed into
TimerAdd(). This is a CASE SENSATIVE compare. |
|
Return
value description |
TRUE if the timer was found and removed. FALSE if the timer wasn't
found. |
Overrides: Call
only the highest priority method
Common to all objects
Returns 0.0 if the object's timers are
suspended, 1.0 if they're active, or different values if they're slowed down.
Returns 0.0 if the object's timers are
suspended.
Returns 1.0 if the object's timers are active.
The object's timers might also be slowed down (a
value between 0.0 and 1.0) or sped up (above 1.0).
See also TimerSuspendedSet().
|
Parameter
Name |
Description |
|
Return
value description |
Returns TRUE if the object's timers are
suspended, FALSE if they are active. |
Overrides: Call
only the highest priority method
Common to all objects
Suspends, resumes, or changes the speed of an
object's timers.
This suspends, resumes, or changes the speed of
an object's timers.
An application can use this to quickly and
easily suspend timers for an object when the object is inactive. Or, the
object's timers can be slowed down when it doesn't need accurate timing.
|
Parameter
Name |
Description |
|
TimerSpeed |
(Optional) If 0.0 then all the object's timers
will be suspended. If 1.0 then any suspended timers will be resumed. If not
specified then it will suspend the object (with 0.0 value). If between 0.0 and 1.0, the timers will be
activated, but at a slower pace. If above 1.0, the timers will be activated
at an accelerated pace. |
|
SuspendContains |
(Optional) If TRUE this will suspend all the objects contained by this
object, and whatever they contain. If FALSE then only this object will be
suspended (or resumed). If not specified then only this object will be
affected. |
|
Return
value description |
None. |
Overrides: Call
only the highest priority method
Common to all objects
No property definitions in the library.
No objects in the library.
Returns the absolute value of a number.
Returns the absolute value of a number.
|
Parameter
Name |
Description |
|
Value |
A number. |
|
Return
value description |
Absolute value. |
Returns the arc-cosine of a number.
Returns the arc-cosine of a number.
|
Parameter
Name |
Description |
|
Value |
A number. |
|
Return
value description |
Arc-cosine, in randians. |
Returns the arc-sine of a number.
Returns the arc-sine of a number.
|
Parameter
Name |
Description |
|
Value |
A number. |
|
Return
value description |
Arc-sine, in randians. |
Returns the arc-tangent of a number.
Returns the arc-tangent of a number.
|
Parameter
Name |
Description |
|
Value |
A number. |
|
Return
value description |
Arc-tangent, in randians. |
Returns the arc-tangent of a number.
Returns the arc-tangent of a number.
|
Parameter
Name |
Description |
|
Y |
Y-coordinate of the number. |
|
X |
X-coordinate of the number. |
|
Return
value description |
Arc-tangent, in randians. |
If this isn't an integer, return's the next
highest integer.
If this isn't an integer, return's the next
highest integer.
Example:
ceil (123) returns 123.
ceil (123.1) returns 124.
ceil (123.8) returns 124.
ceil (-1.1) returns -1.
|
Parameter
Name |
Description |
|
Value |
A number. |
|
Return
value description |
Next highest integer. |
Enumerates all the classes.
This enumerates all the classes as strings. You
can pass a class string into ObjectNew(), for example.
|
Parameter
Name |
Description |
|
Return
value description |
List of all the classes, as strings. |
Returns the cosine.
Returns the cosine.
|
Parameter
Name |
Description |
|
Value |
Angle, in radians. |
|
Return
value description |
Cosine of the angle. |
Returns the hypberboloic cosine.
Returns the hypberboloic cosine.
|
Parameter
Name |
Description |
|
Value |
Angle, in radians. |
|
Return
value description |
Hypberboloic cosine of the angle. |
Deletes a group of objects.
This accepts a list of objects and deletes them
all at once, including their contained objects.
|
Parameter
Name |
Description |
|
ObjectList |
List of objects to delete. |
|
DeleteContained |
If TRUE then even the object's contents are deleted. If FALSE then the
object's contents are left behind. |
|
Return
value description |
TRUE if success. |
Raise the constant, e, to the specified power.
Raise the constant, e, to the specified power.
|
Parameter
Name |
Description |
|
Value |
Number. |
|
Return
value description |
Raise the constant, e, to the specified power. |
If this isn't an integer, return's the next
lowest integer.
If this isn't an integer, return's the lowest
highest integer.
Example:
floor (123) returns 123.
floor (123.1) returns 123.
floor (123.8) returns 123.
floor (-1.1) returns -2.
|
Parameter
Name |
Description |
|
Value |
Number. |
|
Return
value description |
Next lowest integer. |
Enumerates all the global variables.
This enumerates all the global variables. It
returns a list with their names.
|
Parameter
Name |
Description |
|
Return
value description |
List with the names of all the global
variables. The names are strings. |
Gets the value of a global.
This gets the value of a global.
x = GlobalGet ("MyGlobal");
is
equivalent to
x = MyGlobal;
The only difference is that GlobalGet() lets you
access globals created after compile time.
|
Parameter
Name |
Description |
|
Global |
The name of the global. This must be a string. |
|
Return
value description |
The value of the global, or Undefined if the global does not exist. |
Changes the get/set code for accessing the
global.
This adds code for getting or setting a global.
Applications can use this to dynamically create code to intercept a global get
and set. (If no code exists then the global is accessed directly.)
Example:
GlobalSet ("MyNewGlobal", 0);
GlobalGetSet ("MyNewGlobal", GetFunc,
SetFunc);
After this is called, any attempts to change
"MyNewGlobal" will be passed to SetFunc(), and any attemps to get the
value will be passed to GetFunc().
|
Parameter
Name |
Description |
|
Global |
The name of the global. This must be a string. |
|
GetCode |
This is the code that will be run when the global is read from. It
must either be the name of an existing function or method (with object). If this is NULL the global will be accessed directly when it's read. |
|
SetCode |
This is the code that will be run when the
global is written to. It must either be the name of an existing function or
method (with object). If this is NULL the global will be accessed
directly when it's written. |
|
Return
value description |
TRUE if the global get/set code is changed, FALSE or Undefined if the
call fails. |
Tests to see if a global variable exists.
This tests to see if a global variable exists.
|
Parameter
Name |
Description |
|
Global |
The name of the global. This must be a string. |
|
Return
value description |
TRUE if the global variable exists, FALSE if it doesn't. |
Removes a global variable from the list.
This removes a global variable from the list.
|
Parameter
Name |
Description |
|
Global |
The name of the global. This must be a string. |
|
Return
value description |
TRUE if the global is removed, FALSE if it doesn't exist. |
Sets the value of a global.
This sets the value of a global.
GlobalSet ("MyGlobal", 54);
is
equivalent to
MyGlobal = 54;
The only difference is that GlobalSet() lets you
access globals created after compile time.
|
Parameter
Name |
Description |
|
Global |
The name of the global. This must be a string. If the global does NOT exist then it will be
created. |
|
Value |
The value to set the global to. |
|
Return
value description |
The value of the global. If this is used to create a global, it will
normally return the value of the global. However, if the global cannot be
created it will return Undefined. |
Converts a 32-character GUID string to an
object.
This converts a 32-character GUID string (from
ObjectToGUIDString()) to an object reference. The string will be converted even
if the object does not exist.
|
Parameter
Name |
Description |
|
String |
GUID-string of the object. This must be
exactly 32 characters long, consisting only of '0'...'9', and 'a'...'f' (or
'A'...'F'). |
|
Return
value description |
Object defined by the GUID. The object reference will be created even
if the object doesn't really exist. If the string is not a valid GUID-string
then Undefined will be returned. |
Tests to see if the value is boolean.
Returns TRUE if the value is a boolean, FALSE if
it is any other type.
|
Parameter
Name |
Description |
|
Value |
Value of any type. |
|
Return
value description |
Boolean value. |
Tests to see if the value is a character.
Returns TRUE if the value is a character, FALSE
if it is any other type.
|
Parameter
Name |
Description |
|
Value |
Value of any type. |
|
Return
value description |
Boolean value. |
Returns TRUE if the character is alphabetical.
Returns TRUE if the character is alphabetical,
such as 'A'..'Z' and 'a'..'z', including non-roman.
|
Parameter
Name |
Description |
|
Value |
Value of any type. |
|
Return
value description |
Boolean value. |
Returns TRUE if the character is alphabetical or
numeritcal
Returns TRUE if the character is alphabetical or
numberical, such as 'A'..'Z' and 'a'..'z', including non-roman, or '0'..'9'.
|
Parameter
Name |
Description |
|
Value |
Value of any type. |
|
Return
value description |
Boolean value. |
Returns TRUE if the character is a digit.
Returns TRUE if the character is a digit, '0' ..
'9'.
|
Parameter
Name |
Description |
|
Value |
Value of any type. |
|
Return
value description |
Boolean value. |
Returns TRUE if the character is lower case.
Returns TRUE if the character is lower case.
|
Parameter
Name |
Description |
|
Value |
Value of any type. |
|
Return
value description |
Boolean value. |
Returns TRUE if the character is whitespace.
Returns TRUE if the character is whitespace,
such as space, tab, and newline.
|
Parameter
Name |
Description |
|
Value |
Character. |
|
Return
value description |
Boolean value. |
Returns TRUE if the character is upper case.
Returns TRUE if the character is upper case.
|
Parameter
Name |
Description |
|
Value |
Character. |
|
Return
value description |
Boolean value. |
Tests to see if the value is a function.
Returns TRUE if the value is a function or an
method associated with an object, FALSE if it is any other type.
|
Parameter
Name |
Description |
|
Value |
Value of any type. |
|
Return
value description |
Boolean value. |
Tests to see if a number is infinite.
Returns TRUE if the number is infinite, FALSE if
it's finite.
|
Parameter
Name |
Description |
|
Value |
Value of any type. |
|
Return
value description |
Returns TRUE if the number is infinite, FALSE if it's finite. |
Tests to see if the value is an list.
Returns TRUE if the value is an list, FALSE if
it is any other type.
|
Parameter
Name |
Description |
|
Value |
Value of any type. |
|
Return
value description |
Boolean value. |
Tests to see if the value is a method.
Returns TRUE if the value is a method, FALSE if
it is any other type.
|
Parameter
Name |
Description |
|
Value |
Value of any type. |
|
Return
value description |
Boolean value. |
Tests to see if a value is a number.
Returns TRUE if the value is not-a-number (in
the floating point sense), FALSE if it is a number (in the floating point
sense).
|
Parameter
Name |
Description |
|
Value |
A number. |
|
Return
value description |
Returns TRUE if the value is not-a-number (in the floating point
sense), FALSE if it is a number (in the floating point sense). |
Tests to see if the value is a number.
Returns TRUE if the value is a number, FALSE if
it is any other type.
|
Parameter
Name |
Description |
|
Value |
Value of any type. |
|
Return
value description |
Boolean value. |
Tests to see if the value is an object.
Returns TRUE if the value is an object, FALSE if
it is any other type.
|
Parameter
Name |
Description |
|
Value |
Value of any type. |
|
Return
value description |
Boolean value. |
Tests to see if the value is a resource.
Returns TRUE if the value is a resource, FALSE
if it is any other type.
|
Parameter
Name |
Description |
|
Value |
Value of any type. |
|
Return
value description |
Boolean value. |
Tests to see if the value is a string.
Returns TRUE if the value is a string or string
table entry, FALSE if it is any other type.
|
Parameter
Name |
Description |
|
Value |
Value of any type. |
|
Return
value description |
Boolean value. |
Returns the current language.
Returns the language currently in use by the
virtual machine. The current language affects what resources and string table
entries are used; since they're language specific.
|
Parameter
Name |
Description |
|
Return
value description |
Current language. This is a standard Windows
language identifier number. |
Sets the current language.
This sets the current language.
Changing the language affects what resources and
string table entries are used; since they're language specific.
|
Parameter
Name |
Description |
|
Language |
New language to use. This is a standard
Windows language number. |
|
Return
value description |
None |
Returns the natural log of the number.
Returns the natural log of the number.
|
Parameter
Name |
Description |
|
Value |
A number. |
|
Return
value description |
Returns the natural log of the number. |
Returns the log (base 10) of the number.
Returns the log (base 10) of the number.
|
Parameter
Name |
Description |
|
Value |
A number. |
|
Return
value description |
Returns the log (base 10) of the number. |
Returns the higher of two values.
Returns the higher of two values.
|
Parameter
Name |
Description |
|
Value1 |
A number or string. |
|
Value2 |
A number or string. |
|
Return
value description |
Returns the higher
of two values. |
Returns the lower of two values.
Returns the lower of two values.
|
Parameter
Name |
Description |
|
Value1 |
A number or string. |
|
Value2 |
A number or string. |
|
Return
value description |
Returns the higher
of two values. |
Does the inverse of MMLToList().
This does the inverse of MMLToList(). It takes a
list, in the same format as is output by MMLToList(), and returns a string with
the MML/XML.
|
Parameter
Name |
Description |
|
List |
List, in the same output format is
MMLToList(). |
|
OneMainTag |
If TRUE, this assumes there is one main MML/XML tag for the entire
string. If FALSE, it allows for many. |
|
Return
value description |
String. |
Converts a MML string (or resource) into a list
of entries.
A MML string, like "<hello
val=43>This is a test</hello>" is basically an XML string.
Resource are also stored in a matter very similar to XML.
This function parses the string or resource, and
returns a list with the MML (like XML) all parsed up.
The list contains three elements, [Name,
Attributes, Contents].
Name is a string name of the tag, such as "hello".
Attributes is a list containing the attributes
of the tag, or NULL if there are no attributes. Each attribute is a sub-list
with [AttribName, AttribValue].
Contents is a list containing the contents of
the tag, or NULL if there are no contents. The contents are a sub-list with
strings, or more sub-lists, are are embeded MML/XML tags.
For example: "<hello val=43>This is a
test</hello>" would be converted to ["hello", [
["val", "43"] ] , ["This is a test"] ].
|
Parameter
Name |
Description |
|
MMLOrResource |
Either an MML string or a resource. |
|
OneMainTag |
If TRUE, this assumes there is one main MML/XML tag for the entire
string. If FALSE, it allows for many. If a resource is passed in, this MUST
be set to TRUE. For example: "<hello>This is a test</hello>"
would get converted to ["hello", NULL, ["This is a
test"]] if OneMainTag is TRUE. It would be converted to [NULL, NULL, [
["hello", NULL, ["This is a test"]] ]] if OneMainTag is FALSE. For example: "<p>LineOne</p><p>Line
two</p>" MUST have OneMainTag set to TRUE. |
|
Return
value description |
List. See the main
description. |
Clones an object or set of object.
This clones an object or set of objects. All the
object's contents (sub-objects) are also cloned.
The object's properties and timers are copies,
with references to cloned objects remapped to their clones. If an object is
cloned, but its parent is NOT cloned, then the clone's will NOT be contained by
any object.
Once all the objects have been cloned, the
Property will be set to PropertyValue, and then all the objects will be called
with Constructor2().
|
Parameter
Name |
Description |
|
Clone |
This is either a single object, or a list of
objects. Any objects contained within these objects are also cloned. |
|
Property |
If this is a string, then all the cloned objects will have this
propery, such as "pCloneOf" set to the value from PropertyValue.
The value will be set BEFORE Constructor2() is called for the cloned object.
This is useful to identify which objects are cloned. If NULL, then no property will be set. |
|
PropertyValue |
Value to set the
Property to. |
|
Return
value description |
Returns a list of all the
cloned objects. Each cloned object is a sub-list with [Original object, New
object]. The list is NOT sorted. You can use this to later delete the clones. |
Enumerates all the objects.
This returns a list of all the objects.
|
Parameter
Name |
Description |
|
Return
value description |
List of all the
objects. |
Creates a new object from a name or other
object.
This creates a new object from a name or other
object. It acts like "new XXX;" except that an arbitrary object class
can be created.
|
Parameter
Name |
Description |
|
Create |
This can be a
string containing the object's class, such as ObjectNew
("oMyObject"); which is equivalent to "new oMyObject;".
Or, this can be another object, in which case the base class/layer will be
used. (Ex: ObjectNew (oMyObject) will create an object with the same class as
oMyObject.) |
|
Return
value description |
The new object, or NULL
if error. |
Tests to see if an object exists.
This tests to see if an object exists. An object
may have been delete.
For example:
x = new Object;
ObjectQuery(x); // returns TRUE
delete x;
ObjectQuery(x); // returns FALSE
|
Parameter
Name |
Description |
|
Object |
Object to test. |
|
Return
value description |
TRUE if the object
exists, FALSE if it doesn't. |
Converts an object to a 32-character GUID
string.
This converts an object reference to a
32-character GUID string. The string is 32 hexadecimal digits that uniquely
identify the object.
|
Parameter
Name |
Description |
|
Object |
Object |
|
Return
value description |
String with the object's
GUID. If it's not an object then Undefined is returned. |
Raise a number to the specified power.
Raise a number to the specified power.
|
Parameter
Name |
Description |
|
Base |
Number to raise. |
|
Exponent |
Power to raise it to. |
|
Return
value description |
The number raised
to the specified power. |
Returns a random value.
This returns a random value.
If no parameters are passed, it's a random value
between 0 and 32767.
If one parameter is passed in, and the parameter
is a list, this returns one of the elements of the list.
If one parameter is passed in, and the parameter
isn't a list, this returns a random number between 0 (inclusive) and Value1.
(exclusive)
If two parameters are passed in, this returns a
random number between Value1 (inclusive) and Value2 (exclusive).
|
Parameter
Name |
Description |
|
Value1 |
(Optional) A
number. If only one parameter is provided then this can be a list. |
|
Value2 |
(Optional) A number. |
|
Return
value description |
Random number or
selection from the list. |
Seeds the random value.
This seeds the random value. You can set the
seed to ensure that calls to Random() are follow the same pattern every time.
|
Parameter
Name |
Description |
|
Seed |
(Optional) Seed
number, 0 to 4 billion. If no parameter is used then the seed will be based
on the current time. |
|
Return
value description |
None |
Enumerates a list of all the resources in the
project.
This enumerates a list of resources in the
project.
|
Parameter
Name |
Description |
|
Return
value description |
List of resources. Each resource is
represented by a sub-list with [ResourceName, ResourceType]. ResourceName is the
name of the resource, that can be passed to ResourceGet(). ResourceType is
the type of the resource, such as "TransPros". |
Gets a resource given a string name.
Gets a resource given a string name.
For example: ResourceGet
("rMyResource") will return the resource, rMyResource. If the
resource doesn't exist, it returns NULL.
|
Parameter
Name |
Description |
|
ResourceString |
String for the
resource. |
|
Return
value description |
Resource as a value. |
Rounds the value to the nearest integer.
Rounds the value to the nearest integer.
Example:
round (123) returns 123.
round (123.1) returns 124.
round (123.5) returns 124.
round (123.8) returns 124.
round (-1.1) returns -1.
|
Parameter
Name |
Description |
|
Value |
A number. |
|
Return
value description |
Rounds the value to the nearest integer. |
Returns the sine.
Returns the sine.
|
Parameter
Name |
Description |
|
Value |
Angle, in radians. |
|
Return
value description |
Sine of the angle. |
Returns the hypberboloic sine.
Returns the hypberboloic sine.
|
Parameter
Name |
Description |
|
Value |
Angle, in radians. |
|
Return
value description |
Hypberboloic sine of the angle. |
Returns the square root of a number.
Returns the square root of a number.
|
Parameter
Name |
Description |
|
Value |
A number. |
|
Return
value description |
Returns the square root of a number. |
Returns the tangent.
Returns the tangent.
|
Parameter
Name |
Description |
|
Value |
Angle, in radians. |
|
Return
value description |
Tangent of the angle. |
Returns the hypberboloic tangent.
Returns the hypberboloic tangent.
|
Parameter
Name |
Description |
|
Value |
Angle, in radians. |
|
Return
value description |
Hypberboloic tangent of the angle. |
Converts from a year, month, day, etc. to a time
(in days) since January 1, 1601.
This converts from a year, month, day, etc. to a
time (in days) since January 1, 2001.
|
Parameter
Name |
Description |
|
GMT |
If TRUE then the
time is GMT (system) time. If FALSE then time is in the local computer's
time. |
|
Year |
Year. Example: 2004 |
|
Month |
Month. 1..12 |
|
Day |
Day. 1..31 |
|
Hour |
Hour. 0..23 |
|
Minute |
Minute. 0..59 |
|
Second |
Second. 0..59 |
|
Milliseconds |
Milliseconds. 0..999 |
|
Return
value description |
Time. If an error
occurs this returns NULL. |
Gets the time.
This returns the time. This is a single number, which
is the number of days since January 1, 2001.
To convert it to year, month, day, etc. call
TimeToDateTime()
|
Parameter
Name |
Description |
|
Return
value description |
Number of days
since January 1, 1601. |
Returns the number of seconds that the computer
has been running.
Returns the number of seconds that the computer
has been running.
This is a good time function to check how many
seconds, minutes, or hours of real-time have ellapsed.
|
Parameter
Name |
Description |
|
Return
value description |
Number of seconds
since the computer was started. |
Converts from a time (in days) to a year, month,
day, etc.
This converts from a time (in days) to a year,
month, day, etc.
It accepts a time from TimeGet() or
TimeFromDateTime() and returns a list. The elements of the list contain the
year, month, day, etc.
|
Parameter
Name |
Description |
|
Time |
Time from TimeGet()
or TimeFromDateTime() |
|
GMT |
If TRUE then the time is calculated as GMT
(system) time. If FALSE then time is calculated in the local computer's time. |
|
Return
value description |
Returns a list.
List[0] = year (ex: 2004), List[1] = month (1..12), List[2] = day (1..31),
List[3] = hour (0..23), List[4] = minute (0..59), List[5] = second (0..59),
List[6] = milliseconds (0.999), List[7] = day of week (1..7). |
Returns the time zone information.
This returns the offset in time for the current
time zone, in days. UTC = local time + TimeZone().
|
Parameter
Name |
Description |
|
Return
value description |
Offset in days. |
Converts the value to a boolean.
Converts the value to a boolean.
|
Parameter
Name |
Description |
|
Value |
Value of any type. |
|
Return
value description |
Boolean value. |
Converts the value to a character.
Converts the value to a character.
|
Parameter
Name |
Description |
|
Value |
Value of any type. |
|
Return
value description |
Character. |
Converts the value to a function.
Converts the value to a function.
|
Parameter
Name |
Description |
|
Value |
Value of any type,
but usually a string of the function's name. |
|
Return
value description |
Function, or undefined if it can't be
converted. |
Converts the value to a public method.
Converts the value to a public method.
|
Parameter
Name |
Description |
|
Value |
Value of any type,
but usually a string of the method's name. |
|
Return
value description |
Method, or undefined if it can't be converted. |
Converts the value to a number.
Converts the value to a number.
|
Parameter
Name |
Description |
|
Value |
Value of any type. |
|
Return
value description |
Number. |
Converts an object.method to an object, or a
hexadecimal string to an object.
Converts an object.method to an object, or a
hexadecimal string to an object.
If the value is an Object.Method, this will
return the object part. If the string is a 32-digit hexadecimal string, this
will return an object ID, which may not be valid. If an object is passed in,
the same object will be returned. Otherwise, it returns undefined.
|
Parameter
Name |
Description |
|
Value |
Value of any type. |
|
Return
value description |
Object or undefined. |
Converts the value to a string.
Converts the value to a string.
|
Parameter
Name |
Description |
|
Value |
Value of any type. |
|
Return
value description |
String. |
Sanitizes a string to it can be placed into a
resource.
Resources uses a tagged text system called MML
(very similar to XML). MML uses special characters, like < and > to
indicate the start of a section, such as:
<speak>Hello there!</speak>
Because some characters are used by the tag
system (< and >), these can't be included directly in the strings. These
need to be converted to character codes. In this case, '>' is converted to
'<' and '<' is converted to '>'.
If you want to create create your own resources
that include strings you will need to do these conversions. The easiest way is
to use the ToStringMML() function.
For example: To speak "2 <3" you
would use:
"<speak>" + ToStringMML("2
< 3") + "</speak";
This produces the string:
"<speak>2 <
3</speak>"
|
Parameter
Name |
Description |
|
String |
The string that is
to be "sanitizied" so it can be placed in a resource string. |
|
Return
value description |
Sanitized version of the string, which
characters like '<' converted to '<', etc. |
Outputs the string to the debug trace and/or for
logging.
Calling trace with a string (or other value)
will output the string to the debug trace. It will also output the string for
logging purposes.
For example:
Trace ("Write this in the log.\n");
will write the data in the log.
|
Parameter
Name |
Description |
|
String |
String that should
be output to the debug trace, or the log. |
|
Return
value description |
None |
Returns the data's type as a string.
Returns the data's type as a string.
The following strings are returned:
"bool" for a boolean
"char" for a character
"function" for a function
"list" for a list
"list.method" for a method call into a
list
"method" for a method
"number" for a number
"null" for a NULL value
"object" for an object
"object.method" for a method call into
an object
"resource" for a resource reference
"string" for a string
"string.method" for a method call into
a string
"stringtable" for a stringtable
reference
"undefined" for undefined
|
Parameter
Name |
Description |
|
Value |
Value to check the
type of. |
|
Return
value description |
String for the value's type. |
Shortcut to add a line within a string.
Shortcut to add a line within a string.
"\r\n"
3.141592653589793238
3.141592653589793238
3.141592653589793238
6.283185307179586476
6.283185307179586476
6.283185307179586476
This library is needed to use ther IF server's features.
The server library is necessary for the MIFL scripting language to communicate with the "MIF Server". It provides functionality for managing user connections over the internet. It accepts new user connections, sends and receives messages to users, etc.
Copyright information.
<p><bold><big>
Copyright information
</big></bold></p>
<p>
Circumreality is copyright 2001-2009 by Mike Rozak, all rights reserved.
See <bold>http://www.mXac.com.au</bold> for
more information.
</p>
<p>
The lexicon for text-to-speech comes from CMU University.
</p>
You must have one (and only one) rTitleInfo for
every interactive fiction title. This is a placeholder resource that can be
overridden.
Default voice-chat features that a player can
use. You may wish to customize this for your own need, perhaps even on a
character-race basis.
Called just before the connection object is
destroyed..
ConnectionEnd() is called after just before a
cConnection object is destroyed. You should replace this method with whatever
functionality a connection should undertake when its shutting down.
|
Parameter
Name |
Description |
|
Return
value description |
None |
Overrides: Call
only the highest priority method
NOT Common to all objects
Called with an error occurs in the connection.
This method is called when an error occurs with
the connection, basically meaning that a disconnect is necessary because the
connection has terminated.
The default behavior of the ConnectionError()
call for the Connection object is to delete itself.
|
Parameter
Name |
Description |
|
ErrorNumber |
Error number. |
|
ErrorString |
String describing the error. |
|
Return
value description |
None |
Overrides: Call
only the highest priority method
NOT Common to all objects
Called when miscellaneous pieces of information
are sent to the server.
Called when miscellaneous pieces of information
are sent to the server. This includes messages for the user's time zone,
graphics speed, and language.
|
Parameter
Name |
Description |
|
Name |
This is the value name. Currently, it can be: "artstyle" - A number from 0 to 3
for the art style to use. See gVisualPainterlyNames. "timezone" - The current time zone.
Add this value (in hours) to GMT to create the local time. "langid" - The current Windows
language ID. "1033" is American English, for example. "graphspeed" - The graphics speed
setting that the user has chosen, from 0 to 4. |
|
Value |
A string value. |
|
Return
value description |
None |
Overrides: Call
only the highest priority method
NOT Common to all objects
Called with a logoff message is received from
the client.
Called with a logoff message is received from
the client. It means that the client has shut down.
The default behavior of the ConnectionLogOff()
is to set a timer for 30 (or whatever) seconds that will then cause a shutdown.
|
Parameter
Name |
Description |
|
TimeToShutdown |
If this is a number, then it's the number of
seconds before the connection will shut down. If this is 0 then the shutdown is aborted. If Undefined or NULL, then the connection will
shut down in 30 seconds, or whatever gConnectionLogOffTime is. |
|
Return
value description |
None |
Overrides: Call
only the highest priority method
NOT Common to all objects
Called with a new command message comes in from
the client.
This method is called by the server when a new
message comes in from the client. It is up to the client to process the
message.
You should override this to handle the incoming
message. Usually this involves forwarding the message to the player or
character object.
|
Parameter
Name |
Description |
|
LanguageID |
The language of the message. This number is a
standard LANGID from WindowNT. It can be passed into LanguageSet(). |
|
Message |
A message string for the incoming message. |
|
Return
value description |
None |
Overrides: Call
only the highest priority method
NOT Common to all objects
Sends a message to the user.
Sends a message to the user.
This also sends the message to any users that
are spying on the current user.
|
Parameter
Name |
Description |
|
MessageQueue |
Pass true to queue this message up in the
audio queue, so that if it's a message for more audio or graphics, it won't
be heard/shown until it's turn in the queue. If false then play this audio (or show the
image) right away. |
|
Message |
Message to send. This is either a resource, or string that's wrapped
up in MML tags to look like a resource. |
|
Return
value description |
True if the message was sent, false if it
failed. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Sends a voice chat message to the user.
Sends a voice chat message to the user.
This also sends the message to any users that
are spying on the current user.
|
Parameter
Name |
Description |
|
MMLString |
MML string to send, from
MMLSpeakObjectVoiceChat(). |
|
VoiceChatBinary |
Binary data of voice chat, from the connection's ConnectionVoiceChat()
method. |
|
Garble |
If TRUE then garble the speech to simulate the
characters not understanding one another. |
|
Effect |
0 for no effect. 1 for whisper. |
|
Return
value description |
True if the message was sent, false if it
failed. |
Overrides: Call
only the highest priority method
NOT Common to all objects
Called once the connection has been initialized.
ConnectionStart() is called after a new
cConnection object has been created. You should replace this method with
whatever functionality a new connection should undertake.
|
Parameter
Name |
Description |
|
Return
value description |
None |
Overrides: Call
only the highest priority method
NOT Common to all objects
Called with an image uploaded from the client.
This method is called by the server when a new
image comes in from the client. It is up to the client to process the message.
You should override this to handle the incoming
message. Usually this involves saving the image away and alerting the player.
|
Parameter
Name |
Description |
|
Number |
Image number, 1 based. |
|
Width |
Width of the image, in pixels. |
|
Height |
Height of the image in pixels. |
|
Binary |
JPEG binary for the image that can be saved to the database. |
|
Return
value description |
None |
Overrides: Call
only the highest priority method
NOT Common to all objects
Called with voice chat data that comes from the
client.
This method is called by the server when a voice
chat packet comes in from one of the clients.
You should override this to handle the incoming
message. Usually this involves sending the voice chat information to all
players in the room.
|
Parameter
Name |
Description |
|
SpeakTo |
Object that the player is speaking to, or NULL
if none. |
|
SpeakingStyle |
Speaking style. "speak" if normal speech.
"whisper" is whispering. "shout" if yelling. |
|
Language |
Language string (localized to whatever the
current language of the player is). You should make sure other players can
understand the languages. |
|
Binary |
Compressed voice chat binary. This can be sent to the other players to
hear and/or saved away. |
|
Return
value description |
None |
Overrides: Call
only the highest priority method
NOT Common to all objects
Internet connection for the object.
If an object (such as an actor) is controlled by
a real person, then the object's pConnection property should point to the
connection object. Likewise, the connection object will have a pConnectionActor
that points to the actor.
A connection can have only one actor, and only
one actor should point to a connection.
Actor object that the connection controls.
This is the actor object (player character) that
the connection controls.
Number that identifies the connection.
This number identifies what connection the
"Connection" object is assoated with.
Language ID used for the connection.
This is the language ID used for the connection.
Pass this to LanguageSet().
Stores the number of seconds before the
connection will log off.
Stores the number of seconds before the
connection will log off.
This is linked to the "logoff" timer
in the connection object. If the connection is working on logoff then the
"logoff" timer will be set at pConnectionLogOffTime will be > 0.
Thus, if you set pConnectionLogOffTime to 0, then make sure to kill the
"logoff" timer.
List of spy connections.
If an administrator is spying on another
character, then this is a list of connections that are spying on this
connections. When ConnectionSend() or ConnectionSendVoiceChat() are called, all
the spies receive the messages too.
Time zone offset.
This is an offset to add to local time to
generate UTC, in days.
It's has an equivalent return to the TimeZone()
call, which returns the offset in time for the current time zone, in days. UTC
= local time + TimeZone().
User that controls this connection. (Points to
cUser class)
User that controls this connection. (Points to
cUser class)
The alert level for the user, affecting what
priority messages are logged.
This value is saved for each user, and stored in
the connection object when the user logs on.
Most users have a value of 0 for pUserLogAlert.
However, if you're concerned that the user may be cheating, set this to 1 or 2.
Higher values will cause more events for the user to be logged.
Normally, only priority 1 and 2 events are
logged, while 3 and 4 events are ignored. If pUserLogAlert is 1 then priorities
1 to 3 will be logged. If pUserLogAlert is 2 then priorities 1 to 4 will be
logged.
Object assocated with a server connection.
This object is automatically created when a new
connection is made to the server. It then receives the ConnectionMessage() and
ConnectionError() callbacks when information has been received from the user's
machine (over the internet).
You may wish to override some of the methods of
the connection object (particularly the ConnectionMessage() callback) to work
with your software.
|
Object
info |
Description |
|
Automatically
creates as an object |
|
|
Contained
In |
|
|
Super-classes |
|
|
Properties |
pConnectionID – undefined pConnectionActor – NULL pConnectionLanguage – 1033 pConectionUser – NULL pConnectionSpies pUserLogAlert pConnectionLogOffTime pConnectionTimeZone |
this wont compile; // so don't compile without a connectionEnd() call
// if this got called then you haven't written your own
// ConnectionEnd code. You can do so by creating an overlaid
// cConnection object in your own library, and supply only
// the ConnectionEnd() method
TextLogPriorityAdjust (this);
if (gTextLogUser[1])
TextLog ("ConnectionError (" + ErrorNumber + ", " + ErrorString + ")");
// BUGFIX - More detailed connection error info
TextLogPriorityAdjust (NULL);
// start the log-off process
// BUGFIX - because this is the result of an error, log off immediately
ConnectionLogOff (0.00001);
this wont compile; // so don't compile without a connectioninfoforserver call
// if this got called then you haven't written your own
// ConnectionInfoForServer code. You can do so by creating an overlaid
// cConnection object in your own library, and supply only
// the ConnectionInfoForServer() method
// if it's undefined then calling from external callback
if (!IsNumber(TimeToShutdown))
TextLogPriorityAdjust (this);
// if time to shut down is 0 then aborting
if (TimeToShutDown === 0) {
if (pConnectionLogOffTime) {
TimerRemove ("logoff"); // kill the timer
pConnectionLogOffTime = Undefined;
}
if (!IsNumber(TimeToShutdown))
TextLogPriorityAdjust (NULL);
return;
}
// if there's already a timer then dont do anything
if (pConnectionLogOffTime) {
if (!IsNumber(TimeToShutdown))
TextLogPriorityAdjust (NULL);
return;
}
// else, create timer
// BUGFIX - If only a temporary character then can log-off right away
// Do this so that queries to the server don't stick around for 30 seconds
if (IsNumber(TimeToShutDown))
this.pConnectionLogOffTime = TimeToShutDown;
else if (!pConnectionActor || pConnectionActor.pIsTemporary)
this.pConnectionLogOffTime = 0.1;
else
this.pConnectionLogOffTime = gConnectionLogOffTime;
TimerAdd ("logoff", FALSE, pConnectionLogOffTime, this.ConnectionLogOffSuccess);
if (!IsNumber(TimeToShutdown))
TextLogPriorityAdjust (NULL);
this wont compile; // so don't compile without a connectionmessage call
// if this got called then you haven't written your own
// ConnectionMessage code. You can do so by creating an overlaid
// cConnection object in your own library, and supply only
// the ConnectionMessage() method
// send to spys
var i;
if (pConnectionSpies) for (i = 0; i < pConnectionSpies.ListNumber(); i++) {
// if the spy has disconnected then remove from the list
if (!ObjectQuery(pConnectionSpies[i])) {
pConnectionSpies.ListRemove (i);
i--;
continue;
}
// send
ConnectionSendFunc (pConnectionSpies[i].pConnectionID, MessageQueue, Message);
} // if spies
var vRet = ConnectionSendFunc (pConnectionID, MessageQueue, Message);
return vRet;
// send to spys
var i;
if (pConnectionSpies) for (i = 0; i < pConnectionSpies.ListNumber(); i++) {
// if the spy has disconnected then remove from the list
if (!ObjectQuery(pConnectionSpies[i])) {
pConnectionSpies.ListRemove (i);
i--;
continue;
}
// send
ConnectionSendVoiceChatFunc (pConnectionSpies[i].pConnectionID, MMLString, VoiceChatBinary, Garble, Effect);
} // if spies
var vRet = ConnectionSendVoiceChatFunc (pConnectionID, MMLString, VoiceChatBinary, Garble, Effect);
return vRet;
this wont compile; // so don't compile without a connection start call
// if this got called then you haven't written your own
// ConnectionStart code. You can do so by creating an overlaid
// cConnection object in your own library, and supply only
// the ConnectionStart() method
this wont compile; // so don't compile without a connectionuploadimage call
// if this got called then you haven't written your own
// ConnectionUploadImage code. You can do so by creating an overlaid
// cConnection object in your own library, and supply only
// the ConnectionUploadImage() method
this wont compile; // so don't compile without a connectionvoicechat call
// if this got called then you haven't written your own
// ConnectionVoiceChat code. You can do so by creating an overlaid
// cConnection object in your own library, and supply only
// the ConnectionVoiceChat() method
// place in the list, keeping it sorted
GConnectionList.ListInsert (this, GConnectionList.ListSearchToInsert(this));
// just in case, send logoff
ConnectionSendFunc (pConnectionID, TRUE, "<logoff/>");
// call into the callback
ConnectionEnd();
// disconnect this connection in the server
ConnectionDisconnect (pConnectionID);
// remove this from the connection list
var index = GConnectionList.ListSearch (this);
if (index != -1)
GConnectionList.ListRemove (index);
// if this is a single-player mode then shutdown the server
// after all the connections are gone
if (!GConnectionList.ListNumber() && ShardParamIsOffline())
ShutDownImmediately(FALSE);
Called when the connection is actually allowed
to log off.
This is called when the connection is actually
allowed to log off, about 30 seconds after ConnectionLogOff() was called.
|
Parameter
Name |
Description |
|
Return
value description |
|
TextLogPriorityAdjust (this);
if (gTextLogUser[1])
TextLog ("ConnectionLogOff");
// Delete this connection
delete this;
TextLogPriorityAdjust (NULL);
Maintains some timers useful for the server
library.
Maintains some timers useful for the server
library.
The timers maintain gPerformanceCPU and
gPerformanceNetwork. They also automatically shut the server down and restart
if the memory used by the server is > gMemoryMaximum.
|
Object
info |
Description |
|
Automatically
creates as an object |
Yes |
|
Contained
In |
|
|
Super-classes |
|
|
Properties |
|
// disable logging if we're offline
if (ShardParamIsOffline()) {
TextLogEnableSet (FALSE);
// don't allow anything to be cached
RenderCacheLimits (0, 0, 0, 0, 0);
}
else {
RenderCacheLimits (gRenderCacheLimitsDataMaximum, gRenderCacheLimitsDataMinimumHardDrive,
gRenderCacheLimitsDataGreedy, gRenderCacheLimitsDataMaxEntriesOn32,
gRenderCacheLimitsDataMaxEntriesOn64);
}
// log startup
if (gTextLogSystem[1])
TextLog ("STARTUP");
TimerAdd ("10sec", TRUE, 10.0, this.TenSecondTimer);
// text log delete timer
TimerAdd ("TextLogDelete", TRUE, 60 * 10 /* 10 min */ + 0.4351, this.TextLogDeleteOld);
// disable logging if we're offline
if (ShardParamIsOffline()) {
TextLogEnableSet (FALSE);
// don't allow anything to be cached
RenderCacheLimits (0, 0, 0, 0, 0);
}
else {
RenderCacheLimits (gRenderCacheLimitsDataMaximum, gRenderCacheLimitsDataMinimumHardDrive,
gRenderCacheLimitsDataGreedy, gRenderCacheLimitsDataMaxEntriesOn32,
gRenderCacheLimitsDataMaxEntriesOn64);
}
// log startup
if (gTextLogSystem[1])
TextLog ("STARTUP");
// create the timer, just to make sure
TimerAdd ("10sec", TRUE, 10.0, this.TenSecondTimer);
// text log delete timer
TimerAdd ("TextLogDelete", TRUE, 60 * 10 /* 10 min */ + 0.4351, this.TextLogDeleteOld);
// log shutdown
if (gTextLogSystem[1])
TextLog ("SHUTDOWN");
Tries to delete old text logs.
This method randomly selects a text log from the
enumerated list. If it's older than the gTextLogDeleteOld then it's deleted.
|
Parameter
Name |
Description |
|
Return
value description |
|
var vEnum = TextLogEnum();
if (!IsList(vEnum))
return;
// select one
vEnum = Random(vEnum);
var vOld = TimeGet() - gTextLogDeleteOld;
if (vEnum[1] < vOld)
TextLogDelete (vEnum[0]);
Timer that's run exactly every 10 seconds.
Timer that's run exactly every 10 seconds.
This updates gPerformanceNetwork,
gPerfomanceCPU, and will reboot the server is gMemoryMaximum is reached.
|
Parameter
Name |
Description |
|
Return
value description |
|
// CPU usage
var vCPU = PerformanceCPU();
while (gPerformanceCPU.ListNumber() > 100)
gPerformanceCPU.ListRemove (0);
gPerformanceCPU.ListConcat (vCPU);
// network usage
var vNetwork = PerformanceNetwork();
vNetwork.ListInsert (vCPU[0]); // get the time from there
while (gPerformanceNetwork.ListNumber() > 100)
gPerformanceNetwork.ListRemove (0);
gPerformanceNetwork.ListConcat (vNetwork);
// how much memory
var vMem1 = PerformanceMemory (1);
var vMem3 = PerformanceMemory (3);
if ((vMem1 > gSafetyMaximumMemory) || (vMem3 > gSafetyMaximumMemory)) {
Trace ("Called ShutDownImmediatelyCheckIn() because exceeded gMemoryMaximum.");
ShutDownImmediatelyCheckIn (TRUE);
return;
}
if (!ShardParamIsOffline()) {
// if there isn't enough hard drive then shut down immediately
vMem1 = PerformanceDisk();
if (vMem1 < gSafetyMinimumDisk) {
Trace ("Called ShutDownImmediatelyCheckIn() because exceeded gSafetyMinimumDisk.");
ShutDownImmediatelyCheckIn (TRUE);
return;
}
// if there are too many GUI objects shut down immediately
vMem1 = PerformanceGUI();
if (vMem1 > gSafetyMaximumGUI) {
Trace ("Called ShutDownImmediatelyCheckIn() because exceeded gSafetyMaximumGUI.");
ShutDownImmediatelyCheckIn (TRUE);
return;
}
// if there are too many threads, shut down immediately
vMem1 = PerformanceThreads();
if (vMem1 > gSafetyMaximumThreads) {
Trace ("Called ShutDownImmediatelyCheckIn() because exceeded gSafetyMaximumThreads.");
ShutDownImmediatelyCheckIn (TRUE);
return;
}
// if there are too many handles shut down immediately
vMem1 = PerformanceHandles();
if (vMem1 > gSafetyMaximumHandles) {
Trace ("Called ShutDownImmediatelyCheckIn() because exceeded gSafetyMaximumHandles.");
ShutDownImmediatelyCheckIn (TRUE);
return;
}
}
Enumerates all the files in the database.
Enumerates all the files in the database.
|
Parameter
Name |
Description |
|
FileNamePrefix |
If this is a string then only those files
beginning with FileNamePrefix will be enumerated. If NULL then all files will
be enumerated. |
|
Return
value description |
If successful, a list of file names (in no particular order). If it
fails then it returns NULL. |
Returns the name of a file in the binary
database, based upon the index number.
Returns the name of a file in the binary
database, based upon the index number.
|
Parameter
Name |
Description |
|
Index |
Index number, from 0 .. BinaryDataNum()-1. |
|
Return
value description |
String for the file name, or NULL if error. |
Loads a binary file from the database.
Loads a binary file from the database.
|
Parameter
Name |
Description |
|
FileName |
File name to load. |
|
Return
value description |
If successful, this returns a string with the binary data. The values
of the string are all 1 more than the original data. Therefore, String[N] =
OriginalData[N]+1. Returns NULL if error. |
Returns the number of files in the database.
Returns the number of files in the database.
|
Parameter
Name |
Description |
|
Return
value description |
Returns the number of files in the database. |
Returns information about the file in the binary
database.
Returns information about the file in the binary
database.
|
Parameter
Name |
Description |
|
FileName |
File name to load. |
|
Return
value description |
If this fails, returns NULL. If successful, returns a list with [FileSize, CreationDate,
LastModify, LastAccess]. FileSize is the size of the file in bytes.
CreationDate is the date/time when the file was created. LastModify is the
last modification date/time. LastAccess is the last access date/time,
although this value is unreliable since it isn't always written out. |
Sends a message to all connected clients saying
that database file has changed.
Calling this function sends a message to all
connected clients letting them know that the database file has changed and they
should reaload it.
|
Parameter
Name |
Description |
|
FileName |
The file that has
been changed. This will have recently been written to BinaryDataSave() |
|
Return
value description |
None |
Deletes binary data from the binary database.
Deletes binary data from the binary database.
|
Parameter
Name |
Description |
|
FileName |
File name to delete |
|
Return
value description |
TRUE if the file was found and deleted, FALSE if it could not be
found. |
Renames a file in the binary database.
Renames a file in the binary database.
|
Parameter
Name |
Description |
|
FileName |
File name to
rename. |
|
RenameTo |
Name
to rename it to. If the file already exists then the rename call will fail. |
|
Return
value description |
TRUE if the file was found and renamed, FALSE
if it could not be found or if RenameTo already exists. |
Saves binary data into the binary database.
Saves binary data into the binary database.
Any user images, user sounds, etc. should be
saved in this database so they can be accessed by all users.
NOTE: Even though data is written to the binary
database, it will not automatically be refreshed on the players' machines. To
enfore a refresh, you'll need to send out the BinaryDataRefresh() message to
all the clients, as well as having them draw their current room's contents.
|
Parameter
Name |
Description |
|
FileName |
File name to save
this as. |
|
Data |
This is a string with the data in it. The
actual binary data is Data[N] - 1. |
|
Return
value description |
TRUE if the data was saved, FALSE if there was
an error. |
Not really a function. Used to find bug
comments.
If you put BUGBUG comments in your properties
and function descriptions to indicate that work needs to be done, then whenever
help is built, you can look at this BUGBUG "function" to see where
those references are.
|
Parameter
Name |
Description |
|
Return
value description |
TRUE if the data was saved, FALSE if there was
an error. |
This bans an IP address.
If a user from a specific IP address it constantly
logging in and staying logged, you can use ConnectionBan() to ban the IP
address outright so that MIFL isn't even notified of the connection. Basically,
as soon as the connection occurs it's disconnected.
The banned IP address will stay banned until the
server is restarted (once a day, or however often then server reboots).
Note: This banning is different than some of the
other IP address banning/blacklisting functions because the connection never
gets to MIFL.
Calling ConnectionBan() DOESN'T disconnect the
IP address if it's already connected.
|
Parameter
Name |
Description |
|
IPAddress |
A string of the IP
address to ban, such as "12345.67.89". |
|
BanAmount |
Each IP address keeps a "ban value" with it, starting at 0.
Whenever ConnectionBan() is called, the ban value is increased by BanAmount.
When BanAmount >= 1.0 the IP address is banned. |
|
BanDays |
Number of days to ban.
This information will be forgotten as soon as the server shuts down. |
|
Return
value description |
TRUE if the address was succefully banned. |
Has the server disconnect with one of the
connections.
Calling ConnectionDisconnect() will cause the
server to disconnect from the given connection number.
This is no usually called directly, but is
automatically handled when the Connection object is deleted.
|
Parameter
Name |
Description |
|
ConnectionIdent |
Connection number to disconnect. |
|
Return
value description |
Returns true if the connection was disconnected, false if the
connection number wasn't valid. |
Enumerates all the connections to the server.
This enumerates all the connections made to the
server.
|
Parameter
Name |
Description |
|
Return
value description |
List of connection identifiers. |
Gets a piece of information about the connection.
This will get a piece of information about the
connection.
The types of information are:
"ip" - The user's IP address.
"status" - The current status of the
connection, such as "logging on".
"user" - Name of the user.
"uniqueid" - Uniquely identifies
user's machine. Empty string if no ID.
"character" - Name of the user's
character.
"object" - The object that will be
used to send the ConnectionMessage() and ConnectionError() messages to.
"sendbytes" - Number of bytes that
have been sent by the server to the user's machine. (This total is BEFORE the
data compression.)
"sendbytescomp" - Like
"sendbytes", except compressed data.
"sendbytesexpect" - Number of bytes
expected to be send.
"receivebytes" - Number of bytes
received by the server from the user's machine.
"receivebytescomp" - Like
"receivebytes", except compressed data.
"receivebytesexpect" - Number of bytes
that expect to receive.
"connecttime" - Number seconds that
the user has been connected to the server.
"sendlast" - Number of seconds since
data was last sent to the user.
"receivelast" - Number of seconds
since data was last received from the user.
"bytespersec" - Average bytes per
second received from the user.
|
Parameter
Name |
Description |
|
ConnectionIdent |
Connection number to get info about. |
|
InfoString |
String indicating the type of information to send, such as
"ip". |
|
Return
value description |
Returns the value requested, or Undefined if
the InfoString was wrong. |
Sets a piece of information about the
connection.
This will set a piece of information about the
connection.
The types of information are:
"ip" - The user's IP address. This
doesn't affect the real IP address used, but is useful for record keeping.
"status" - The current status of the connection,
such as "logging on".
"user" - Name of the user.
"uniqueid" - Uniquely identifies
user's machine. Empty string if no ID.
"character" - Name of the user's
character.
"object" - The object that will be
used to send the ConnectionMessage() and ConnectionError() messages to.
Changing this will redirect the messages to a different object.
|
Parameter
Name |
Description |
|
ConnectionIdent |
Connection number to set info about. |
|
InfoString |
String indicating the type of information to send, such as
"ip". |
|
Value |
New value to set. |
|
Return
value description |
Returns true if the value was changed, false if it failed to be
changed. |
Called by the server when a new user connects.
This function is
called by the server when a new user connects to the server. The function
creates a "Connection" object and returns that object. Any further
callbacks, such as ConnectionMessage() and ConnectionError() will be sent to
the connection object.
You may override this
with your own function.
|
Parameter
Name |
Description |
|
ConnectionIdent |
Number that uniquely identifies the
connection. The created object will remember this connection number. |
|
Return
value description |
Object that's associated with the connection. From now on, the
connection object will be passed ConnectionMessage() and ConnectionError()
calls when it receives an incoming message or an error in the connection. If a non-object is returned then the new connection will be
disconnected. |
Returns the number of bytes queued to be send
out to a connection.
This returns the number of bytes queued up to be
sent to a connection. It does not includes shared data, such as the
dowload-on-demand files.
You can use this to ensure that a user isn't
intetionally creating a backlog on their side and trying to crash the server.
|
Parameter
Name |
Description |
|
ConnectionIdent |
Connection number to get info about. |
|
Return
value description |
Returns the number of bytes queued up to be sent to out to the
connection. This does NOT include data from files to be uploaded to the
client as requested. |
Sends a cached render or TTS .wav to the
player's computer.
This sends a cached render of TTS .wav file to
the player's computer.
It's used by MMLSpeak() and MMLSpeakNarrator() to
pre-send cached TTS to the player's computer so it speaks right away.
|
Parameter
Name |
Description |
|
ConnectionIdent |
Connection number to get info about. |
|
MMLString |
MML string that names the render/TTS. |
|
Return
value description |
Returns the quality of the data sent, from 0+. If this failed, returns -1. |
Sends a message to the user.
This sends a message to the user.
NOTE: Use the ConnectionSend() method instead of
ConnectionSendFunc().
|
Parameter
Name |
Description |
|
ConnectionIdent |
Connection number to send to. |
|
MessageQueue |
Pass true to queue this message up in the audio queue, so that if it's
a message for more audio or graphics, it won't be heard/shown until it's turn
in the queue. If false then play this audio (or show the image) right away. |
|
Message |
Message to send. This is either a resource, or string that's wrapped
up in MML tags to look like a resource. |
|
Return
value description |
True if the message was sent, false if it failed. |
Sends a voice chat message to the user.
This sends a voice chat message to the user.
NOTE: Use the ConnectionSendVoiceChat() method
instead of ConnectionSendVoiceChatFunc().
|
Parameter
Name |
Description |
|
ConnectionIdent |
Connection number to send to. |
|
MMLString |
MML string to send, from MMLSpeakObjectVoiceChat(). |
|
VoiceChatBianry |
Binary data of voice chat, from the
connection's ConnectionVoiceChat() method. |
|
Garble |
If TRUE then garble the speech to simulate the characters not
understanding one another. |
|
Effect |
0 for no effect. 1 for whisper. |
|
Return
value description |
True if the message was sent, false if it failed. |
Limit the number of connections available on the
shard.
Call this function to limit the number of
connections available on the shard.
This limit will also be shown to players
wondering how full the world is.
This function is automatically called based on
gSafetyMaximumUsersConnected.
|
Parameter
Name |
Description |
|
Number |
Limit, from 1 to (around) 2000. |
|
Return
value description |
TRUE if changed. FALSE if the number is too high/low. |
Backs up the database.
You can use this function to make nightly
backups of the database.
This first saves the database (essentially
callying DatabaseSave()), and then copies all the database files to a backup
directory.
|
Parameter
Name |
Description |
|
Directory |
Name of the directory where to backup to, such
as "c:\\my backups\\monday". |
|
Return
value description |
Returns TRUE if the database was backed up, FALSE if there was an
error. |
Adds a new object to the database.
This adds the given object to the database (so
it will be backed up to disk). It immediately checks out the added object.
Once an object has been added you will need to
call DatabaseObjectCheckIn() when it's finished being used, before it's
deleted.
|
Parameter
Name |
Description |
|
DatabaseName |
Name of the database. This cannot contain any
spaces, and must be made up of letters, numbers (except the fist character),
and underscores. |
|
Object |
Object to be added to the database. |
|
Return
value description |
Returns TRUE if the object was added, FALSE if
it couldn't be added (perhaps because it already exists in the database). |
Checks in an object to the database.
This checks in an object that was checked out.
Every time DatabaseObjectCheckOut() is called to create an object, a call to
DatabaseObjectCheckIn() must be called when the object is finished being used
(just before it's deleted).
Calls to DatabaseObjectCheckIn() deletes the
object from the virtual machine unless a parameter (DontDelete) is set.
The checked in object will (eventually) be saved
to disk. To force a checked-in object to be saved right away, call
DatabaseSave(); this can be a slow call since it will save everything in the
database.
|
Parameter
Name |
Description |
|
DatabaseName |
Name of the database. This cannot contain any
spaces, and must be made up of letters, numbers (except the fist character),
and underscores. |
|
Object |
Object which is to be checked in. |
|
DontSave |
(Optional) If set to TRUE then the checked-in
object won't be saved, which means any changes to it will be lost. If FALSE
(or not specified) the object will be saved. |
|
DontDelete |
(Optional) If set to TRUE then the checked-in
object won't be deleted after it's checked in. If FALSE (or not specified)
then the object is automatically deleted from the virtual machine after it's
saved to the database. |
|
Return
value description |
Returns TRUE if the object was checked in,
FALSE if it couldn't be checked in (perhaps because the object is not checked
out). |
Checks out an object from the database.
Call this function to load an object from the
database into the current virtual machine. Once an object is checked out you
will need to check it in before the object is destroyed.
For example: If a monster (aka: object) is in a
seldom-visited room then keeping it saved to disk until a player enters the
room will save memory on the server. When the player enters the room, the
monster would be checked-out. (The object ID can be gotten by calling
DatabaseObjectQuery() to find all checked-in objects within the room.)
|
Parameter
Name |
Description |
|
DatabaseName |
Name of the database. This cannot contain any
spaces, and must be made up of letters, numbers (except the fist character),
and underscores. |
|
Object |
Object which is to be checked out. |
|
Return
value description |
Returns TRUE if the object was added, FALSE if
it couldn't be checked out (perhaps because the object is already checked
out). |
Removes an object from the database.
This deletes the given object from the database.
An object cannot be deleted if it is checked
out.
|
Parameter
Name |
Description |
|
DatabaseName |
Name of the database. This cannot contain any
spaces, and must be made up of letters, numbers (except the fist character),
and underscores. |
|
Object |
Object to be deleted from the database. |
|
Return
value description |
Returns TRUE if the object was deleted, FALSE
if it couldn't be deleted (perhaps because it doesn't exist or is checked
out). |
Returns a list of objects checked out in the
database.
Returns a list of objects checked out in the
database.
|
Parameter
Name |
Description |
|
DatabaseName |
Name of the database. This cannot contain any
spaces, and must be made up of letters, numbers (except the fist character),
and underscores. |
|
Return
value description |
List of all the objects in the database that are checked out to this
VM. |
Gets the Nth (checked out) object in the
datbase.
This gets the Nth (potentially checked-out)
object in the database.
It can be used to see what objects are in the
database and/or checked out, and if they should be checked out or deleted.
|
Parameter
Name |
Description |
|
DatabaseName |
Name of the database. This cannot contain any
spaces, and must be made up of letters, numbers (except the fist character),
and underscores. |
|
Index |
Index to get, from 0 to DatabaseObjectNum()-1. |
|
CheckedOut |
If TRUE then this gets the Nth checked out
item in the database. If FALSE if gets the Nth item (checked out or checked
in). |
|
Return
value description |
Object, or NULL/FALSE if error. |
Returns the number of objects (checked out) from
the database.
Returns the number of objects (potentially
checked out) from the database. This function is useful for making the
DatabaseObjectGet() call.
|
Parameter
Name |
Description |
|
DatabaseName |
Name of the database. This cannot contain any
spaces, and must be made up of letters, numbers (except the fist character),
and underscores. |
|
CheckedOut |
If TRUE returns the number of checked out objects. If FALSE returns
the number of objects (total) in the database. |
|
Return
value description |
Returns the number of checked-out objects, or
FALSE if error. |
Checks to see if an object is checked out.
Checks to see if an object is checked out.
|
Parameter
Name |
Description |
|
DatabaseName |
Name of the database. This cannot contain any
spaces, and must be made up of letters, numbers (except the fist character),
and underscores. |
|
Object |
Object to be to see if it's checked out. |
|
TestOtherVM |
If TRUE then this will see if the object is
checked out on another VM too (which is slower). If FALSE this only tests for
the current VM. |
|
Return
value description |
2 if the object is checked out to this VM. 1 if it's checked out to
another VM (not yet an issue, since a database is only owned by on VM at the
moment). 0 if it's not checked out. -1 if the object does not exist in the
database. |
Checks to see if an object is checked out to any
database.
Checks to see if an object is checked out to any
database.
|
Parameter
Name |
Description |
|
Object |
Object to be to see if it's checked out. |
|
TestOtherVM |
If TRUE then this will see if the object is checked out on another VM
too (which is slower). If FALSE this only tests for the current VM. |
|
Return
value description |
If the object is checked out, returns the
database name as a string. If it isn't then returns a NULL. |
Saves a checked-out object to the database.
This saves a checked-out object to the database.
An application should call this if any important information in the object
changes so that in the event of an application crash, the object will be saved.
If an object is checked-in it will automatically
be saved.
Once an object is saved to the database, it is
normally written to disk at the application's convenience. To write to disk
immediately (which can be slow), call DatabaseSave().
|
Parameter
Name |
Description |
|
DatabaseName |
Name of the database.
This cannot contain any spaces, and must be made up of letters, numbers
(except the fist character), and underscores. |
|
Object |
Checked-out object that is to be saved to disk. |
|
Return
value description |
Returns TRUE if the object was added, FALSE if
it couldn't be saved (perhaps because the object is not checked out). |
Causes a property to be cached by the database.
The database automatically caches some
commonly-used property values when an object is added, checked in, or saved.
Cached properties can be accessed much more quickly than ones that aren't
cached. Thus, (as a general rule) calls to DatabasePropertyGet() and
DatabaseQuery() should only be passed properties that are cached, or they will
become extremely slow.
To tell a database to cache a property, call
DatabasePropertyAdd().
Make sure to call this BEFORE filling the
database with any objects since calling
it after objects have been added will result in calls for DatabasePropertyGet()
and DatabaseQuery() for the given property to return Undefined. (This will be
rectified the next time the object is saved or checked in though.)
|
Parameter
Name |
Description |
|
DatabaseName |
Name of the database.
This cannot contain any spaces, and must be made up of letters, numbers
(except the fist character), and underscores. |
|
Property |
Name of the property. |
|
Return
value description |
Returns TRUE if the property was added, FALSE
if it couldn't be added. |
Lists all of the cached properties.
This returnes a list of all the properties
cached by the database.
|
Parameter
Name |
Description |
|
DatabaseName |
Name of the database.
This cannot contain any spaces, and must be made up of letters, numbers
(except the fist character), and underscores. |
|
Return
value description |
Returns a list of properties cached by the database. |
Gets one or more properties from one or more
objects.
This gets one or more properties from one or more
objects stored in the database. If the properties are cached (See
DatabasePropertyAdd()) then the values will be retrieved much quicker.
NOTE: If a property is not cached, and it's a
default value for the object, then DatabasePropertyGet() will return Undefined
for the value. (Example: If the object is a "cLantern", the default "Weight" for a cLantern
is 10, and this lantern's weight is 10, then DatabasePropertyGet() will return
Undefined if the property is not cached.) This happens because when an object
is saved any properties that are the same as the object's default aren't
written out (to save disk space).
|
Parameter
Name |
Description |
|
DatabaseName |
Name of the database.
This cannot contain any spaces, and must be made up of letters, numbers
(except the fist character), and underscores. |
|
Objects |
This is either a single object, or a list containing one or more
objects. |
|
Properties |
This is either a string representing a public
property, or a list containing one or more strings. A property can also be: "containedin" - Returns the object
this is contained in, or NULL if not contained. "dataischeckedout" - To see if the
object is checked out. "datadatecreated" - To find the date
that the object was created. "datadatemodified" - To find the
date that the object was last modified. "datadateaccessed" - To find the
date that the object was last checked out. (Note: The dates are returns as the number of
days since Jan 1, 2001.) |
|
Return
value description |
Returns FALSE if there was an error getting the property. If it succedes, the return is one of the following formats: Single object and single property - This returns the value for the
property. Single object and list of properties - This returns a list containing
values, corresponding to the properties. For example: If Properties had
["Name", "Age", "Height"] passed in, the return
would be ["Mike", "20", "1.9m"] List of objects and single property - The return is a list of the
property value for each object. For example: If Objects had [Mike, Fred,
George] passed in, with a Property of "Age", then the return would
be [20, 43, 15]. List of objects and list of properties - This returns a list of lists.
The top-level entries all correspond to the object. The secondard list
corresponds to the properties. Thus, if Objects was [Mike, Fred, George] and
Properties was ["Name", "Age", "Height"], then
the return would be [["Mike", 20, "1.9m"],
["Fred", 43, "1.8m"], ["George", 15, "1.7m"]] |
Removes a cached property from the database.
This removes a property that was cached by a
call to DatabasePropertyAdd()
|
Parameter
Name |
Description |
|
DatabaseName |
Name of the database.
This cannot contain any spaces, and must be made up of letters, numbers
(except the fist character), and underscores. |
|
Property |
Name of the property. |
|
Return
value description |
Returns TRUE if the property was removed,
FALSE if it couldn't be removed. |
Sets one or more properties from one or more
objects.
This sets one or more properties for one or more
objects stored in the database.
If an object is checked out then
DatabasePropertySet() will fail.
Changed properties will (eventually) be saved to
disk. To save them immediately, call DatabaseSave().
|
Parameter
Name |
Description |
|
DatabaseName |
Name of the database.
This cannot contain any spaces, and must be made up of letters, numbers
(except the fist character), and underscores. |
|
Objects |
This is either a single object, or a list containing one or more
objects. |
|
Properties |
This is either a string representing a public
property, or a list containing one or more strings. A property can also be: "containedin" - Returns the object
this is contained in, or NULL if not contained. "dataischeckedout" - To see if the
object is checked out. "datadatecreated" - To find the date
that the object was created. "datadatemodified" - To find the
date that the object was last modified. "datadateaccessed" - To find the
date that the object was last checked out. (Note: The dates are returns as the number of
days since Jan 1, 2001.) |
|
Values |
The format of values depends upon whether a single object is passed
in, or a list of objects, and whether a single property is used or a list of
properties. Single object and single property - This is the value for the
property. Single object and list of properties - This is a list containing the
new values, corresponding to the properties. For example: If Properties had
["Name", "Age", "Height"] passed in, this could
be ["Mike", "20", "1.9m"] List of objects and single property - The parameter is a list of the
property value for each object. For example: If Objects had [Mike, Fred,
George] passed in, with a Property of "Age", then this parameter
could be [20, 43, 15]. List of objects and list of properties - This must be a list of lists.
The top-level entries all correspond to the object. The secondary list
corresponds to the properties. Thus, if Objects was [Mike, Fred, George] and
Properties was ["Name", "Age", "Height"], then
this could be [["Mike", 20, "1.9m"], ["Fred",
43, "1.8m"], ["George", 15, "1.7m"]] |
|
Return
value description |
Returns TRUE if success, FALSE if there's an
error (such as one of the objects being checked out). |
Queries the database for objects meeting the
given conditions.
This searches through the database for all
objects meeting the given conditions. The objects are returned in a list.
NOTE: The conditions should rely on attributes
that are cached in the database (See DatabasePropertyAdd()) since un-cached
attributes will be slower.
NOTE: If a property is not cached, and it's a
default value for the object, then DatabasePropertyGet() will return Undefined
for the value. (Example: If the object is a "cLantern", the default "Weight" for a cLantern
is 10, and this lantern's weight is 10, then DatabasePropertyGet() will return
Undefined if the property is not cached.) This happens because when an object
is saved any properties that are the same as the object's default aren't
written out (to save disk space).
|
Parameter
Name |
Description |
|
DatabaseName |
Name of the database.
This cannot contain any spaces, and must be made up of letters, numbers
(except the fist character), and underscores. |
|
Conditions |
This defines the condiitons that must be met for an object to be
returned. (You could also think of them as a filter.) If this NULL then all
objects are returned. Otherwise, it is a list with the following format: The first element in the conditions list is an operator string
indicating what kind of condition is requires. The next parameters depend
upon the operators. For example: {"<", "|Age", 32} will return all
entries where the "Age" Property is less than 32.
{"containsc", "|Name", "Mike"} will return all
objects where the "Name" property contains the text
"Mike". The following operators are supported: Single operand: 2nd item in the list is either an attribute string or
another list. "-" - Negation. "~" - Bitwise not. "!" - Loglical not. Two operands: "%" - Modulo. "<<" - Bitwise left ">>" - Bitwise right "<" - Less than "<=" - Less than equals ">" - Greater than ">=" - Greater than equals "==" - Equality "!=" - Not equal "===" - Strict equality "!==" - Not equal, strict equality "==c" - Case insentative equals "!=c" - Case insentative not-equals "contains" - See if the 2nd item's string is contained in
the first item's string. "containsc" - Case insensative contains. Any number of operands: Any number of elements. "*" - Multiplication. "/" - Division "+" - Addition "-" - Subtraction "&" - Bitwise and "^" - BItwise xor "|" - Bitwise or "&&" - Logical and "||" - Logical or The operands (list values other than the first element) can be a
value, a property name, or another list of conditions. Property name - To indicate that a string is the name of a property,
as opposed to a string, prefix the property with a '|' character (or whatever
is passed into PropDisambig). For example "|Name" refers to the
property "Name", while "Name" is just a string. Sub-conditions - A condition can have sub-conditions. For example: To
find all people whose name is "Mike" or "Fred", then pass
in ["||", ["==c", "|Name", "Mike"],
["==c", "|Name", "Fred"]]. A property can also be: "containedin" - Returns the object this is contained in, or
NULL if not contained. "dataischeckedout" - To see if the object is checked out. "datadatecreated" - To find the date that the object was
created. "datadatemodified" - To find the date that the object was
last modified. "datadateaccessed" - To find the date that the object was
last checked out. (Note: The dates are returns as the number of days since Jan 1, 2001.) |
|
PropDisambig |
This is the character that is placed at the
beginning of a string to indicate that the string represents a property. '|'
is usually used, but any character is possible. If 0 is passed in, then all strings are
assumed to be properties. |
|
Return
value description |
Returns FALSE if there was an error enumerating the objects. If successful, it returns a list with all the objects (in the
database) which pass the conditions. |
Saves the database to disk.
Normally, the database is saved bit-by-bit to
disk, so that users don't see multi-second pauses in game-play. However, this
can be dangerous since if the server crashes, not all the data will be saved.
To ensure that all the data is saved, call
DatabaseSave().
|
Parameter
Name |
Description |
|
DatabaseName |
(Optional) Name of the
database. This cannot contain any spaces, and must be made up of letters,
numbers (except the fist character), and underscores. If no database is
specified then all of them will be saved. |
|
Return
value description |
Returns TRUE if the database was saved, FALSE if there was an error. |
Sends E-mail from the sever.
Call this to send E-mail from the server.
It connects using SMTP to send the message.
Since this is an asynchronous function, and
won't return success/failure, all the E-mail transmission and success
information is automatically logged.
NOTE: This CAN'T send mail using SSL at the
moment!
|
Parameter
Name |
Description |
|
Domain |
String of the domain that
sending from, such as "bigpond.com". |
|
SMTPServer |
SMTP server, such as
"mail.bigpond.com". |
|
EmailTo |
Email address of the person sending to, such
as "Mike@mXac.com.au". This MUST be a valid E-mail address without
extra spaces. |
|
EmailFrom |
Email address of the sender, such as "AutoSend@mXac.com.au".
This MUST be a valid E-maill address without extra spaces. |
|
NameFrom |
String to display in the "from"
field, such as "Interactive Fiction Automatic Mailer". |
|
Subject |
Subject string. |
|
Message |
Message text. |
|
AuthUser |
Some E-mail systems requires user authentication before they send
E-mail. If your system need this, make sure to fill in AuthUser and
AuthPassword. Otherwise, leave them as empty strings, "". |
|
AuthPassword |
Some E-mail systems requires user
authentication before they send E-mail. If your system need this, make sure
to fill in AuthUser and AuthPassword. Otherwise, leave them as empty strings,
"". |
|
Return
value description |
Returns TRUE, but this doesn't guarantee the mail has actually been
sent since its an asnchronous process. |
Returns the resource (in a list) for a given
online help article.
Returns the resource (in a list) for a given
online help article. If the article does not exist then it returns NULL.
|
Parameter
Name |
Description |
|
Actor |
The actor looking in
help. Some actors, either because of skills or access priviledges, won't be
able to see some help topics. |
|
ArticleName |
String with the article's name. The name must be an exact match
(except for case.) |
|
Books |
Controls what books of help will be searched. If this is NULL, then only help articles in
the default book, "", will be searched. (NULL will usually be
passed in.) If it's a string then only that book will be
searched. If it's a list of strings, then only those
books whose string appears in the list will be kept. |
|
Return
value description |
If the article cannot be found it returns NULL. Otherwise, it returns
a list. The first item is the MML text for the article, of the form
"<Help>...</Help>". The second item is the TOC directory, such as "major topic/minor
topic". It might just be an empty string, like "". The third
item the secondary TOC directory, if one exists. |
Returns the resource for a given online help
article.
Returns the resource for a given online help
article. If the article does not exist then it returns NULL.
|
Parameter
Name |
Description |
|
Actor |
The actor looking in
help. Some actors, either because of skills or access priviledges, won't be
able to see some help topics. |
|
ArticleName |
String with the article's name. The name must be an exact match
(except for case.) |
|
Books |
Controls what books of help will be searched. If this is NULL, then only help articles in
the default book, "", will be searched. (NULL will usually be
passed in.) If it's a string then only that book will be
searched. If it's a list of strings, then only those
books whose string appears in the list will be kept. |
|
Return
value description |
MML text for the article, of the form
"<Help>...</Help>". NULL if the article can't be found. |
// get it
var vList = HelpArticle (Actor, ArticleName, Books);
if (!vList)
return NULL;
// make up the string for the up-option
// potentially show "up" option
var vUp = vList[1];
if (vUp.StringLength())
vUp = " \"" + ToStringMML(vUp) + "\"";
vUp = "<p/><p align=Center><a href=\"" +
ToStringMML (ToString(sHelpContents) + vUp) +
"\"><bold>" +
ToStringMML (sHelpContentsUp) +
"</bold></a></p>";
// find the ending MML in the list
var vRet = vList[0];
var vSearch = "</MML>";
var vFind = vRet.StringSearch (vSearch);
if (vFind >= 0)
vRet.StringInsert (vUp, vFind);
// add in back
var vBack;
if (Actor && IsList(Actor.pHelpHistory) && (Actor.pHelpHistory.ListNumber() >= 2)) {
vBack = "<p/><p align=Center><a href=\"" +
ToStringMML (sHelpBackCommand) +
"\"><bold>" +
ToStringMML (sHelpBackShow) +
"</bold></a></p>";
vFind = vRet.StringSearch (vSearch);
if (vFind >= 0)
vRet.StringInsert (vBack, vFind);
}
return vRet;
Returns the directory of articles at the given level
of contents.
Returns the directory of articles at the given
level of contents.
Articles are organized into directories, just
like the file system. Each article is placed somewhere in the directory
structure, such as "Major category/minor category/sub category".
(Note the FORWARD slash.)
Calling HelpContents() with "" will
return all articles at the root of the directory structure, and a list of all
sub-directories.
Calling with "Major Category" will
return all articles and sub-directories under "Major Category"; this
will include "Minor Category".
|
Parameter
Name |
Description |
|
Actor |
The actor looking in
help. Some actors, either because of skills or access priviledges, won't be
able to see some help topics. |
|
Directory |
Directory to enumerate. This can be "" to enumerate the
root, or a series of names separated by a forward slash ('/'). |
|
Books |
Controls what books of help will be searched. If this is NULL, then only help articles in
the default book, "", will be searched. (NULL will usually be
passed in.) If it's a string then only that book will be
searched. If it's a list of strings, then only those
books whose string appears in the list will be kept. |
|
Return
value description |
NULL if there's an error, or a list if it succedes. The first element of the list is a list of all the articles in the
directory. Each article is a list of its own, with the first element being
the article name, and the second being a short description of the article. The second element of the main list is a list of sub-directories, each
sub-directory being a string. |
Returns the directory of articles at the given
level of contents, as a resource string.
Returns the directory of articles at the given
level of contents, as a resource string. Unlike HelpContents(), you can pass
this string to the client to have it display the help.
|
Parameter
Name |
Description |
|
Actor |
The actor looking in
help. Some actors, either because of skills or access priviledges, won't be
able to see some help topics. |
|
Directory |
Directory to enumerate. This can be "" to enumerate the
root, or a series of names separated by a forward slash ('/'). |
|
Books |
Controls what books of help will be searched. If this is NULL, then only help articles in
the default book, "", will be searched. (NULL will usually be
passed in.) If it's a string then only that book will be
searched. If it's a list of strings, then only those
books whose string appears in the list will be kept. |
|
Return
value description |
NULL if couldnt find the contents, or a resource string of the form
"<Help>...</Help>". |
// get the list
var vList = HelpContents (Actor, Directory, Books);
if (!IsList(vList))
return NULL; // error
var vArticles = vList[0];
var vDir = vList[1];
// create a nice <Help>...</Help> resource string for this
var vRet = "<Help><LangID v=" + LanguageGet() + "/><MML>";
// loop over all the articles
var i;
var vItem;
if (vArticles.ListNumber()) {
vRet += "<p><bold><big>" +
ToStringMML (Directory.StringLength() ? sHelpContentArticlesWith.StringFormat(Directory) : sHelpContentArticles) +
"</big></bold></p><ul>";
// sort the articles alphabetically
vArticles.ListSort (SortSubListString);
for (i = 0; i < vArticles.ListNumber(); i++) {
vItem = vArticles[i];
vRet += "<li><a href=\"" +
ToStringMML (ToString(sHelpArticle) + " \"" + ToStringMML(vItem[0]) + "\"") +
"\"><bold>" +
ToStringMML (vItem[0]) +
"</bold></a> - " +
ToStringMML (vItem[1]) +
"</li>";
} // i
vRet += "</ul><p/>";
} // if articles
// loop over all the directories
var vSuperDir;
if (vDir.ListNumber()) {
vRet += "<p><bold><big>" +
ToStringMML (Directory.StringLength() ? sHelpContentDirectoriesWith.StringFormat(Directory) : sHelpContentDirectories) +
"</big></bold></p><ul>";
vSuperDir = "" + Directory; // to make it into a new string
if (vSuperDir.StringLength())
vSuperDir += "/";
// sort all the directories
vDir.ListSort();
for (i = 0; i < vDir.ListNumber(); i++) {
vItem = vDir[i];
vRet += "<li><a href=\"" +
ToStringMML (ToString(sHelpContents) + " \"" + ToStringMML(vSuperDir + vItem) + "\"") +
"\"><bold>" +
ToStringMML (vItem) +
"</bold></a></li>";
} // i
vRet += "</ul><p/>";
} // if vDir
// potentially show "up" option
var vFind;
if (Directory.StringLength()) {
vFind = -1;
while (TRUE) {
i = Directory.StringSearch ("/", vFind+1);
if (i >= 0)
vFind = i;
else
break;
}
if (vFind >= 0)
vFind = " \"" + ToStringMML(Directory.StringSubString (0, vFind)) + "\"";
else
vFind = ""; // if going up to top
vRet += "<p align=center><a href=\"" +
ToStringMML (ToString(sHelpContents) + vFind) +
"\"><bold>" +
ToStringMML (sHelpContentsUp) +
"</bold></a></p>";
}
// add in a search
vRet +=
"<p align=center><bold><button accel=enter href=\"" +
ToStringMML(sHelpContentsMMLSearchCmd.StringFormat("<search>")) +
"\">" +
ToStringMML(sHelpContentsMMLSearchShow) +
"</button><font color=#000000><edit defcontrol=true width=50% name=search/></font></bold></p>";
// add in back
var vBack;
if (Actor && IsList(Actor.pHelpHistory) && (Actor.pHelpHistory.ListNumber() >= 2)) {
vBack = "<p/><p align=Center><a href=\"" +
ToStringMML (sHelpBackCommand) +
"\"><bold>" +
ToStringMML (sHelpBackShow) +
"</bold></a></p>";
vRet += vBack;
}
vRet += "</MML></Help>";
return vRet;
Searches through the help articles for a match
against the keywords.
Searches through the help articles for a match
against the keywords.
|
Parameter
Name |
Description |
|
Actor |
The actor looking in
help. Some actors, either because of skills or access priviledges, won't be
able to see some help topics. |
|
Keywords |
List of keywords to search for, separated by space. |
|
Books |
Controls what books of help will be searched. If this is NULL, then only help articles in
the default book, "", will be searched. (NULL will usually be
passed in.) If it's a string then only that book will be
searched. If it's a list of strings, then only those
books whose string appears in the list will be kept. |
|
Return
value description |
NULL if there's an error, or a list if it succedes. The list is a list of articles that match. Each article is a sub-list
with the article's name, a short description of the article, and an integer
score. The list is sorted so that the best scores are first. |
Searches through the help articles for a match
against the keywords, returning MML resource text.
Searches through the help articles for a match
against the keywords, returning MML resource text. The resource text can then
be sent to the client.
|
Parameter
Name |
Description |
|
Actor |
The actor looking in
help. Some actors, either because of skills or access priviledges, won't be
able to see some help topics. |
|
Keywords |
List of keywords to search for, separated by space. |
|
Books |
Controls what books of help will be searched. If this is NULL, then only help articles in
the default book, "", will be searched. (NULL will usually be
passed in.) If it's a string then only that book will be
searched. If it's a list of strings, then only those
books whose string appears in the list will be kept. |
|
Return
value description |
NULL if couldnt find anything to search for, or a resource string of
the form "<Help>...</Help>". |
// get the list
var vList = HelpSearch (Actor, Keywords, Books);
if (!IsList(vList))
return NULL; // error
if (!vList.ListNumber())
return NULL; // nothing found
// create a nice <Help>...</Help> resource string for this
var vRet = "<Help><LangID v=" + LanguageGet() + "/><MML>";
// loop over all the found
var i;
var vItem;
vRet += "<p><bold><big>" +
ToStringMML (sHelpSearch) +
"</big></bold></p><ol>";
// sort the articles alphabetically
for (i = 0; i < vList.ListNumber(); i++) {
vItem = vList[i];
vRet += "<li><a href=\"" +
ToStringMML (ToString(sHelpArticle) + " \"" + ToStringMML(vItem[0]) + "\"") +
"\"><bold>" +
ToStringMML (vItem[0]) +
"</bold></a> - <italic>(" +
floor (vItem[2] / 1000) +
"%)</italic> " +
ToStringMML (vItem[1]) +
"</li>";
} // i
vRet += "</ol>";
// add in back
var vBack;
if (Actor && IsList(Actor.pHelpHistory) && (Actor.pHelpHistory.ListNumber() >= 2)) {
vBack = "<p/><p align=Center><a href=\"" +
ToStringMML (sHelpBackCommand) +
"\"><bold>" +
ToStringMML (sHelpBackShow) +
"</bold></a></p>";
vRet += vBack;
}
vRet += "</MML></Help>";
return vRet;
Parses a noun-case string based upon the case
settings.
This parses a noun-case string based upon the
case settings...
A noun-case string is a string containing the
name of an object. The string also has extra tags to identify how the name
changes depending upon the "case" of the noun. For example: A noun
case string for a "person" object would be
"(sing?person:people)"; the "sing" followed by a '?' means
that if the requested case is singular then return the first option,
"person". If the requested case is not singular (plural) then return
"people". This is a simple example using English. Many languges are
highly inflected, and can get more complicated.
In the noun-case string, a test follows the form,
"(X?Y:Z)", where X is the noun-case tested (see below), Y is the
string that's used if the test passes, and Z is used if the test fails. The Z
string is not necessary; the noun-case string could just be "(X?Y)",
in which case if the X case does not pass, then no string will used.
Tests can be placed inside one another.
Therefore, "(X?(A?B:C):Z)" is possible. Tests can also be placed in
sequece and include sub-strings that are always used:
"M(X?Y:Z)N(A?B:C)O".
The noun case passed in is a number that is an
or-ed combination of the gNC_XXX_YYY global variables. (Do not change thse
variables, since they're supposed to be constant values.) To completely
describe the noun form, use one of each XXX type, except for the _Mask ending.
This would mean: gNC_Art_YYY, gNC_Count_YYY, gNC_Anim_YYY, gNC_Fam_YYY,
gNC_Verbose_YYY, gNC_Gender_YYY, gNC_Caps_YYY, gNC_Person_YYY,
gNC_Quantity_YYY, and gNC_Case_YYY. If you don't specify a case from the given
XXX category then a default will be used.
gNC_Art_YYY lets you control if there's an
article in front of the noun. gNC_Art_None is for no article, gNC_Art_Definite
uses "the" (in English). gNC_Art_Indefinite uses "a" or
"an" (in English).
gNC_Count_YYY controls the count aspect of the
noun. gNC_Count_Single creates a singular form, gNC_Count_Few is for a plural
form with two to three objects, and gNC_Count_Many is for plural objects with
four or more objects. (Few and Many are necessary since some languages have two
forms of plural.)
gNC_Anim_YYY specifies if the noun is animate or
inanimate: gNC_Anim_Animate or gNC_Anim_Inanimate. English does not use these.
gNC_Fam_YYY controls the familiar and formal
forms: gNC_Fam_Familiar and gNC_Fam_Formal. Many languages have familiar and
formal pronouns. English does not.
gNC_Verbose_YYY specifies if the name is the
long or short form: gNC_Verbose_Short or gNC_Verbose_Long. Some objects will
have a long form "John Alva Edison" or a short form "John",
depending upon how they're used in speech.
gNC_Gender_YYY can be gNC_Gender_Male,
gNC_Gender_Female, or gNC_Gender_Neuter.
gNC_Caps_YYY causes the first letter of the noun
to be capitalized. It can be gNC_Caps_Upper or gNC_Caps_Lower.
gNC_Person_YYY defines 1st (I, we), 2nd (you, you-plural),
or 3rd person (he, they). It can be gNC_Person_First, gNC_Person_Second, or
gNC_Person_Third.
gNC_Quantity_YYY indicates whether a quantity
number should be shown in front of the noun. gNC_Quantity_NoQuantity (default)
doesn't show a quantity, while gNC_Quantity_Quantity will show a quantity.
(Note: NLPNounCase() does NOT handle the display of quantities.)
gNC_Case_YYY controls the linguistic "noun
case". English has three cases: gNC_Case_Objective (such as
"me", "him", or "her"), gNC_Case_Subjective (such
as "I", "he", or "she"), and gNC_Case_Possessive
(such as "My", "his", or "her"). Other languages
have more cases. A full list can be seen by looking for global variables
beginning with "gNC_Case_".
When a noun-case string includes the name of the
case to check, it can be reduced to the first 2-4 characters of the case.
There's no need to type the whole word. Example: "(Obj?me:I)" will
test to see if the noun is gNC_Case_Objective. If it is then "me"
will be othed, otherwise "I". Enough letters need to be used from the
start of the name so that it can be identified from another case. If you're not
sure, use the first 4 characters.
|
Parameter
Name |
Description |
|
NounCaseString |
String that includes
tests based upon the case. |
|
NounCase |
Case of the noun. One or more gNC_XXX_YYY or-ed together. For example:
(gNC_Gender_Male | gNC_Count_Singular | gNC_Case_Objective) |
|
AppendNounCase |
(Optional) If TRUE (or not specified), this
appends the noun-case information to the string in the form of
"{X}", where X is the NounCase value that is used for noun-verb
agreement. Appending this information now makes the call into NLPVerbForm()
or NLPStringFormat() easier. |
|
Return
value description |
The string that results from the tests in the NounCaseString and the
NounCase settings. |
This parses a NLP sentence and produces
hypothesis.
NLPParse() accepts a natural language sentence,
such as a command or something the user says to a NPC.
It then uses a series of NLPRuleSet rules (see
the documentation) that were added through NLPRuleSetAdd() calls. Rules
disabled with NLPRuleSetEnableSet() are ignored. The language used is
determined by the current language. (See LanguageSet()).
The sentence is analyzed using the NLP rules,
producing a list of hypothesis for what was said (potentially 100's - 1000's).
The hypothesis are simplified versions of original sentence. For example:
"Could you tell me who Mike is?" and "Who is Michael?"
could both be simplified down to "`WHOIS Mike". (Other hypothesis
will also be generated.)
The returned hypothesis can be passed to a more
specific parser, such as one for interpreting commands or interpreting speech
to a NPC.
|
Parameter
Name |
Description |
|
Parser |
The name of the parser to
use. Use NLPParserEnum() to obtain a list of parsers. |
|
TemporaryRules |
These are temporary rules that will be used along with the rules in
the parser's rule sets. The format is a MML string with
"<NLPRuleSet>...</NLPRuleSet>", defining the meaning of
the rules. For more information onthe
format see NLPRuleSet. You can use the temporary rules for word/phrase meanings that change
every time the parser is called, such as pronouncs. After all, the meaning of
"it" changes depending upon the last few sentences. If you don't wish to use any temporary rules then pass in NULL. |
|
Sentence |
This is the sentence that's to be parsed. |
|
Return
value description |
This returns a list of hypothesis. Each hypothesis is a list. The first element in the list is the
probability (or score) of the hypothesis, from 0..1. The next elements are a
series of hypothesized sentences. Most hypothesis will only contain one
sentence, but some may be more than one. Each sentence is a list of words, such as "who",
"is", "mike". If any word began with a '|' character and
was followed by an object ID, the original string will automatically be
converted to an object ID. If any strings begins with a '`' then it is
automatically lower cased. |
Duplicates a NLP parser.
Use this to clone a NLP parser.
You may wish to clone a NLP parser when a new
NPC appears on the scene. A parser common to all NPCs could be set up, and then
if a NPC uses a slightly different set of rules, clone the common parser and
modify it to fit the NPC.
|
Parameter
Name |
Description |
|
Orig |
The name of the parser to
clone. Use NLPParserEnum() to obtain a list of parsers. |
|
CloneTo |
Clone the parser to this. |
|
Return
value description |
TRUE if the parser was found and cloned. FALSE
if the parser could not be found. |
Enumerates the NLP parsers.
This enumerates what NLP parsers are running in
the VM. You will probably have one parser for dealing with commands from the
user, and one or more for parsing NLP conversations with NPCs.
|
Parameter
Name |
Description |
|
Return
value description |
Returns a list containing all the parsers'
names. |
Removes a NLP parser.
This deletes a NLP parser when it is no longer
being used.
|
Parameter
Name |
Description |
|
Parser |
The name of the parser to remove. Use
NLPParserEnum() to obtain a list of parsers. |
|
Return
value description |
TRUE if the parser was found and removed. FALSE if the parser could
not be bound. |
Returns a pronoun string.
Uses the NounCase parameter to determine what
pronoun string to return, such as "I" vs. "me".
|
Parameter
Name |
Description |
|
NounCase |
One or more flags or-ed together. The flags
are from the gNC_XXX_YYY constants. For more information see NLPNounCase(). |
|
AppendNounCase |
(Optional) If TRUE (or not specified), this appends the noun-case
information to the string in the form of "{X}", where X is the
NounCase value that is used for noun-verb agreement. Appending this
information now makes the call into NLPVerbForm() or NLPStringFormat()
easier. |
|
Return
value description |
Pronoun string. |
Adds a new rule set to a given parser.
Call this to add a rule set to a given parser.
The rule set starts out enabled.
NOTE: Added rule sets are NOT saved when the VM
is saved to disk. Therefore, any objects or code that rely on a rule set MUST
reload it when the VM is reloaded from disk.
|
Parameter
Name |
Description |
|
Parser |
The name of the parser to look in. Use
NLPParserEnum() to obtain a list of parsers. If the parser does not already exist then it
will be added to the list. |
|
RuleSet |
The name of the rule set to add. If the rule set already exists then
the existing one is overwritten. |
|
Rules |
These are the rules to add. If
they are a resource they must be of the type, "NLPRuleSet". If they
are a string, they must be MML with
"<NLPRuleSet>...</NLPRuleSet>". (Search in the
documentation for more information on NLPRuleSet.) If the Rules parameter is
a resource or it's a string token, NLPRuleSetAdd() will see if the resource
or string token supports multiple languages. If it does then all of the
supported languages will be added at once. The proper language will be
selected when NLPParse() is called. Otherwise, the rule set
will only work for one language, as specified by the current language. (See
LanguageSet()) |
|
Return
value description |
TRUE if the rule set is added, FALSE if there's an error. |
Enables or disables all rule sets in a parser.
Enables or disables all rule sets in a parser.
|
Parameter
Name |
Description |
|
Parser |
The name of the parser to look in. Use
NLPParserEnum() to obtain a list of parsers. |
|
Enable |
If TRUE then enable all rule sets in the parser. If FALSE then disable
all rule sets. |
|
Return
value description |
TRUE if the rule sets are enabled, FALSE if
it's disabled. If the parser cannot be found it returns NULL. |
Returns TRUE if the rule set is enabled.
This tests to see if a rule set is enabled,
returning TRUE or FALSE.
|
Parameter
Name |
Description |
|
Parser |
The name of the parser to look in. Use
NLPParserEnum() to obtain a list of parsers. |
|
RuleSet |
The name of the rule set to test. Use NLPRuleSetEnum() to obtain a
list of rule sets in a parser. |
|
Return
value description |
TRUE if the rule set is enabled, FALSE if it's
disabled. If the rule set cannot be found it returns NULL. |
Enables or disables a rule set in a parser.
This enables or disables a rule set in a parser.
Disabling a rule set causes it not to be used when NLPParse() is called.
|
Parameter
Name |
Description |
|
Parser |
The name of the parser to look in. Use
NLPParserEnum() to obtain a list of parsers. |
|
RuleSet |
The name of the rule set to set. Use NLPRuleSetEnum() to obtain a list
of rule sets in a parser. |
|
Enable |
Pass in TRUE to enable, FALSE to disable. |
|
Return
value description |
TRUE if success, FALSE if the rule set could not be found. |
Enumerates the rule sets in a given parser.
This returns a list of all the rule sets in a
given parser.
|
Parameter
Name |
Description |
|
Parser |
The name of the parser to look in. Use
NLPParserEnum() to obtain a list of parsers. |
|
Enabled |
If TRUE then enumerate only enabled rule sets. If FALSE then enumerate
only disabled rule sets. If NULL then enumerate all rule sets, enabled or
disabled. |
|
Return
value description |
A list containing string-based names of all
the rule sets in the parser. If the parser could not be found the function
will return FALSE. |
Returns TRUE if the rule set exists.
Returns TRUE if the rule set exists.
|
Parameter
Name |
Description |
|
Parser |
The name of the parser to look in. Use
NLPParserEnum() to obtain a list of parsers. |
|
RuleSet |
The name of the rule set to test. Use NLPRuleSetEnum() to obtain a
list of rule sets in a parser. |
|
Language |
Langauge ID (from LanguageGet()) to look for. If this is NULL
then any language is acceptable. |
|
Return
value description |
TRUE if the rule set exists. FALSE if it does not. |
Removes a rule set from a given parser.
This removes a rule set from a given parser.
|
Parameter
Name |
Description |
|
Parser |
The name of the parser to look in. Use NLPParserEnum()
to obtain a list of parsers. |
|
RuleSet |
The name of the rule set to remove. Use NLPRuleSetEnum() to obtain a
list of rule sets in a parser. |
|
Return
value description |
TRUE if the rule set was removed. FALSE if it
could not be found. |
Replaces %1, %2 in a string, and calls
NLPVerbForm().
This useful function replaces occurances of
"%1", "%2", etc. in a string with new substrings. (See the
StringFormat() method), and then calls NLPVerbForm() to convert noun-case tags
(enclosed in left and right braces) and verb-form tags (enclosed in
parenthesis).
The NLPStringFormat() function allows for easy
insertion of object names into a string. Example: NLPStringFormat ("%1
(am/are/is/are) in good shape.", object.NLPName (actor, gNC_Case_Subjective))
will result in %1 being replaced with the object name, and the correct verb
form being chosen.
You might wish to use NLPStringFormat2(), which
has the same functionality but allows objects to reword the String to meet
specific conditions.
|
Parameter
Name |
Description |
|
String |
String with %1, %2, etc. in it. If this is a list, a random element will be
selected from the list for speaking. If this is an object, the object is returned.
The passthrough happens to enable SpeakScript() to take cConvStory
(potentially associated with cKnowledge) and cConvTree objects. |
|
Param1 |
If %1 is found, it is replaced by Param1. |
|
Param2 |
If %2 is found it is replaced by Param2. |
|
Return
value description |
New string that includes the replacements. |
// select from list
while (IsList(String))
String = Random(String);
if (IsObject(String))
return String;
var sReplace = String.StringFormat (arguments[1], arguments[2],
arguments[3], arguments[4], arguments[5], arguments[6], arguments[7],
arguments[8], arguments[9], arguments[10]);
return NLPVerbForm (sReplace);
Replaces %1, %2 in a string, and calls
NLPVerbForm().
This function is like NLPStringFormat(), except
that:
- It takes slightly different parameters,
allowing you to pass in sub-lists with objects instead of using NLPName() all
the time.
- For each object mentioned, as well as the room
that the Actor is in, StringReword() is called to see if the string should be
customized for a specific case. For example: You might want to change the
response of, "You pick up the jewel." to "With greedy eyes, you
carefully pick up the jewel."
This useful function replaces occurances of
"%1", "%2", etc. in a string with new substrings or names
of objects.
|
Parameter
Name |
Description |
|
Actor |
Player character that this string will be
displayed to. |
|
String |
String with %1, %2, etc. in it. If this is a list, a random element will be
selected from the list for speaking. |
|
Replace1 |
This replaces %1 in the string. If it's a
string, the replacement is direct. If it's a list, the first parameter is the
object name to display, the second is the gNC_XXX_YYY parameters. The list
normally has only two items, but if there's a 3rd and 4th item, the 3rd is
the possessor of the object (1st item), and the 4th is the gNC_XXX_YYY
parameters for the possessor. |
|
Replace2 |
Like Replace1. |
|
Replace3 |
Like Replace1. |
|
Replace4 |
Like Replace1. |
|
Replace5 |
Like Replace1. |
|
Return
value description |
The new string. |
var vReplace = [Replace1, Replace2, Replace3, Replace4, Replace5];
var vNum = vReplace.ListNumber();
var i, vList, vRet;
// actor
if (vRet = Actor.StringReword (Actor, String, Replace1, Replace2, Replace3, Replace4, Replace5))
String = vRet;
// room
var vRoom = ActorToRoom (Actor, TRUE);
if (vRoom && (vRet = vRoom.StringReword (Actor, String, Replace1, Replace2, Replace3, Replace4, Replace5)))
String = vRet;
// call the objects
for (i = 0; i < vNum; i++) {
// only care if its a list
if (!IsList(vList = vReplace[i]))
continue;
// get the string
if (IsObject(vList[2])) {
if (vRet = vList[0].StringReword (Actor, String, Replace1, Replace2, Replace3, Replace4, Replace5))
String = vRet;
if (vRet = vList[2].StringReword (Actor, String, Replace1, Replace2, Replace3, Replace4, Replace5))
String = vRet;
}
else
if (vRet = vList[0].StringReword (Actor, String, Replace1, Replace2, Replace3, Replace4, Replace5))
String = vRet;
} // i
// change the sub-lists to strings
for (i = 0; i < vNum; i++) {
// only care if its a list
if (!IsList(vList = vReplace[i]))
continue;
// get the string
if (IsObject(vList[2]))
vReplace[i] = vList[0].NLPNamePossessed (Actor, 0, vList[2], vList[3]);
else
vReplace[i] = vList[0].NLPName (Actor, vList[1]);
} // i
return NLPStringFormat (String, vReplace[0], vReplace[1], vReplace[2], vReplace[3], vReplace[4]);
Conjugates verbs in a sentence.
This conjugates verbs in a sentence.
NLPVerbForm() is useful for dealing with verb forms when "filling in the
blank" for a strink "%1 (am/are/is) in good health.", since the
verb could be conjugated to "I am in good health.", "You are in
good health.", "He is in good health.", or "They are in
good health."
The function accepts a string containing a
sentence with special tags indicating what class each noun is, along with the
verbs of each form. It uses to noun tags to determine what forms the verb
should take.
Each noun (that could affect a to-be-conjugated
verb) must have a tag next to it. The tag is a number surrounded by curly
braces, { and }. The number is the values of gNC_XXX_YYY or-ed together. See
NLPNounCase() for a description of the flags. Example: "{345321}"
Each verb (that can be conjugated) must be in
parenthesis with the alternative forms of the verb separated by slashes. The
forms are language dependent. In English, the forms are 1st-person singular,
2nd-person singular and all plurals, and 3rd-person singular. Example:
"(am/are/is)" or "(walk/walk/walks)").
The function returns a string with the noun case
numbers removed, and the verbs conjugated.
In order to do the processing, this function
calls into NLPVerbFormCallback(). NLPVerbFormCallback() already supports
Enlgish. If you wish to use Circumreality for another language you many need to
modify this function.
|
Parameter
Name |
Description |
|
String |
The string with
noun and verb tags. |
|
Return
value description |
A string the verbs conjugated, and noun and verb tags removed. |
Callback from NLPVerbForm()
NLPVerbFormQuery() is called by NLPVerbForm() to
determine how to conjugate a verb. NLPVerbFormCallback() already supports
English. If you are porting Circumreality to another language (such as Swedish)
then you may need to modify this function.
NLPVerbFormCallback() is passed a list of all
the nouns and verbs in the sentece. The nouns are represented by their
noun-case value (See NLPNounCase() for a description). The verbs are initially
filled in with 0.
Example: If "He{34532} (am/are/is/are) in
good shape." where passed into NLPVerbForm(), NLPVerbFormQuery() would be
passed the following list: [34532, 0].
NLPVerbFormCallback() needs to find all the
0-values, indicating verbs, and look at the neighboring noun cases to determine
what form should be used. It then fills in a NEGATIVE number for the verb form.
Verb forms are language independing. In English,
however, the following forms are used:
-1 : 1st person singular
-2 : 2nd person singular, and plural
-3 : 3rd person singular
|
Parameter
Name |
Description |
|
FormList |
This originally
contains a list of noun-case values and 0's where the verb form must be
filled in. NLPVerbFormCallback() should change all the 0's to negative values
indicating their verb form. |
|
Return
value description |
None |
Returns the amount of CPU used by the process.
Returns the amount of CPU used by the process.
To use this, you'll need to occasionally poll
the CPU and subtract values over a given time segment.
|
Parameter
Name |
Description |
|
Return
value description |
List with [Time since creation, Time in
process, Main thread's time since creation, Main thread's time in process]. Time since creation - The number of seconds
since the creation of the process. Time in process - The total time spend in the
process since the process was created, divided by the number of processors in
the system. In seconds. Main thread's time since creation - Number of
seconds the main thread has existed. Main therad's time in process - Number of
seconds the main thread has run, NOT divided by the number of processors. Note: The main thread's time information is returned
since it's the thread that uses the most CPU. If you are using a multicore or
SMP machine, the main thread will be the gating factor for the maximum number
of users on the system. Once this reaches around 100% utilization you won't
be able to get any more users on, even if you have spare capacity in the
other processors. |
Keeps track of how much CPU an object uses.
This keeps track of how much CPU an object uses.
More specifically, it monitors CPU usage on the main thread, assuming that the
main thread is the bottleneck for perfomance.
You can use this to keep track of how much CPU
(approximately) an individual player is using by passing the player's character
in as the object.
This starts monitoring the current object. It assumes
that the CPU from here on out is being used by the current object, UNTIL
PerformanceCPUObject() is called again, or the callback (or timer) finishes.
|
Parameter
Name |
Description |
|
Object |
Object to be monitored, usually the player's
character. If this is NULL, the monitoring of the current object stops. |
|
Return
value description |
None |
Returns the percentage of CPU used by the
object.
Used in conjuction with PerformanceCPUObject(),
this returns the percentage of CPU being used by the given object. The
timerframe is over the last 60 seconds (approximately).
|
Parameter
Name |
Description |
|
Object |
Object to query, usually the player's
character. |
|
Return
value description |
Percentage (0.0 .. 1.0) of CPU (for the main thread only) that has
been used by this object. Of course, the number will only be accurate if
PerformanceCPUObject() was called with the object. |
Returns the amount of free space on the database
disk.
Returns the amount of free space on the database
disk.
|
Parameter
Name |
Description |
|
Return
value description |
Returns the free space in gigabytes on the
database disk. |
Returns the number of GUI objects allocated to
the process.
Returns the number of GUI objects allocated to
the process. This can be used to detect slow memory leaks.
|
Parameter
Name |
Description |
|
Return
value description |
Returns the number of GUI objects. |
Returns the number of handles used on the
server.
Returns the number of handles used on the
server. This can be used to detect slow memory leaks.
|
Parameter
Name |
Description |
|
Return
value description |
Returns the number of
handles. |
Returns the amount of memory allocated by the
server.
Returns the amount of memory allocated by the server.
|
Parameter
Name |
Description |
|
MemoryType |
Type of memory
information that want... 0 - The amount of memory
specifically allocated by the server code. 1 - The amount of memory
allocated by the memory manager (which is more than the amount allocated by
the server code.) 2 - Current working set. 3 - Amount of memory used
by the process, in total. This includes pooled and non-pooled. |
|
Return
value description |
See MemoryType. Values are in megabytes. |
Returns the amount of data sent/received from
the server over the Internet.
Returns the amount of data sent/received from
the server over the Internet.
|
Parameter
Name |
Description |
|
Return
value description |
List with [Megabytes
sent, Megabytes received]. Note: Megabyte =
1,000,000 bytes exactly. |
Returns the number of threads used on the
server.
Returns the number of threads used on the
server. This can be used to detect slow memory leaks.
|
Parameter
Name |
Description |
|
Return
value description |
Returns the number of threads. |
Limits how much memory and disk space is
allocated to the render cache.
The server accepts rendered images and
text-to-speech recordings from the players' computers and caches them away.
These are then sent out to other players, saving them processing.
Use this function to set the limits of how large
the cache will be allowed to get.
|
Parameter
Name |
Description |
|
DataMaximum |
Maximum amount of data
that will be cached, in megabytes. |
|
DataMinimumHardDrive |
If there isn't much hard drive left, the cache
will shrink itself. This is the minimum amount of hard drive (in megabytes)
will be kept available. If the free hard drive is less than this, the cache
will be deleted until that much is free. |
|
DataGreedy |
When the server first
starts up with a new MIF file, it will erase the existing cache. In order to fill the
cache quickly, if the cache size is less than DataGreedy (in megabytes) then
all rendered images and text-to-speech are accepted from players. Normally,
data is only occasionally accepted. |
|
DataMaxEntriesOn32 |
Maximum number of entries that will be allowed
on a 32-bit server (around 10000). This number has to be somewhat small so
that the memory for the index doesn't use up all the limited 2 gigabyte
limit. |
|
DataMaxEntriesOn64 |
Maximum number of entries
that will be allowed on a 64-bit server (around 1000000). |
|
Return
value description |
None |
Enumerates all the saved games.
Enumerates all the saved games.
|
Parameter
Name |
Description |
|
FileName |
"File name" for
the saved game. This must be short (less than 64 characters) and not contain
any illegal windows file-name characters, such as \. |
|
Return
value description |
Returns a list of saved games. Each saved game
is a list with the first element being the game name, followed by the
creation time, modification time, and last access time. |
Deletes a save-game file, and all of its
sub-files.
Deletes a save-game file, and all of its
sub-files.
|
Parameter
Name |
Description |
|
FileName |
Saved-game file to
delete. |
|
Return
value description |
TRUE if the file was deleted. FALSE if it
doesn't exist. |
Enumerates all the saved-game files.
Enumerates all the saved-game files.
|
Parameter
Name |
Description |
|
Return
value description |
Returns a list of
strings, one for each saved-game filename. |
Returns the name of a saved-game file given its
index.
Returns the name of a saved-game file given its
index.
|
Parameter
Name |
Description |
|
Index |
Index, from 0 .. SavedGamesFilesNum()-1. |
|
Return
value description |
Returns a string with the name, or NULL if it
can't be found. |
Returns the number of save-game files.
Returns the number of save-game files.
|
Parameter
Name |
Description |
|
Return
value description |
Returns the number of
saved-game files. |
Returns date/time stamp information about a
sub-file.
Returns date/time stamp information about a
sub-file.
|
Parameter
Name |
Description |
|
FileName |
"File name" for
the saved game. This must be short (less than 64 characters) and not contain
any illegal windows file-name characters, such as \. |
|
SubFileName |
Name of the sub-file. |
|
Return
value description |
Returns a list with
[creation time, modification time, last access time]. If the sub-file doens't
exist then this returns FALSE. |
Loads in a saved game.
Loads in a saved "game" that was saved
by SaveGameSave().
|
Parameter
Name |
Description |
|
FileName |
"File name" for
the saved game. This must be short (less than 64 characters) and not contain
any illegal windows file-name characters, such as \. |
|
SubFileName |
Name of the game to load. |
|
RemapObjects |
If an object is loaded
and it doesn't currently exist then it keeps its old ID. However, if an object
already exists then: If RemapObjects is TRUE then a new ID will be given to
the object (and returned; see below). If FALSE then the current object will
be replaced with the saved object. Furthermore, if
RemapObjects == FALSE and the game was saved with SavelAll == TRUE then any deleted
objects will also be deleted when the game is loaded. |
|
Return
value description |
If RemapObjects == TRUE then this will return
a list of all the objects remapped when they were loaded. Each remapping is a
list with the first element being the original object (when it was saved),
and the second element being the new object (now that it's loaded). Otherwise, this returns TRUE on success, or
FALSE on failure. |
Given an index into a saved-game file, this
returns the sub-file name.
Given an index into a saved-game file, this
returns the sub-file name.
|
Parameter
Name |
Description |
|
FileName |
"File name" for
the saved game. This must be short (less than 64 characters) and not contain
any illegal windows file-name characters, such as \. |
|
Index |
Index, from 0 .. SavedGameNum()-1. |
|
Return
value description |
Returns the sub-file's
name, or NULL if the index exceeds the number of sub-files. |
Returns the number of saved game sub-files in a
saved-game file.
Returns the number of saved game sub-files in a
saved-game file.
|
Parameter
Name |
Description |
|
FileName |
"File name" for
the saved game. This must be short (less than 64 characters) and not contain
any illegal windows file-name characters, such as \. |
|
Return
value description |
Returns the number of sub-files. |
Deletes a saved game.
Deletes a saved game.
|
Parameter
Name |
Description |
|
FileName |
"File name" for
the saved game. This must be short (less than 64 characters) and not contain
any illegal windows file-name characters, such as \. |
|
SubFileName |
Name of the game to delete. |
|
Return
value description |
TRUE if the game was
deleted. FALSE if the game name wasn't found. |
Saves a game.
This saves a "game". It does so by
saving a set of objects. NOTE: It does NOT save any global variables, so no
game information that changes should be placed in a global variable at save
time.
You can also use it to save a collection of
objects, used for instancing.
|
Parameter
Name |
Description |
|
FileName |
"File name" for
the saved game. This must be short (less than 64 characters) and not contain
any illegal windows file-name characters, such as \. |
|
SubFileName |
Name of the game to save. If a game already
exists it will be overwritten. |
|
SaveAll |
If TRUE, save all objects
except those in the list. It will also note which objects have been deleted
and save that information so when the data is reloaded it will delete objects
that had been deleted. If FALSE then save only
those objects in the list. NOTE: You shouldn't save
any of the connection objects because when you reload the game the current
connection information will be overwritten. The same goes for other objects
that depend on run-time. |
|
SaveChildren |
If TRUE, automatically expand the list to
include children (recursively) of the objects in the list. |
|
ObjectList |
List of objects to
exclude or include. This can be an empty list if SaveAll == TRUE. |
|
Return
value description |
TRUE if the game was deleted. FALSE if the
game name wasn't found. |
Returns a parameter indicating what shard this
is.
This returns a parameter indicating what shard
this is. It returns the string specified
in the TitleInfo resource for the running shard. If the server is running on
the hose (no Internet support) then it returns "offline".
|
Parameter
Name |
Description |
|
Return
value description |
String with the shard's
parameter. |
Returns TRUE if this world is runing offline.
Returns TRUE if this world is runing offline.
It calls ShardPram() and returns TRUE if the
value is "offline".
NOTE: After this is called once, you can use
gShardPramIsOffline for speed. Given all the objects that call this during
initialization, you can be absolutely sure that AFTER initialization,
gShardParamIsOffline is vlaid.
|
Parameter
Name |
Description |
|
Return
value description |
TRUE if running offline.
FALSE if online (and multiple users) |
Causes the (near) immediate shutdown of the
server.
This causes the (near) immediate shutdown of the
server. This DOES NOT check in any objects that need checking in.
You may wish to call another ShutDownXXX()
function that will give players some warning about how long they have before
the shutdown occurs, and which will check in necessary objects.
|
Parameter
Name |
Description |
|
Restart |
If TRUE then the server
will restart as soon as it has shut down. Use the automatic restart as a way
to clean up memory. |
|
Return
value description |
None |
Internal sort callback that sorts by a string,
case insensative.
Internal sort callback that sorts by a string,
case insensative.
|
Parameter
Name |
Description |
|
ItemA |
First item to compare. |
|
ItemB |
Second item to compare. |
|
Return
value description |
0 if the items are the
same, positive if ItemA appears after itemB, and negative if vice versa. |
// since the contents are strings, just compare the strings
return ItemA.StringCompare (ItemB, FALSE);
Internal sort callback that sorts by first
element (a string) in the sublist.
Internal sort callback that sorts by first
element (a string) in the sublist.
|
Parameter
Name |
Description |
|
ItemA |
First item to compare. |
|
ItemB |
Second item to compare. |
|
Return
value description |
0 if the items are the
same, positive if ItemA appears after itemB, and negative if vice versa. |
// since the contents are lists, just compare the first element of the
// list using a string compare.
return ItemA[0].StringCompare (ItemB[0], FALSE);
Internal sort callback that sorts by first
element (a value) in the sublist.
Internal sort callback that sorts by first
element (a value) in the sublist.
|
Parameter
Name |
Description |
|
ItemA |
First item to compare. |
|
ItemB |
Second item to compare. |
|
Return
value description |
0 if the items are the
same, positive if ItemA appears after itemB, and negative if vice versa. |
// since the contents are lists, just compare the first element of the
// list using a string compare.
var v1 = ItemA[0];
var v2 = ItemB[0];
if (v1 < v2)
return -1;
else if (v1 > v2)
return 1;
else
return 0; // same
Internal sort callback that sorts by second
element (a value) in the sublist.
Internal sort callback that sorts by second
element (a value) in the sublist.
|
Parameter
Name |
Description |
|
ItemA |
First item to compare. |
|
ItemB |
Second item to compare. |
|
Return
value description |
0 if the items are the
same, positive if ItemA appears after itemB, and negative if vice versa. |
// since the contents are lists, just compare the first element of the
// list using a string compare.
var v1 = ItemA[1];
var v2 = ItemB[1];
if (v1 < v2)
return -1;
else if (v1 > v2)
return 1;
else
return 0; // same
Logs a line of text.
This writes a line of text to the log. (You can
also write text using TextLogNoAuto() and Trace()).
This uses the actor, room, and user set in TextLogAutoSet().
If the text log is disabled
(TextLogEnableSet()), then this won't do anything.
|
Parameter
Name |
Description |
|
Text |
Text string to log. |
|
Object |
(Optional) If the log involves an object, such
as oLantern, pass the object in here. |
|
Return
value description |
TRUE if success. FALSE if
an error occurs. |
Returns a value set by TextLogAutoSet().
Returns a value set by TextLogAutoSet().
|
Parameter
Name |
Description |
|
Parameter |
This is either
"actor" (for the player's character), "room" (for the
current room), or "user" (for the user object). |
|
Return
value description |
TRUE if success. FALSE if an error occurs. |
Sets one of the automatic objects for TextLog().
In order to save work for the author, whenever
the user sends a command, TextLogAutoSet() is called to remember the current PC
(actor), user, and room the character is in. Then, any calls to TextLog() will
automatically log the actor, user, and room.
|
Parameter
Name |
Description |
|
Parameter |
This is either
"actor" (for the player's character), "room" (for the
current room), or "user" (for the user object). |
|
Object |
Pass in an object, or NULL to clear the
setting. |
|
Return
value description |
TRUE if success. |
Deletes a log file.
Deletes a log file.
|
Parameter
Name |
Description |
|
LogID |
This is an identifier
string for the log, from TextLogEnum(). |
|
Return
value description |
TRUE if the file was deleted. FALSE if it
wasn't. A log file won't be deleted if it's read-only, or if it's still in
use. |
Returns TRUE if text logging is enabled.
Returns TRUE if text logging is enabled.
|
Parameter
Name |
Description |
|
Return
value description |
Returns TRUE if text
logging is enabled. |
Enables or disables text logging.
If you wish to disable text logging completely,
such as for an offline game, then call TextLogEnableSet(FALSE).
|
Parameter
Name |
Description |
|
Enable |
TRUE to enable, FALSE to
disable. |
|
Return
value description |
None |
Enumerates all the log files that still exist.
Enumerates all the log files that still exist.
The server's MIFL code will probably
automatically delete old log files.
|
Parameter
Name |
Description |
|
Return
value description |
Returns a list of log
files. Each log file is a sub-list with [LogID, Date]. LogID is a string that
identifies the log file, and which can be passed into a few LogTextXXX()
functions. Date is the time/date (as per TimeGet()) that the log file covers. |
Logs a line of text.
This writes a line of text to the log. (You can
also write text using TextLog() and Trace()).
If the text log is disabled
(TextLogEnableSet()), then this won't do anything.
|
Parameter
Name |
Description |
|
Text |
Text string to log. |
|
Actor |
Character object performing the action. |
|
Object |
(Optional) If the log
involves an object, such as oLantern, pass the object in here. |
|
Room |
Room object the action takes place in. |
|
User |
User object (if specific
to a user) where the object takes place. |
|
Return
value description |
TRUE if success. FALSE if an error occurs. |
Returns the number of lines in a text log.
Returns the number of lines in a text log.
NOTE: This function is somewhat slow so don't
call it too often. Try to cache the number.
|
Parameter
Name |
Description |
|
LogID |
This is an identifier
string for the log, from TextLogEnum(). |
|
Return
value description |
The number of lines in the log, or FALSE if
there's an error. |
Adjusts the gTextLogSystem and gTextLogUser
globals.
Adjusts the gTextLogSystem and gTextLogUser
globals. Both of these globals are used to determine which priority events get
logged.
TextLogPriorityAdjust() should be called
whenever a message is processed from a user, as well as at the end of the
processing. It modifies gTextLogSystem and gTextLogUser globals based on
gTextLogSystemAlert, as well as the connection's pUserLogAlert.
This also calls TextLogAutoSet() for the
"user", "actor", and "room".
|
Parameter
Name |
Description |
|
Connection |
Connection object, with
pUserLogAlert set based on the connection's user. |
|
Return
value description |
None |
Reads a line from a log file.
Reads a line from a log file. You can find out
the number of lines by calling TextLogNumLines().
|
Parameter
Name |
Description |
|
LogID |
This is an identifier
string for the log, from TextLogEnum(). |
|
Line |
Line number, from 0 to TextLogNumLines(). |
|
Return
value description |
If successful, this
returns a list with [Time, String, Actor, Object, Room, User]. Time is the
time (as per TimeGet()) when the event occurred. String, Actor, Object, Room,
and User are the parameters passed into TextLog(), TextLogNoAuto(), or
Trace(). |
Searches through the logs for specific events.
This searches through all the logs (from
TextLogEnum()), for events involving a specific search string, actor, object,
room, or user.
The search is asyncrhonous, so the results are
passed into a callback.
The callback will be passed a list of search
results. Each result is a sub-list with [LogID, LineNumber]. LogID is the log
file identifier, like from TextLogEnum(). LineNumber is the line number that
matches the criteria for the search (such as the string, actor, etc.)
|
Parameter
Name |
Description |
|
StartTime |
Starting time to search
(from TimeGet()). Log entries before this will be ignored. |
|
StopTime |
Finishing time (from TimeGet()). Log entries
after this will be ignored. |
|
String |
Sub-string to search for,
case INsensative. This can be NULL, in which case all strings will be
accepted. |
|
Actor |
Actor (character) that must be mentieond in
the log. If this is NULL then all actors are allowed. |
|
Object |
Object that must be
mentioned in the log line. This can be NULL. |
|
Room |
Room that must be mentioned in the log line.
This can be NULL. |
|
User |
User that must be
mentioned in the log line. This can be NULL. |
|
Callback |
Either a function or method (with object)
that's called. This should accept two parameters. The first is the search
results (see main description). The second is Parameter. |
|
Parameter |
Parameter to pass into
the callback. |
|
Return
value description |
TRUE if the search was started. FALSE if it
failed. |
Call this to allow voice chat wave-bases to be
used by various users.
Players can disguise their voices with voice
chat. The disguises can include basing their disguised voice off a looped voice
recording instead of their normal voice. This allows their voice to sound like
a growl, or like a musical instrument, etc.
As a security precaution, the server only allows
pre-approved wave files to be used. In order for a wave file to be
pre-approved, you must pass a <VoiceChatInfo> resource into
VoiceChatAllowWaves(). This will remember all the wave files from the resource.
|
Parameter
Name |
Description |
|
AllowFiles |
This is a
<VoiceChatInfo>...</VoiceChatInfo> resource. See the main
function description. |
|
Return
value description |
If successful, this returns a list with [Time,
String, Actor, True on success. |
Sorted list of all the connection objects on the
system.
You can examine GConnectionList to see the list
of all "Connection" objects. Do NOT change this. The list will
automatically be updated as Connection objects are added or removed.
[]
Number of seconds before a user is logged off.
This is used when a user logs off, and dissuades
them from logging out during combat.
30
Animate object (such as a person).
Animate object (such as a person).
Some languages have different noun cases for
animate vs. inanimate object.
See NLPNounCase() for a complete description.
0x0010
Inanimate object (such as a box).
Inanimate object (such as a box).
Some languages have different noun cases for
animate vs. inanimate object.
See NLPNounCase() for a complete description.
0x0020
Animated mask
Animated mask.
See NLPNounCase() for a complete description.
0x0030
Definite article ("the" in English).
Definite article ("the" in English).
See NLPNounCase() for a complete description.
0x0003
Indefinite article ("a" or
"an" in English).
Indefinite article ("a" or
"an" in English).
See NLPNounCase() for a complete description.
0x0002
Animated mask
Animated mask.
See NLPNounCase() for a complete description.
0x0030
Definite article ("the" in English).
Definite article ("the" in English).
See NLPNounCase() for a complete description.
0x0003
Lower-case noun. (Ex: "car", instead
of "Car")
Lower-case noun. (Ex: "car", instead
of "Car")
See NLPNounCase() for a complete description.
0x1000
Capitalization mask
Capitalization mask.
See NLPNounCase() for a complete description.
0x3000
Upper-case noun. (Ex: "Car", instead
of "car")
Upper-case noun. (Ex: "Car", instead
of "car")
See NLPNounCase() for a complete description.
0x2000
Abessive noun case.
Abessive noun case.
See NLPNounCase() for a complete description.
0x010000
Ablative noun case.
See NLPNounCase() for a complete description.
0x020000
Absolutive noun case.
See NLPNounCase() for a complete description.
0x030000
Accusative noun case.
See NLPNounCase() for a complete description.
0x040000
Adessive noun case.
See NLPNounCase() for a complete description.
0x050000
Allative noun case.
See NLPNounCase() for a complete description.
0x060000
Comitative noun case.
See NLPNounCase() for a complete description.
0x070000
Dative noun case.
See NLPNounCase() for a complete description.
0x080000
Dedative noun case.
See NLPNounCase() for a complete description.
0x090000
Elative noun case.
See NLPNounCase() for a complete description.
0x0a0000
Ergative noun case.
See NLPNounCase() for a complete description.
0x0b0000
Essive noun case.
See NLPNounCase() for a complete description.
0x0c0000
Genetive noun case.
See NLPNounCase() for a complete description.
0x0d0000
Illative noun case.
See NLPNounCase() for a complete description.
0x0e0000
Inessive noun case.
See NLPNounCase() for a complete description.
0x0f0000
Instrumental noun case.
See NLPNounCase() for a complete description.
0x100000
Locative noun case.
See NLPNounCase() for a complete description.
0x110000
Case mask.
See NLPNounCase() for a complete description.
0xff0000
Nominative noun case.
See NLPNounCase() for a complete description.
0x120000
Object noun case. English only. (Ex:
"me", "him", "her")
NOTE: This is the same value as the accusative
case.
See NLPNounCase() for a complete description.
0x040000
Oblique noun case.
See NLPNounCase() for a complete description.
0x130000
Partitive noun case.
See NLPNounCase() for a complete description.
0x140000
Possessive noun case.
See NLPNounCase() for a complete description.
0x150000
Postpositional noun case.
See NLPNounCase() for a complete description.
0x160000
Prepositional noun case.
See NLPNounCase() for a complete description.
0x170000
Prolative noun case.
See NLPNounCase() for a complete description.
0x180000
Subjective noun case. English only. (Ex:
"I", "he", "she")
See NLPNounCase() for a complete description.
0x120000
Terminative noun case.
See NLPNounCase() for a complete description.
0x190000
Translative noun case.
See NLPNounCase() for a complete description.
0x1a0000
Vocative noun case.
See NLPNounCase() for a complete description.
0x1b0000
Plural noun, but only a few ("two
cars").
This is available since some langauges
differentiate between plural (2-3) and plural (4+) for noun cases.
See NLPNounCase() for a complete description.
0x0008
Plural noun, more than a few ("ten
cars").
This is available since some langauges
differentiate between plural (2-3) and plural (4+) for noun cases.
See NLPNounCase() for a complete description.
0x000c
Count mask.
See NLPNounCase() for a complete description.
0x000c
Singlular noun ("one car").
See NLPNounCase() for a complete description.
0x0004
Familiar noun (as opposed to formal).
Some languages differentiate familiar nouns from
formal ones.
See NLPNounCase() for a complete description.
0x0040
Formal noun (as opposed to familiar).
Some languages differentiate familiar nouns from
formal ones.
See NLPNounCase() for a complete description.
0x0080
Familiar mask.
See NLPNounCase() for a complete description.
0x00c0
Female gendered noun.
See NLPNounCase() for a complete description.
0x0800
Male gendered noun.
See NLPNounCase() for a complete description.
0x0400
Gender mask.
See NLPNounCase() for a complete description.
0x0c00
Neuter gendered noun.
See NLPNounCase() for a complete description.
0x0c00
First person ("I" or "we").
See NLPNounCase() for a complete description.
0x4000
Person (1st, 2nd, or 3rd) mask.
See NLPNounCase() for a complete description.
0xc000
Second person ("you" or
"you"-plural).
See NLPNounCase() for a complete description.
0x8000
Third person ("he" or
"they").
See NLPNounCase() for a complete description.
0xc000
Speaks a NLP name as a pronoun.
See NLPNounCase() for a complete description.
0x4000000
Quantity mask.
See NLPNounCase() for a complete description.
0x3000000
Don't show any quantity numbers in front of the
noun.
See NLPNounCase() for a complete description.
0x1000000
Show quantity numbers in front of the noun, such
as "52 gold pieces".
See NLPNounCase() for a complete description.
0x2000000
Use the long form of the noun.
Example: The short form might be
"knife" while the long form might be "long and pointy
knife".
See NLPNounCase() for a complete description.
0x0200
Verbose mask.
See NLPNounCase() for a complete description.
0x0300
Use the short form of the noun.
Example: The short form might be
"knife" while the long form might be "long and pointy
knife".
See NLPNounCase() for a complete description.
0x0100
Keeps track of the amount of CPU used for the
last few minutes.
This is a list of CPU-performance snapshots
taken every 10.0 seconds.
Each sub-element contains, [TimeSeconds,
ProcessUsedSeconds, MainThreadTimeSeconds, MainThreadUsedSeconds]. TimeSeconds
is the time, in seconds, since the process started. ProcessUsedSeconds is the
number of seconds spent in the process since it began running.
MainThreadTimeSeconds is the time the main thread has been running.
MainThreadsUsedSeconds is the number of seconds the main thread has been used
since it began running.
oServerTimer will keep around 100 records,
covering 1000 seconds.
[]
Keeps track of the network performance for the
last few minutes.
This is a list of network-performance snapshots
taken every 10.0 seconds.
Each sub-element contains, [TimeSeconds,
Megabytes sent, Megabytes received]. TimeSeconds is the time in seconds, since
the process started. Megabytes sent/received indicate how much data has been
sent over the internet since the server started.
oServerTimer will keep around 100 records,
covering 1000 seconds.
[]
Default value passed to RenderCacheLimits().
1000; // 1 gig
Default value passed to RenderCacheLimits().
10000
Default value passed to RenderCacheLimits().
100000
Default value passed to RenderCacheLimits().
100000; // 100 gig
Default value passed to RenderCacheLimits().
1000; // 1 gig
Maximum number of GUI objects that can be
allocated to the process before it shuts down automatically.
1000
Maximum number of handles allocated on the SERVER
before automatic shutdown.
50000
Maximum number of megabytes that can be
allocated before an automatic shutdown occurs.
This defaults to 1.5 gigabytes, since 32-bit
windows has a 2 megabyte limit.
1500; // 1.5 gb
Maximum number of threads allocated on the
SERVER before automatic shutdown.
2000
Minimum amount of disk space (in gigbytes) that
must be available before automatic shutdown.
This is a safety measure to ensure the server
never runs out of disk space and crashes.
0.5; // 0.5 gigabytes
Where single-player games are stored.
This is the main "file name" used for
saving single-player games.
"SinglePlayer"
Set by a call to ShardParamIsOffline(), and is
valid thereafter.
Undefined
Automatically delete text logs older than this
many days.
3
List used to quickly determine if a system event
should be logged.
This is a list that's used to quickly determine
if a system event (not related to a specific user) should be logged. See the
tutorial on the text logging features for more information.
[TRUE, TRUE, TRUE, FALSE, FALSE]
Determines what priority events should be
logged.
gTextLogSystemAlert is used by
TextLogPriorityAdjust() to determine what events are logged, and ultimately
affects the list values for gTextLogSystem and gTextLogUser.
Use a value of 0 for a normal system alterness,
causing priority 1 and 2 events to be logged, while 3 and 4 are ignored. 1
indicates a higher level of alter, with event priorities 1 through 3 being
logged. 2 causes priorities 1 through 4 to be logged.
Changing this won't affect gTextLogSystem or
gTextLogUser until TextLogPriorityAdjust() is called.
0
List used to quickly determine if a user event
should be logged.
This is a list that's used to quickly determine
if a user event (specific to a user) should be logged. See the tutorial on the
text logging features for more information.
[TRUE, TRUE, TRUE, FALSE, FALSE]
BUGBUG – Not yet copied from integrated development environment. You can find this information by installing CircumReality, and running MIFServer.exe.
BUGBUG – Not yet copied from integrated development environment. You can find this information by installing CircumReality, and running MIFServer.exe.
BUGBUG – Not yet copied from integrated development environment. You can find this information by installing CircumReality, and running MIFServer.exe.
BUGBUG – Not yet copied from integrated development environment. You can find this information by installing CircumReality, and running MIFServer.exe.
BUGBUG – Not yet copied from integrated development environment. You can find this information by installing CircumReality, and running MIFServer.exe.
BUGBUG – Not yet copied from integrated development environment. You can find this information by installing CircumReality, and running MIFServer.exe.
BUGBUG – Not yet copied from integrated development environment. You can find this information by installing CircumReality, and running MIFServer.exe.
BUGBUG – Not yet copied from integrated development environment. You can find this information by installing CircumReality, and running MIFServer.exe.
BUGBUG – Not yet copied from integrated development environment. You can find this information by installing CircumReality, and running MIFServer.exe.