|
This manual covers scripting/coding for the Wyvern Platform.
It's for advanced users only: you should already be fairly
comfortable in at least one programming language before you
read this manual.
Contents
You can do just about anything in Wyvern using
Jython or
Java. The two languages
are basically equivalent — different syntax, but you can do
the same things in each of them.
Some of our examples will be in Java and some will be in Jython.
After a while you'll see how to translate between the two of
them. The calls to the game functions have the same names and
arguments.
Most of the game is written in Java, because it's faster.
Jython is an implementation of the language
Python for Java, so it
has to get translated down into Java before the game can
run the code.
Despite it being about 2x slower, Jython is the best language
for doing Wiz areas, because you don't have to compile it.
You just create objects with it, and any changes you make
show up in the new objects (but not the old ones). You can
test different code side-by-side with multiple game objects.
We recommend you use Jython for your Wyvern programming.
This manual doesn't cover the Jython or Java language syntax.
We focus primarily on the services that are available within
the game for creating and manipulating objects and maps. From
time to time we'll point out Jython- or Java-specific issues
you have to be aware of.
Jython and Java are both Object Oriented languages. You
will have a lot of trouble using them if you're not familiar
with the the basic Object-Oriented Programming (OOP) concepts:
classes, objects, instantiation, inheritance, interfaces,
methods, instance variables, and encapsulation. They're not
difficult concepts to lean, and you should find yourself an
OOP tutorial if you're unfamiliar with this programming style.
The Wyvern game engine, which we'll call the Wyvern Platform
in the rest of the document, is designed to give you maximum
flexibility for controlling the game environment. We've written
hundreds of game extensions, including:
- all the spells (120 or so)
- all the guilds
- all the special magical items like crystal balls,
portable holes, monster-suckos, cloak of thieves, and
so on
- the Minath Monster Arena and the Alaria Fight Arena
- the Tower of Sokoban
- the Chess and Checkers games
- the Side-Scroller quest
As well as many, many others. We're only scratching the surface,
though — we expect that over time, Wizards will create hundreds
or even thousands of completely new experiences for the game.
Before you can realistically understand the examples we give,
though, you have to know what things you can (and can't) do with
the game engine. We'll cover them briefly here before moving on
to more detailed tutorials.
You have 4 basic tools at your disposal for creating content
for the game:
- game maps, which you can create with the map editor
- archetypes, which (right now) you have to create by hand
- jython and java source-code files
- xml files for storing things like High Score lists
and other rich content. The game provides utilities
that make it fairly straightforward to write and read
your own custom xml files.
We're going to assume, for this manual, that you know how to
create maps and archetypes. At some point we'll have a comprehensive
manual on how to do it, but right now Wizards should be able
to help you figure it out.
Speaking of tools, in order to program for Wyvern you'll need:
- the Map Editor (try to stay in sync with the latest version)
- any plain-text editor like Notepad, vi, or Emacs
- an image editor that can make GIF files (if you're doing artwork)
You do not need:
- jython on your local machine. It's on the server. All
you need to do is upload your .py files.
- a java compiler. This is also on the server. There's
no way to test your source code on your local box.
This manual will focus on creating objects in Jython and Java.
Before you go much further, you should read the
Getting Started document — it
shows you how to create, upload, and execute python files.
The game has a number of concepts or abstractions that you should
familiarize yourself with:
- Maps — the game has maps that consist of objects
at (x, y) locations. x goes to the right (positive)
and y goes down. Maps are connected together by
teleporters (buildings, stairs, invisible teleporters,
and so on). There are two main kinds of maps, "unbounded"
and "bounded", but 99.9% of the maps in the game are
bounded maps, and you'll almost always use unbounded ones.
- Game Objects — any object that can be placed in
a map is a Game Object, including terrain, buildings,
monsters, players, weapons, armor, spells, traps,
and some invisible objects like teleporters, shops,
and no-monsters squares. Any archetype you can select in
the map editor and put into the map is a Game Object.
- Properties — game objects have properties, such
as their image, height, width, short name, long name,
hit points, weight, and so on. Every property has a
name (a string) and a value that can be
a double, int, boolean, string, Archetype, or JavaBean.
More on this later.
- Archetypes — these are templates from which Game
Objects are produced. They function a bit like classes,
but with no code. They're basically just lists of
properties, usually stored in an XML file with the
extension ".arch".
- Monster AIs — monsters (and some spells) are controlled
by AIs (Artificial Intelligences). Most monsters are
controlled by the StandardAI, which is fairly customizable
via properties on the monster, but you can also create your
own AIs.
- Cameras — a Camera is something that can provide
a view of a map. Every player has a camera that you can
take control of. For instance, ships can move the player's
camera inside and outside the ship. The Wizard Eye spell
and Crystal Ball also control the player's camera.
- Timers — you can set timers, so you can get
called back after a certain period. There's a special
utility class that lets you set timers that suspend when
the object's map is suspended or unloaded, and resume
if the map resumes.
- Hooks — these are special notifications you can
register for. If you want to know if someone has done
something (and possibly change it), you use Hooks. There
are least four different kinds, and we'll talk a LOT more
about them in this manual, since they're the way you get
the most interesting things accomplished.
- Commands — also known as Commands, these are
objects that provide commands to players, monsters, and
wizards. All the
player commands and
wiz commands
are provided by Commands. You can make your own Commands to
give the player access to new commands. For instance, the
Alaria Fight Arena is an Command that gives you "fight",
"match", and the other arena commands.
- Events and Event Queues — every command
in the game is encapsulated as an Event. Every monster
and player in the game (and some other objects) has at
least one Event Queue for holding the events while they're
waiting to be executed. You can issue commands to a
player or monster by sticking events in their queue.
- Predicates — a Predicate is a test, like "is this
player blind", or "is this object made of gold". Predicates
are encapsulated as objects so you can pass them into a map
(or bag, or whatever) to look for GameObjects that "match"
your test. The game has dozens of built-in predicates you
can use, or you can easily create your own.
- Logs — a Log lets you write a timestamped message
to a text file. The game provides APIs for writing to
your own log files.
We'll talk in more detail about each of these abstractions
later in the manual. For now, just be aware that these are
the major types of objects in the game — the ones you'll be
dealing with the most as you create custom content.
Before we go into too much more detail, it's useful to cover
the steps that the game goes through to load the game, or
load a map, or load an object in a map.
The game starts by creating the World object, which maintains
the player lists and active maps. Whenever you want to see
who's online, or what maps are loaded, you ask the World object
(wyvern.world.World).
The World bootstraps the Game Scheduler, which handles all
events in the game. This includes:
- command events, such as players saying "move north"
or "kill kobold".
- timer events, for objects that have set timers.
The most common ones are animation timers, but we
also have corpse decay timers, lava timers, and many
others.
- camera events — well, just one. The game stops
every 1/15th of a second or so and refreshes all the
cameras in the game. This is handled as an event that
goes through the queue just like any other event, largely
for auditing purposes.
Once the Scheduler is started, the World loads up all the
maps in the system preload list. This is just a text file
of all the maps (usually cathedrals) that we want loaded on
startup. After preloading the maps, the World fires off a
few processes to update the high score list and deaths list,
and returns control to the server.
The last thing the Server does is open a listener for
incoming connections. Whenever a connection comes in, it
hands the connection to the wyvern.world.Registration, which
handles connecting a new player and logging them in.
A map is loaded by a class called wyvern.kernel.maps.MapLoader.
There is no other way to load a map, unless you want to do a LOT
of work by yourself.
You don't usually load maps by creating a MapLoader (although you
can). Typically you just ask the World to load the map for you. The
World normally does this on a separate thread of execution (i.e. in
the "background"), so the game doesn't lock up while the map is
loading. This is why you get the message "Please wait while this map
is loaded." when you first apply a teleporter.
Maps are stored in XML files with a special format. They
look like this:
<map>
<header>
(map properties)
</header>
<arch ...>
<arch ...>
<arch ...>
</map>
That is, the first XML element is a "header" element that has
the properties for the map, such as whether it's dark, and whether
it allows player-killing.
All the remaining elements are "arch" entries, which stands for
archetypes. An arch element specifies 3 things:
- the (x, y) location in the map where the object should be placed
- the path of the archetype to load OR class of the object to create
- (optionally) any additional properties to put on the created object
The MapLoader reads each object in sequence and puts them in the
map one at a time, building the map up from scratch. If an object
needs to know when all the objects are finally in the map, there's
a special "done-loading" notification it can register for. More
on this later.
All objects that can go in a map file are GameObjects — that is,
an archetype always specifies a GameObject to create, plus some
additional properties to add to the object. GameObjects can be
written in Java or Jython.
As the MapLoader loads objects and puts them in the map, it
goes through the following sequence:
- The object's properties are all parsed from the XML.
They can be of type double, int, boolean, string, archetype,
or javabean. We'll talk more about this later.
- All of the object's parent archetypes are loaded from disk
(if any). For instance, if you put in a diamond, the game
may have to load the "gem" parent-archetype as well (if
diamond inherits from gem, which in fact it does).
- The MapLoader "instantiates" the archetype, which means
it finds the Java or Jython class (such as Bag, or Monster),
and creates an object of that class. Then it copies the
properties from the archetype onto the object.
- Finally, it places the object in the map at the location
specified in the "<arch..." xml element.
That may all sound pretty boring, but it's useful to keep it in
mind when you're creating your objects.
All we've talked about so far is how the game builds up the
in-game data structures that represent maps, objects, and properties.
How does stuff happen? How do things move around?
Everything in the game happens through an Event of some sort.
Well, almost everything. If a player wants to move, for example,
the following sequence of events occurs:
- the player types "move"
(or clicks in their view with the mouse)
- the client sends a command ("move east",
or a direction key, or "mouse x y")
to the server
- the server takes the text command and wraps it with a
CommandEvent, which parses the verb and arguments.
The command verb is the first word. The arguments are
everything after the first word.
- the server stuffs the new CommandEvent into the player's
Event Queue.
- some time later, the Game Scheduler notices this new
event in the player's queue, and tells the Executive
to execute the event.
- the Executive asks the player object to say which
object ("Command") will handle the command, in this
case, "move".
- our example command, "move",
is handled by the MoveCommand
(wyvern.kernel.motion.MoveCommand). The MoveCommand has
code for figuring out whether the player can move, how fast
to move, whether the move is actually a push or an attack,
and then, ultimately, it moves the player to the new spot
in the map.
That's pretty much the sequence of things, over and over.
We've basically got enough background now to be able to start
talking about the meatier coding details — in other words, how
you can make YOUR stuff happen.
There are several fundamentally different things you can
do by writing code:
- You can create objects and put them in maps.
You can load them from archetype files, or assemble them
yourself. This includes ALL GameObjects — terrain, monsters,
spells, shops, everything. Well, not Players — that would
be a bad idea. But everything else.
- You can examine or set an object's
properties.
For example, your code could make a player blind, or invisible,
or flying, or change their name or title. There are wizard
commands that can do this as well. Setting properties is by
far the most common thing that happens in Wyvern code, so
we'll do plenty of examples for it.
- You can call game functions —
for instance, you can load maps, shout to everyone,
have players broadcast messages to people near them,
and hundreds (if not thousands) of other functions
that are already implemented for you. The complete
list is available in javadoc format.
- You can provide new commands for players.
For example, the Portable Hole provides the command "exit"
for getting out of the hole. Ships, shops, guilds, and
other objects provide new commands for players.
- You can change what happens for existing
commands. For instance, the Sokoban game prevents you
from moving diagonally. This is the hooks mechanism
we've mentioned, and you'll learn a lot more about it later.
- You can be notified when specific things
happen.
Perhaps you want your monster to start moving when someone gets
too close to your altar. These notifications are also done
using hooks (a slightly different kind).
- You can read and write data in files.
You can use plain text files, XML files, or binary data —
your choice. Java and Jython provide excellent file I/O
mechanisms, and Wyvern provides an XML wrapper class for
doing XML input and output.
Most of the interesting objects in the game do several of
these things. For instance, the Minath Monster Arena does
a bunch of them:
- it provides the "start"
and "bail" commands for starting
a fight or wimping out.
- it creates monsters on the fly (actually by reading them
out of an XML file, which says which monsters should be
used for each level in the areana).
- it calls game functions — for example, when the Game
Driver shouts "So-and-so is fighting in the Minath Arena",
the arena is called the World.shout() function.
- it hooks the DeathEvent, and instead of actually dying,
the player gets teleported out of the arena. It also
hooks the "apply" command,
to prevent players from entering portable holes.
- it writes to a file (XML again) with the player's ranking,
level, best time, and so on.
- it sets properties on the player (healing them and
restoring drained XP when the match is over).
So the Minath Arena is an example of a game extension that
uses many of the mechanisms available to you. We'll probably
provide the source code for it so you can see how it was done.
Gory Details
OK, we've hopefully given you a nice preview of some of the
things you can do by coding in Jython. Now it's time to
move into the real details — exactly what hooks are, how
to use properties, how to make an Command to provide new
commands, and so forth.
|