Code Examples
This section just has a whole bunch of sample code, taken right
out of the game. We don't go into enormous detail about the
examples - they're here for you to browse and study, but we've
focused more on quantity of code than on quality descriptions.
Keep in mind that many of these objects have undergone revisions
or bug fixes since we uploaded the code. We'll try to keep them
up to date if there are major fixes, but there's no guarantee
the real versions of these objects work exactly like this in the
game. These are here as tutorials, not spoilers.
Contents
This section has a few different magical game objects that are
implement in Jython.
Here's a very simple piece of armor, the Cloak of Invisibility. This
cloak makes you invisible when you put it on.
We can't quite use a simple archetype for the cloak, because it
has to visually update the wearer (on everyone's screen) when it
is worn or removed. So we create a tiny Jython class for it.
Here's the source code
The class has the following parts:
- A block comment at the top, saying what it does.
- The imports section, where we declare what Java
class names we want to use.
- The class definition, where we say it inherits from Cloak
(in package wyvern.lib.classes.armor).
- An initialize method, where we set the default
properties for this cloak.
- The setWorn method, which is where we know it
has been worn or removed. See the documentation for
wyvern.lib.classes.armor.Armor for details.
- The __repr__ and toString methods, where
we say what the object's description will be under certain
circumstances. This little quirk is common to all Jython
files - we think it's a bug in Jython that you have to do
it, but the Jython designers disagree. Oh well.
Notice that in the initialize method, we set "bonus-invisible",
which tells it to increment the wearer's "invisible" property when
it's worn (and decrement it when it's removed). We also set an
"invisible" property, because the cloak
itself is invisible.
That's it! All of this could have been done using an archetype,
except that we needed that setWorn() function to call the agent's
invalidate() method so the screen updates when the agent wears
or removes it.
This magical object produces a random piece of food when you apply it.
It's a more complicated example.
View the source code.
You'll notice a few differences from the Cloak of Invisibility:
- It extends MapObject (the standard game object class).
- It implements a Java interface called Applyable. This
means you can apply it with the "apply" command.
- Before setting most of its default properties, it checks
to see if they already exist. If so, it doesn't set them.
This is a polite way to let someone override your default
properties in an archetype.
The key trick to this class is generating a random piece of food.
It does so by calling
Kernel.instantiate('random/random_food')
.
The result is a RandomObject, from package wyvern.lib.classes.random.
When you create a RandomObject, you need to invoke the generate()
method on the object to turn it into a real game object (in this
case, a piece of food). You'll see Kernel.instantiate() a lot,
and you'll probably use RandomObjects a lot too.
This is a fairly complicated object. It lets you type "view"
and a player's name, and you can spy on the player. It uses
some of the game's fancy extension mechanisms to do this trick.
View the source code.
The key concepts in this class are:
- The person using the crystal ball is the agent, and
the person the agent is spying on is the target.
- The ball works by moving the agent's camera along with
the target. Whenever the target moves, the ball moves
the agent's camera to the target's new location. This
idea is the basis for some other game functions, like
the Wizard Eye spell, the Wizard "snoop" command, and
ships and boats.
- The ball is an Command. Command is a Java interface
that lets you add new commands that players can type in.
In this case, the new command is "view". The Command
interface has 3 methods:
knowsCommand
,
createEvent
and execute
. You
can read more about Commands in the coding tutorial.
- It also implements the MethodHookCallback interface.
This allows the crystal ball to figure out when the target
player moves, so it can move the agent's camera along with
the target.
The crystal ball is a pretty complicated example, and we include
it so you can see just how powerful the game engine is when it
needs to be.
The Amulet of Lifesaving listens for when you die, and prevents
it. After it saves your life, it crumbles to dust.
The source code for this is
amulet_lifesaving.py.
This object is a simple HookCallback - it gets on the
death pre- and post-hooks. It uses the deathPreHook
to veto your death, and it uses the deathFailedPostHook
to crumble away and restore your HP and SP.
We could have done everything on the deathPreHook.
The only reason we wait to save their life on the
deathFailedPostHook is that there's a (remote) possibility
that another HookCallback, also on the deathPreHook, would
want to prevent the amulet from working. So we respect that and
only do our work when we know our veto was successful.
Keep in mind that having multiple HookCallbacks for the same
pre-hook is not guaranteed to work the same way every time,
since the callback objects could be invoked in any order.
So it's a pretty rare case we're coding for here - it might have
been better to have used the deathPreHook for everything.
View the source code.
The Staff of Striking is a silly artifact that makes the player
think they're doing tons of damage, when in fact it's not doing
much at all.
It's also a HookCallback, and it works by hooking the
damagePreHook and changing the damage message before
the event is executed.
Traps almost all function the same way - they have a
steppedOn
method that gets called when a player or monster triggers the trap.
View the source code.
The main concepts are:
- It inherits from AbstractTrap, just like all other traps.
- It doesn't do anything if the target isn't a Monster.
- It simply drains the target's spell points by a random
amount.
That's all there is to it!
This is a
much more complicated trap. When you step on
it, it produces random magical effects - some good, some bad.
View the source code.
The main concepts are:
- Some of its effects involve casting spells.
- Some of its effects are simply messages, and it doesn't
do anything.
- One of its effects is to create a bunch of random monsters -
a particularly nasty effect.
This trap is definitely more involved, and demonstrates different
features of the game engine.
This section has a bunch of spell examples. Nearly all the spells
in the game (well over 100 of them) are written in Jython. The
spells all inherit from the base class
wyvern.lib.classes.magic.Spell, which has utility functions
for propagating the spell and damaging monsters.
There are several "area-effect" subclasses of Spell, including:
- wyvern.lib.classes.magic.BallSpell
(examples: fireball, poison cloud)
- wyvern.lib.classes.magic.ConeSpell
(examples: fear, sandstorm, firespray)
- wyvern.lib.classes.magic.BoltSpell
(examples: lightning bolt, death ray)
Many spells are timed, so they implement the Timed interface
and set timers using the Kernel class.
All spells have one Art and one or more Elements. We try to code
spells so that if you've got extra points in the Element(s) for that
spell, it improves the spell effect.
This is one of the simplest spells you can cast. It teleports
the player to a random, nearby location.
View the source code.
The main concepts are:
- It uses the Range class (from package wyvern.lib) to produce
random values.
- When it has chosen a location to put the caster, it sends
the caster a command via the commandNow method.
The command is "teleport", where an example might be
"teleport village/village 5 3". Teleporting is how you
move players around in the game.
This spell is a pretty typical BoltSpell.
View the source code.
The main concepts are:
- It inherits from BoltSpell, which expects you to set the
properties "speed", "length", and "duration", to control
the shape and length of the spell.
- It sets a "wc-fire" property on the spell, which does fire
damage to the monsters it hits. The game has approximately
a dozen standard damage types, including fire, frost, shock,
cut, stab, smash, acid and others.
- All spells have a few elements in common, including needing
to set the Art, Element(s), and the spell description, which
is displayed by the Spellbook for that spell.
This spell is a pretty typical ConeSpell.
View the source code.
The main concepts are:
- It has three properties that determine the area of effect:
- "cone-length", which is the maximum distance the
spell reaches from the caster's fingertips.
- "fanout", which is how wide it gets at the max distance.
- "duration", which is how many "tics" the spell goes
for. If duration is less than cone-length, then the
spell will look as if it's been "cut off" before it's
finished. If the duration is longer, then the cone
will stay at its max length for longer.
- the spell does some basic checks in start() to make sure that
it can be fired, and then delegates to the superclass.
This probably seems a bit redundant, since the superclass
could do the checks. We may fix this in the future, but
for now, you have to put this snippet of code into every
ConeSpell subclass.
- it sets the "duration" property and "wc-fire" property based
on the FIRE element, which is derived from the caster's
skill in the element (or the level of the magic item, if
the spell came from a rod or whatever). So setting "duration"
in the initializer really just set a default that's overwritten
later.
Again, pretty simple. Damaging the monsters is all taken care
of by the superclass - you just have to make sure you've set
the weapon class for the spell.
This spell makes the caster temporarily immune to being slowed
or paralyzed.
View the source code.
The main concepts are:
- It implements the Timed interface, which requires a
timerExpired method for the timer to call.
When the timer goes off, we dispel the spell.
- It calls adjustTransientIntProperty on the caster's
"resist-slow" and "resist-paralysis" properties. This
makes sure the spell effect doesn't accidentally become
permanent, which would be bad. In general, when you change
properties using spells, you should change their transient
values using setTransientProperty, addTransientProperty and
adjustTransientIntProperty.
The spell also checks the agent's skills in the spell's elements,
and makes it slightly better if they have those skills.
This powerful spell does magic damage to every monster within a
certain distance of the caster.
View the source code.
The main concepts are:
- It checks the caster's sk-death and sk-spirit
skills. The death skill increases the damage, and the
spirit skill increases the spell radius.
- It looks for Attackable objects, not just Monsters. Doing
damage to Attackables lets it damage diggable walls,
monster generators and other damageable items. This makes
the spell more powerful.
- The only complicated part is where it goes through every
(x, y) location in a big square around the caster, looks
for Attackables, and calls its own damageMonster
method (inherited from the Spell superclass) on each
attackable object. In order for damageMonster to work
correctly, you have to set a weapon class on the spell
first. In our case, we set "wc-magic", meaning we do magic
damage to the target objects.
We coded the spell so that it only damages each monster once.
However, when the spell loops through every location, it will
find large monsters more than once. So we create a cache, using
a java.util.HashSet, and put the monsters in the cache when we
damage them. We don't damage a monster that we find in the cache.
This spell permanently improves a weapon by increasing its
accuracy and damage.
View the source code.
The main concepts are:
- It does a lot of checks to make sure the weapon can be
enchanted. If you don't specify a weapon to enchant, it
tries to use your first wielded weapon. It refuses to
enchant unique weapons, and if the enchantment is too high,
it has a chance of blowing up the weapon and doing damage
to the caster.
- It calls its own isCursed method, which is a
utility on the Spell superclass that returns true if
the spell was cast by a magic item (such as a scroll)
that was cursed. If so, it performs a negative enchantment.
If we blow up the weapon, it invokes the WieldCommand (from package
wyvern.kernel.combat) to force the weapon to become unwielded.
This is the proper way to unwield a weapon - if you don't do it
this way, the player will not be able to wield anything else
during that session.
This spell puts a powerful electric charge on a weapon, which
discharges the first time the weapon hits a monster.
View the source code.
The main concepts are:
- It temporarily reduces the caster's spell points while
the charge is on the blade. When the blade discharges,
it restores the spell points to the caster.
- It has to call recomputeWCs on the weapon after
adjusting its "wc-shock" property. This is required
because the weapon caches its weapon classes, which are
otherwise expensive to compute.
- It uses method hooks. Some interesting methods
in certain game classes have method hooks, which are
basically notifications for when the method is called.
Any class that has a method hook will describe it in
the documentation for that method. Method hooks differ
from "regular" hooks in that you can't veto them.
This spell stops a hostile monster in its tracks. The monster
will stop attacking until it's attacked again. It's a fairly
complicated spell.
View the source code.
The main concepts are:
- It refuses to work on certain monster types.
- It uses the veto power of the HookCallback interface.
The spell adds itself to the "attack" and "kill" commands
("kill" is actually just a synonym for "attack", but you
have to include both of them). When the monster or player
tries either command, it vetoes it, and eventually the
combat system stops trying to automatically attack back.
It takes a few rounds, though, before it works.
- It also has to remove a property called "@hunt", which is
set on opponents who are attacking each other.
How would you have known how to code the spell this way?
The answer is (hopefully) that the combat system classes are
thoroughly documented, and they tell you what properties they
set and what actions can be hooked and vetoed.
Here are some jython classes that don't fit into any particular
category.
View the source code.
This is a speech filter that makes you quack like a duck.
Speech Filters are simple to use - you just add one to a
player's property list, using any name, and they just work.
A Speech Filter only has one required method -
applyFilter(), which takes the string
to filter, and returns the filtered version.
This filter is uncomplicated, but you can imagine all sorts
of filters that do fancier things to the player's speech.
View the source code.
This is a utility class that Kiz wrote for populating a map
with random monsters of the appropriate level. You just
drop one into your map and it'll create monsters.
There's still some debugging code in there, commented out.
Following are some examples of how to do things in the game,
where we didn't have any Jython code handy, so we're just
including a sample in Java instead. The syntax is different,
but the basic approach is the same.
View the source code.
This is a simple Skill object that gives halflings the
"hide" and "unhide" commands, which turn invisibility
on and off. The basic approach for creating a Skill is
to code a class that implements AddRemoveNotify, and then
code a way to have an instance of your class added to
the player's property list.