Added TutorialWorld - a small, but complete single-player quest area showing off some of Evennia's features. You can find it in contrib/tutorial_world.

Build with: @batchcommand contrib.tutorial_world.build
I have tested it, but there are most likely still bugs, so report all you find (along with possible suggestions for improvements) to the bugtracker/mailing list.
This commit is contained in:
Griatch 2011-06-26 22:41:14 +00:00
parent 8770a263ac
commit b2b743b0aa
7 changed files with 3133 additions and 0 deletions

View file

@ -0,0 +1,107 @@
===============================================================
Evennia Tutorial World
Griatch 2011
===============================================================
This is a stand-alone tutorial area for an unmodified Evennia install.
Think of it as a sort of single-player adventure rather than a
full-fledged multi-player game world. The various rooms and objects
herein are designed to show off features of the engine, not to be a
very challenging (nor long) gaming experience. As such it's of course
only skimming the surface of what is possible.
================================================================
Install
================================================================
Log in as superuser (#1), then run
@batchcommand contrib.tutorial_world.build
Wait for building to complete. This should build the world and
connect it to Limbo.
Log is as a non-superuser to play the game as intended. The
tutorial area's systems mostly ignores the prescence of a
superuser (so use that to examine things "under the hood" later).
================================================================
Comments
================================================================
The tutorial world is intended for you playing around with the
engine. It will help you learn how to accomplish some more advanced
effects and might give some good ideas along the way.
It's suggested you play it through (as a normal user, NOT as
Superuser!) and explore it a bit, then come back here and start
looking into the (heavily documented) source code to find out how
things tick - that's the "tutorial" in Tutorial world after all.
Please report bugs in the tutorial to the Evennia issue tracker.
* Spoilers below - don't read on unless you already played the
tutorial game. *
===============================================================
Tutorial World Room map
===============================================================
?
|
+---+----+ +-------------------+ +--------+ +--------+
| | | | |gate | |corner |
| cliff +----+ bridge +----+ +---+ |
| | | | | | | |
+---+---\+ +---------------+---+ +---+----+ +---+----+
| \ | | castle |
| \ +--------+ +----+---+ +---+----+ +---+----+
| \ |under- | |ledge | |along | |court- |
| \|ground +--+ | |wall +---+yard |
| \ | | | | | | |
| +------\-+ +--------+ +--------+ +---+----+
| \ |
++---------+ \ +--------+ +--------+ +---+----+
|intro | \ |cell | |trap | |temple |
o--+ | \| +----+ | | |
| | \ | /| | | |
+----+-----+ +--------+ / ---+-+-+-+ +---+----+
| / | | | |
+----+-----+ +--------+/ +--+-+-+---------+----+
|outro | |tomb | |antechamber |
o--+ +----------+ | | |
| | | | | |
+----------+ +--------+ +---------------------+
Notes:
o-- connections to/from Limbo
intro/outro areas are rooms that automatically sets/cleans the
Character of any settings incured upon it during the
tutorial game.
The Cliff is a good place to get an overview of the surroundings.
The Bridge may seem like a big room, but it is really only one
room with custom move commands to make it take longer
to cross. You can also fall off the bridge if you
are unlucky or take your time to take in the view too
long.
In the Castle areas an aggressive mob is patrolling. It implements
rudimentary AI but packs quite a punch unless you have
found yourself a weapon that can harm it. Combat is only
possible once you find a weapon.
The Catacombs feature a puzzle for finding the correct Grave
chamber.
The Cell is your reward if you fail in various ways. Finding a
way out of it is a small puzzle of its own.
The Tomb is a nice place to find a weapon that can hurt the
castle guardian. This is infact the goal of the tutorial.
Explore on, or take the exit to finish the tutorial.
? - look into the code if you cannot find this bonus area!

View file

View file

@ -0,0 +1,991 @@
#
# Evennia batchfile - tutorial_world
#
# Griatch 2011
#
# This batchfile sets up a starting tutorial area for Evennia.
#
# This uses the custom script parents and code snippets found in
# the same folder as this script; Note that we are not using any
# modifications of the default player character at all (so you
# don't have to change anything in any settings files). We also
# don't modify any of the default command functions (except in
# states). So bear in mind that the full flexibility of Evennia
# is not used to its fullest here.
#
# If your BATCH_IMPORT_PATH is unchanged from the default,
# first place yourself in the room you want to connect to
# to the tutorial world and load the file as user #1 with
# @batchprocess tutorial/build
#
# The area we are building looks like this:
#
# ? 03,04
# |
# +---+----+ +-------------------+ +--------+ +--------+
# | | | | |gate | |corner |
# | cliff +----+ 05 bridge +----+ 09 +---+ 11 |
# | 02 | | | | | | |
# +---+----+ +---------------+---+ +---+----+ +---+----+
# | \ | | castle |
# | \ +--------+ +----+---+ +---+----+ +---+----+
# | \ |under- | |ledge | |wall | |court- |
# | \|ground +--+ 06 | | 10 +---+yard |
# | | 07 | | | | | | 12 |
# | +--------+ +--------+ +--------+ +---+----+
# | \ |
# ++---------+ \ +--------+ +--------+ +---+----+
# |intro | \ |cell | |trap/ |temple |
# o--+ 01 | \| 08 +----+ fall | | 13 |
# | | | | /| 15 | | |
# +----+-----+ +--------+ / +--+-+-+-+ +---+----+
# | / | | | |
# +----+-----+ +--------+/ +--+-+-+---------+----+
# |outro | |tomb | |antechamber |
# o--+ 17 +----------+ 16 | | 14 |
# | | | | | |
# +----------+ +--------+ +---------------------+
#
# There are a few ways you could build this layout; one is to do all the
# digging in one go first, then go back and add all the details. The
# advantage of this is that you the area is already there and you can
# more easily jump ahead in the build file to the detail work when you
# want to update things later. In this file we will however build and design
# it all in sequence; room by room. This makes it easier to keep an
# overview of what is going on in each room, tie things to parents etc.
# When building your own world you might want to separate your world into
# a lot more individual batch files (maybe one for just a few rooms) for
# easy handling. The numbers mark the order of construction and also
# the unique alias-ids given to each room, to allow safe teleporting
# and linking between them.
#
#------------------------------------------------------------
# Starting to build the tutorial
#
# This is simple welcome text introducing the tutorial.
#------------------------------------------------------------
#
# We start from limbo.
@tel #2
#
# Build the intro room (don't forget to also connect the outro room to this later)
#
# Note the unique id tut#XX we give each room.
#
@dig/teleport Intro : tutorial_world.rooms.IntroRoom = tutorial;tut;intro;tut#01
#
# ... and describe it.
#
@desc
{gWelcome to the Evennia tutorial!{n
The following tutorial consists of a small single-player quest area. The various rooms are designed to show off
some of the power and possibilities of the Evennia mud creation system. At any time during this tutorial
you can use the {wtutorial{n (or {wtut{n) command to get some background info about the room or certain objects
to see what is going on "behind the scenes".
To get into the mood of this miniature quest, imagine you are an adventurer out to find fame and
fortune. You have heard rumours of an old castle ruin by the coast. In its depth a warrior
princess was buried together with her powerful magical weapon - a valuable prize, if it's true.
Of course this is a chance to adventure that you cannot turn down!
You reach the coast in the midst of a raging thunderstorm. With wind and rain screaming in your
face you stand where the moor meet the sea along a high, rocky coast ...
{g(write 'start' or 'begin' to start the tutorial){n
#
# Show that the tutorial command works ...
#
@set here/tutorial_info =
This is the tutorial command. Use it in various rooms to see what's technically going on and
what you could try in each room. The intro room assigns some properties to your character, like a
simple "health" property used when fighting. Other rooms and puzzles might do the same. Leaving the
tutorial world through any of the normal exit rooms will clean away all such temporary properties.
#------------------------------------------------------------
#
# Outro room
#
# Called from the Intro room; this is a shortcut out. There
# is another outro room at the end showing more text.
# This is the only room we don't give a unique id.
#------------------------------------------------------------
#
@dig/teleport Leaving Tutorial:tutorial_world.rooms.OutroRoom = exit tutorial;exit;back, start again;start
#
@desc
You are quitting the Evennia tutorial prematurely! Come back later.
#
@open exit = #2
#
@set here/tutorial_info =
This outro room cleans up properties on the character that was set by the tutorial.
#
# Back to intro room so we can build from there.
#
start
#------------------------------------------------------------
#
# The cliff
#
#------------------------------------------------------------
#
# This regularly and randomly shows some weather effects. Note how we
# can spread the command's arguments over more than one line for easy
# reading. we also make sure to create plenty of aliases for the room
# and exits.
#
@dig/teleport Cliff by the sea;cliff;tut#02
: tutorial_world.rooms.WeatherRoom
= begin adventure;begin;start
#
# We define the tutorial message seen when the using the tutorial command
#
@set here/tutorial_info =
Weather room
This room inherits from a parent called WeatherRoom. This runs on a
timer that allows various weather-related messages to appear at
irregular intervals.
#
@desc
You stand on the high coast line overlooking a stormy sea far below. Around you the ground is covered
in low grey-green grass, pushed down by wind and rain. Inland, the vast dark moors begin, only here and
there covered in patches of low trees and brushes.
To the east, you glimpse the ragged outline of a castle ruin. It sits perched on a sheer cliff out into
the water, isolated from the shore. The only way to cross seems by way of an old hang bridge anchored
not far east from here.
# This is the well you will come back up from if you end up in the underground.
#
@create/drop Old well;well
#
@desc well =
The broken ruins of an old well sit some way off the path. The stones surrounding it have collapsed
and whereas there is still a chain hanging down it, it does not look very secure. It is probably
a remnant of some old settlement back in the day.
#
# It's important to lock the well object or players will be able to pick it up and
# put it in their pocket ...
#
@lock well = get:false()
#
# By setting the lock_msg attribute there will be a nicer error message if people
# try to pick up the well.
#
@set well/get_err_msg =
You probingly nudge the heavy stones of the well. There is no way you can ever
budge this on your own (besides, what would you do with it? Carry it around?).
#
@create/drop Wooden sign;sign : tutorial_world.objects.Readable
#
@desc sign =
The sign sits at the end of a small path leading out to the short-side anchor of the hang
bridge connecting the mainlaind with the ruin on its desolate cliff. The sign is not
as old as the rest of the scenery and the text on it is easily readable.
#
@lock sign = get:false()
#
@set sign/get_err_msg = The sign is securely anchored to the ground.
#
@set sign/readable_text =
WARNING - Bridge is not safe!
#
@set sign/tutorial_info =
This is a readable object, inheriting from a class objects.Readable. The sign has a cmdset with one command
defined on itself called 'read' that allows you to 'read sign'. Doing so returns the contents of
an attribute containing the information on the sign.
#
# Mood-setting objects to look at
#
@create/drop ruin (in the distance);castle;ruin
#
@desc ruin =
A fair bit out from the rocky shores you can make out the foggy outlines of a ruined castle. The once
mighty towers have crumbled and it presents a jagged shape against the rainy sky. The ruin is
perched on its own cliff, only connected to the mainland by means of an old hang bridge starting not
far east from you.
#
@lock ruin = get:false()
#
@set ruin/get_err_msg = Small as it appears from a distance, you still cannot reach over and pick up the
castle to put in your pocket.
#
@create/drop The sea (in the distance);sea;ocean
#
@desc sea =
The grey sea stretches as far as the eye can se to the east, and far below you its waves crash
against the foot of the cliff. The vast inland moors meets the ocean along a high and uninviting
coastline of ragged vertical stone.
Once this part of the world might have been beautiful, but now the eternal winds and storms have
washed it all down into a grey and barren wasteland.
#
@lock sea = get:false()
#
@set sea/get_err_msg = Noone gets the sea. It gets you.
#
# Set a climbable object for discovering a hidden exit
#
@create/drop gnarled old trees;tree;trees;gnarled : tutorial_world.objects.Climbable
#
@desc trees = Only the sturdiest of trees survive at the edge of the moor. A small group of huddling black things has
dug in near the cliff edge, eternally pummeled by wind and salt to become an integral part of the gloomy scenery.
#
@lock trees = get:false()
#
@set trees/get_err_msg = The group of old trees have withstood the eternal wind for hundreds of years.
You will not uproot them any time soon.
#
# The text to echo to player if trying 'climb tree'
#
@set tree/climb_text =
With some effort you climb one of the old trees.
The branches are wet and slippery but can easily carry your weight. From this high vantage point you can see far and wide.
In fact, you notice {Ya faint yellowish light{n not far to the north, beyond the trees. It looks like some sort of building. From this
angle you can make out a {wfaint footpath{n leading in that direction, all but impossible to make out from ground level.
You climb down again.
#------------------------------------------------------------
# Outside Evennia Inn
#------------------------------------------------------------
@dig Outside Evennia Inn;outside inn;tut#03:tutorial_world.rooms.WeatherRoom
= northern path;north;n;path,back to cliff;back;cliff;south;s
#
# Lock exit from view until we climbed that tree (which is when last_climbed get assigned).
@lock north = view:attr(last_climbed) ; traverse:attr(last_climbed)
# go to outide inn
north
#
@desc
You stand outside a one-story sturdy wooden building. Light flickers behind closed storm shutters. Over the
door a sign creaks in the wind - the writing says {cEvennia Inn{n and is surrounded by a painted image of some sort of snake.
From inside you hear the sound of laughter, singing and loud conversation.
#------------------------------------------------------------
#
# The Evennia Inn
#
#------------------------------------------------------------
@dig/teleport The Evennia Inn;evennia inn;inn;tut#04:tutorial_world.rooms.TutorialRoom = enter;in,leave;out
#
@desc The Evennia Inn consist mainly of one large room filled with tables. The bardisk extends
along the east wall, where multiple barrels and bottles line the shelves. The barkeep seems busy
handing out ale and chatting with the patrons, which are a rowdy and cheerful lot, keeping the sound
level only just below thunderous.
Soon you have a beer in hand and is chatting with the locals. Your eye fall on a {wbarrel{n in a corner with a few
old rusty weapons sticking out. There is a sign next to it: {wFree to take{n.
A patron tells you cheerfully that it's the leftovers from those foolish adventurers that challenged the old ruin before you ...
#
@set here/tutorial_info = Nothing special about this room, only a bonus place to go for chatting
with other players. Oh, and don't forget to grab a blade if you don't already have one (only three blades are available
in this location and don't get refilled until a player goes to the outro room, so the barrel might be empty - if it's any
comfort, the weapons in there won't help much against what is waiting in the ruin anyway ...)
#
@create/drop rusty old sword;rusty;sword;weapon : tutorial_world.objects.Weapon
#
@desc rusty = This is a rusty old broadsword. It has seen better days but the hilt is in good shape.
#
# Only allow to pick up if we don't already has something called weapon
@lock rusty = get:not holds(weapon)
#
@set rusty/get_err_msg = "Now, don't be greedy, friend! You already have a weapon."
#
@create/drop blunt axe;blunt;axe;weapon : tutorial_world.objects.Weapon
#
@desc axe = A heavy weapon, but the edge is dull and covered in rust and nicks.
#
@lock blunt = get:not holds(weapon)
#
@set blunt/get_err_msg = "Hey, you already have a weapon; no need for another!"
#
@create/drop patched spear;patched;spear;weapon : tutorial_world.objects.Weapon
#
@desc patched = The spear tip looks to be in decent condition, but the shaft was broken and is rather poorly mended.
#
@lock patched = get:not holds(weapon)
#
@set patched/get_err_msg = "You already have a weapon, friend, leave some for the next poor sod."
#------------------------------------------------------------
#
# The old bridge
#
#------------------------------------------------------------
#
# Back to cliff
@teleport tut#02
#
# The bridge uses parent rooms.BridgeRoom, which causes the player
# to take a longer time than expected to cross as they
# are pummeled by wind and a chance to fall off. This room should not have
# regular exits back to the cliff, that is handled by the bridge itself.
@dig The old bridge;bridge;tut#05
: tutorial_world.rooms.BridgeRoom
= old bridge;east;e;bridge;hangbridge
#
# put some descriptions on the exit to the bridge
#
@desc bridge = The hang-bridge's foundation sits at the edge of the
cliff to the east - two heavy stone pillars anchors the bridge on this side.
#
# go to the bridge
#
bridge
#
# Set up properties on bridge room (see BridgeRoom)
#
# connect west edge to cliff
#
@set here/west_exit = tut#02
#
# connect other end to gatehouse
#
@set here/east_exit = tut#09
#
# Fall location is the cliff ledge
#
@set here/fall_exit = tut#06
#
@set here/tutorial_info =
Bridge - state room
The bridge is a single room that uses a custom cmdset to overrule the movement
commands. This makes it take a few steps to cross it despite it being only one room.
The room also inherits from the weather room to cause the bridge to sway at regular
intervals. It also implements an timer and a random occurance at every step across
the bridge. It might be worth trying this passage a few times to see what may happen.
Hint: you can fall off!
#------------------------------------------------------------
#
# Ledge under the bridge
#
#------------------------------------------------------------
#
# You only end up at the ledge if you fall off the bridge. It
# has no direct connection to the bridge.
#
@dig/teleport Protruding ledge;cliffledge;ledge;tut#06
#
@set here/tutorial_info =
Ledge
This room is stored as an attribute on the bridge room and used a destination
should the player fall off the bridge. In our example the bridge is relatively
simple and always drops us to the same ledge; a more advanced implementation
might implement different locations to end up in depending on what happens
on the bridge.
#
@desc
You are on a narrow ledge protruding from the side of the cliff, about halfway down.
The air if saturated with salty sea water, sprays hitting your from the crashing
waves below.
The ledge is covered with a few black-grey brushes. Not far from you the cliff-face is
broken down to reveal a narrow natural opening into the cliffside.
#
@create/drop brushes;brush
#
@lock brush = get:false()
#
@desc brush =
The brushes covering the ledge are grey and dwarfed by constantly being pummeled by
salt, rain and wind.
#
@create/drop The sea (far below you);sea;ocean
#
@lock sea = get:false()
#
@desc sea =
Below you rages the grey sea, you can almost imagine that you feel the cliff
tremble under its onslaught.
#
@create/drop The hang bridge (above you);bridge;hangbridge;above
#
@lock bridge = get:false()
#
@desc bridge =
You can see the shape of the hang bridge a fair bit above you, partly obscured
by the rain. There is no way to get back up there from this ledge.
#
@set bridge/get_err_msg = You can't reach it, it's too far away.
#------------------------------------------------------------
#
# Underground passages
#
#------------------------------------------------------------
#
# The underground passages allow the player
# to get back up to the cliff again. If you look at the map,
# the cell also connects to here. We'll get to that
# later.
#
@dig Underground passages;passages;underground;tut#07 : tutorial_world.rooms.TutorialRoom = hole into cliff;hole;passage;cliff
#
@desc hole into cliff =
The hole is natural, the soft rock eroded by ages of sea water. The opening is small but
large enough for you to push through. It looks like it expands into a cavern
further in.
#
hole
#
@set here/tutorial_info =
This room acts a hub for getting the player back to the
start again, regardless of how you got here.
#
@desc
The underground cavern system you have entered seems to stretch on forever,
with criss-crossing paths and natural caverns probably carved by water. It is
not completely dark, here and there faint daylight sifts down from above - the
cliff is porous leaving channels of air and light up to the surface.
(some time later)
You eventually come upon a cavern with a black pool of stale water. In it sits a
murky bucket, the first remnant of any sort of intelligent life down here. The
bucket has disconnected from a chain hanging down from a circular opening high
above. Grey daylight simmers down the hole together with rain that ripples the black surface of the pool.
#
@create/drop pool;water
#
@lock pool = get:false()
#
@set pool/get_err_msg = You sift your hands through the black water without feeling any immediate bottom.
It's chilling cold and so dark you don't feel like taking a sip.
#
@desc pool =
The water of the pool is black and opaque. The rain coming down
from above does not seem to ripple the surface quite so much as
it should.
#
@create/drop hole (high above);hole;above
#
@lock hole = get:false()
#
@set hole/get_err_msg = You cannot reach it from here. You need to climb the chain.
#
@desc hole =
Whereas the lower edges of the hole seems jagged and natural you can faintly
make out that it turns into a man-made circular shafts higher up. It looks like
an old well.
#
# From the passages we get back up to the cliff, so we
# open up a new exit back there.
#
# connect chain to Cliff.
@open climb the chain;climb;chain = tut#02
#
@desc chain =
The chain is made of iron. It is rusty but you think it might still hold
your weight even after all this time. Better hope you don't need to do this more times ...
#------------------------------------------------------------
#
# The Dark Cell
#
#------------------------------------------------------------
#
@dig/teleport Dark cell;dark;cell;tut#08 : tutorial_world.rooms.DarkRoom
#
@set here/tutorial_info =
Dark room
The dark room implements a custom "dark" state. This is a very
restricted state that completely redefines the look command and only
allows limited interactions.
Looking around repeatedly will eventually produce
hints as to how to get out of the dark room.
#
# the description is only seen if the player finds a
# light source.
#
@desc
{YThe {yflickering light{Y of the torch reveals a small square cell. It does not seem you are
still in the castle, for the stone of the walls are chiseled crudely and drip with water and mold.
One wall holds a solid iron-cast door. While rusted and covered with lichen it seems very
sturdy still. In a corner lies what might have once been a bed or a bench but is now
nothing more than a pile or rotting splinters. One of the walls are covered with a thick
cover of black roots where they has broken through the cracks.{n
#
@create/drop iron-cast door;iron;door;iron-cast
#
@lock door = get:false()
#
@desc door =
The door is very solid and clad in iron. No matter how much you push at it, it won't
budge. It seems heavily bolted from the other side.
#
@create/drop stone walls;walls;stone
#
@lock stone = get:false()
#
@desc stone =
The walls are dripping with moisture and mold. A network of roots have burst through
the cracks on one side, bending the stones slightly aside. You feel a faint draft from
that direction.
#
# The crumbling wall is infact an advanced type of Exit, all we need to do is
# to supply it with a destination.
#
# Puzzle wall is an exit without a given destination at start
@create/drop root-covered wall;wall;roots;wines;root : tutorial_world.objects.CrumblingWall
#
# This destination is auto-assigned to the exit when its puzzle is solved
# connect the Underground passages
@set root-covered wall/destination = tut#07
#
@lock roots = get:false()
#
# (the crumbling wall describes itself, so we don't do it here)
#------------------------------------------------------------
#
# Castle Gate
# We are done with the underground, describe castle.
#------------------------------------------------------------
#
# We are done building the underground passages, let's
# head back up to ground level. We teleport to the bridge
# and continue from there.
#
# Back to the bridge
@teleport tut#05
#
# The bridge room should not have any normal exits from it, that is
# handled by the bridge itself.
#
@dig/teleport Ruined gatehouse;gatehouse;tut#09
: tutorial_world.rooms.TutorialRoom
= , Bridge over the abyss;bridge;abyss;west;w
#
@set here/tutorial_info =
This is part of a four-room area patrolled by a mob; the guardian
of the castle. The mob initiates combat if the player stays in the same room
for long enough.
Combat itself is a very simple affair which takes advantage of the strength
of the weapon you use, but dictates a fix skill for you and your enemy. The enemy
is quite powerful, so don't stick around too long ...
#
@desc
The old gatehouse is near collapse. Part of its northern
wall has already fallen down, together with parts of the fortifications in that direction.
Heavy stone pillars hold up sections of ceiling, but elsewhere the flagstones are exposed
to open sky. Part of a heavy portuculis, formerly blocking off the inner castle from attack,
is sprawled over the ground together with most of its frame.
{wEast{n the gatehouse leads out to a small open area surrounded by the remains of the castle.
There is also an archway standing offering passage to a path along the
old {wsouth{nern inner wall.
#
@create/drop fallen portoculis;portoculis;fall;fallen
#
@lock portoculis = get:false()
#
@desc portoculis =
This heavy iron grating used to block off the inner part of the gate house, now it has fallen
to the ground together with the stone archway that once help it up.
#
# We lock the bridge exit for the mob, so it don't wander out on the bridge
#
@lock bridge = traverse:not attr(is_mob)
#------------------------------------------------------------
#
# Along the southern inner wall (south from gatehouse)
#
#------------------------------------------------------------
@dig Along inner wall;inner wall;along;tut#10 : tutorial_world.rooms.WeatherRoom =
Standing archway;archway;south;s,ruined gatehouse;gatehouse;north;n
#
@desc standing archway =
It seems the archway leads off into a series of dimly lit rooms.
#
archway
#
@set here/tutorial_info =
This is part of a four-room area patrolled by a mob; the guardian
of the castle. The mob initiates combat if the player stays in the same room
for long enough.
Combat itself is a very simple affair which takes advantage of the strength
of the weapon you use, but dictates a fix skill for you and your enemy.
#
@desc
What appears at first sight to be a series of connected rooms actually
turns out to be collapsed buildings so mashed together by the ravashes of time
that they all seem to lean on each other and against the outer wall. The whole scene
is directly open to the sky.
The buildings make a half-circle along the main wall, here and there broken by falling
stone and rubble. At one end (the {wnorth{nern) of this half-circle is the entrance to the castle, the ruined
gatehoue. {wEast{nwards from here is some sort of open courtyard.
#------------------------------------------------------------
#
# Corner of castle (east from gatehouse)
#
#------------------------------------------------------------
# back to castle gate
@teleport tut#09
#
@dig/teleport Corner of castle ruins;corner;tut#11:tutorial_world.rooms.TutorialRoom = castle corner;corner;east;e,gatehouse;west;w
#
@desc The ruins opens up to the sky in a small open area, lined by collumns. The open area is
dominated by a huge stone obelisk in its center, an ancient ornament miraculously still standing.
Previously one could probably continue past the obelisk and eastward into the castle keep itself,
but that way is now completely blocked by falled rubble. To the {wwest{n is the gatehouse and
entrance to the castle, whereas {wsouth{nwards the collumns make way for a wide open courtyard.
#
@set here/tutorial_info =
This is part of a four-room area patrolled by a mob; the guardian
of the castle. The mob initiates combat if the player stays in the same room
for long enough.
Combat itself is a very simple affair which takes advantage of the strength
of the weapon you use, but dictates a fix skill for you and your enemy.
#
@create/drop obelisk:tutorial_world.objects.Obelisk
#
@lock obelisk = get:false()
#
@set obelisk/get_err_msg = It's way too heavy for anyone to move.
#
# (the obelisk describes itself, so we need no do it here)
#
# Create the mobile. This is the start location.
@create/drop Ghostly apparition;ghost;apparition;fog : tutorial_world.mob.Enemy
#
@set ghost/full_health = 20
#
@set ghost/defeat_location = dark cell
#
@lock ghost = get:false()
#
@set ghost/get_err_msg = Your fingers just pass straight through it!
#
@desc ghost =
This ghostly shape could momentarily be mistaken for a thick fog had it not moved with such determination and giving echoing
hollow screams as it did. The shape is hard to determine, now and then it seems to form limbs and even faces that fade
away only moments later. The thing reeks of almost tangible spite at your presence. This must be the ruin's eternal guardian.
#
# Give the enemy some random echoes (echoed at irregular intervals)
@set ghost/irregular_echoes =
[The foggy thing gives off a high-pitched shriek.,For a moment the fog wraps around a nearby pillar., The fog drifts lower to the ground as if looking for something., The fog momentarily takes on a reddish hue.,The fog temporarily fills most of the area as it changes shape.,You accidentally breathes in some of the fog - you start coughing from the cold moisture.]
#
# give the enemy a weapon
#
@create foggy tentacles;tentacles:tutorial_world.objects.Weapon
#
# Make the enemy good
#
@set foggy tentacles/hit = 0.7
#
@teleport/quiet tentacles = ghost
#
# Clear inactive mode and start the mob
#
@set ghost/inactive =
#------------------------------------------------------------
#
# The courtyard
#
#------------------------------------------------------------
#
@dig/teleport Overgrown courtyard;courtyard;tut#12 : tutorial_world.rooms.WeatherRoom = courtyard;south;s,castle corner;north;n
#
# Connect west to the inner wall
@open along inner wall;wall;along;west;w, overgrown courtyard;courtyard;east;e = tut#10
#
@set here/tutorial_info =
This is part of a four-room area patrolled by a mob; the guardian
of the castle. The mob initiates combat if the player stays in the same room
for long enough.
Combat itself is a very simple affair which takes advantage of the strength
of the weapon you use, but dictates a fix skill for you and your enemy.
#
@desc The inner courtyard of the old castle is littered with debris and overgrown
with low grass and patches of thorny wines. There is a collapsed structure close to
the gatehouse that looks like a stable.
{wNorth{nwards is a smaller area cornered in the debris, adorned with a
looming obelisk-like thing. To the {wwest{n the castle walls loom over a
mess of collapsed buildings. On the opposite, {weast{nern side of the yard is a
large building with a curved roof that seem to have withstood the test
of time better than many of those around it, it looks like some sort
of temple.
#
@create/drop old stables;stable;stables;building
#
@lock stable = get:false()
#
@desc stable =
The building is empty, if it was indeed once a stable it was abandoned long ago.
#------------------------------------------------------------
#
# The temple
#
#------------------------------------------------------------
#
@dig/teleport The ruined temple;temple;in;tut#13:tutorial_world.rooms.TutorialRoom = ruined temple;temple;east;e, overgrown courtyard;courtyard;outside;out;west;w
#
#
@desc
This building seems to have survived the ravages of time better than most
of the others. Its arched roof and wide spaces suggests that this is a temple
or church of some kind.
The wide hall of the temple stretches before you. At the far edge is a
stone altar with no clear markings. Despite its relatively good condition,
the temple is empty of all furniture or valuables, like it was looted or its
treasures moved ages ago.
Stairs lead down to the temple's dungeon on either side of the altar. A gaping
door opening shows the a wide courtyard to the west.
#------------------------------------------------------------
#
# Antechamber
#
#------------------------------------------------------------
#
@dig Antechamber;antechamber;tut#14
: tutorial_world.rooms.TutorialRoom
=
stairs down;stairs;down;d,
up the stairs to ruined temple;stairs;temple;up;u
#
@desc stairs down =
The stairs are worn by the age-old passage of feet.
#
# Lock the antechamber so the ghost cannot get in there.
@lock stairs down = traverse:not is_mob
#
stairs down
#
@desc
This chamber lies almost directly under the main altar of the temple. The
passage of aeons is felt here and you also sense you are close to great power.
The sides of the chamber are lined with stone archways, these are entrances to
the {wtombs{n of what must have been influental families or individual heroes of
the realm. Each is adorned by a stone statue or symbol of fine make. They do not seem to be
ordered in any particular order or rank.
#
@set here/tutorial_info =
This is the second part of a puzzle involving the Obelisk in another room. The
correct exit will vary depending on which scene was shown on the Obelisk surface.
Each tomb is a teleporter room and is keyed to a number corresponding to the scene
last shown on the obelisk (now stored on player). If the number doesn't match, the tomb
teleports to the Dark Cell. If correct, the tomb teleports to the Ancient Tomb treasure
chamber.
#
# We don't put unique ids on the individual tombs
#
@dig Blue bird tomb
: tutorial_world.rooms.TeleportRoom
= Tomb with stone bird;bird;blue;stone
#
@dig Tomb of woman on horse
: tutorial_world.rooms.TeleportRoom
= Tomb with statue of riding woman;horse;riding;
#
@dig Tomb of the crowned queen
: tutorial_world.rooms.TeleportRoom
= Tomb with statue of a crowned queen;crown;queen
#
@dig Tomb of the shield
: tutorial_world.rooms.TeleportRoom
= Tomb with shield of arms;shield
#
@dig Tomb of the hero
: tutorial_world.rooms.TeleportRoom
= Tomb depicting a heroine fighting a monster;knight;hero;monster;beast
#
@tel Blue bird tomb
#
@set here/puzzle_value = 0
#
@set here/failure_teleport_to = falling!
#
@set here/success_teleport_to = Ancient tomb
#
@teleport Tomb of woman on horse
#
@set here/puzzle_value = 1
#
@set here/failure_teleport_to = falling!
#
@set here/success_teleport_to = Ancient tomb
#
@teleport Tomb of the crowned queen
#
@set here/puzzle_value = 2
#
@set here/failure_teleport_to = falling!
#
@set here/success_teleport_to = Ancient tomb
#
@teleport Tomb of the shield
#
@set here/puzzle_value = 3
#
@set here/failure_teleport_to = falling!
#
@set here/success_teleport_to = Ancient tomb
#
@teleport Tomb of the hero
#
@set here/puzzle_value = 4
#
@set here/failure_teleport_to = falling!
#
@set here/success_teleport_to = Ancient tomb
#------------------------------------------------------------
#
# Falling room
#
# This is a transition between the trap and the cell room.
#------------------------------------------------------------
@dig/teleport Falling!;falling;tut#15: tutorial_world.rooms.TeleportRoom
#
@desc
The tomb is dark. You fumble your way through it. You think you can make out
a coffin in front of you in the gloom.
{rSuddenly you hear a distinct 'click' and the ground suddenly disappears under
your feet! You fall ... things go dark. {n
...
... You come to your senses. You lie down. On stone floor. You shakily
come to your feet. Somehow you suspect that you are not under the tomb
anymore, like you were magically snatched away.
The air is damp. Where are you?
#
@set here/success_teleport_to = dark cell
#
@set here/failure_teleport_to = dark cell
#
# back to antechamber
@tel tut#14
#------------------------------------------------------------
#
# The ancient tomb
#
#------------------------------------------------------------
# Create the real tomb
#
@dig/teleport Ancient tomb;tut#16
: tutorial_world.rooms.TutorialRoom = ,back to antechamber;antechamber;back
#
@desc
The tomb is dark. You fumble your way through it. You think you can make out
a coffin in front of you in the gloom.
The coffin comes into view. On and around it are marked symbols of hawks and
the face of a stern woman, clearly some sort of ancient hero.
#
@set here/tutorial_info =
Congratulations, you have reached the end of this little mini-quest. Just
grab the mythical weapon (get weapon) and the exit will open.
You can end the quest here or go back through the tutorial rooms to
explore further.
#
@create/drop Stone sarcophagus;sarcophagus;stone : tutorial_world.objects.WeaponRack
#
@desc stone = The lid of the coffin is adorned with a stone statue in full size. The weapon held by
the stone hands looks very realistic ...
#
@set sarcophagus/rack_id = rack_sarcophagus
#
@set sarcophagus/min_dmg = 4.0
#
@set sarcophagus/max_dmg = 11.0
#
@set sarcophagus/magic = True
#
@set sarcophagus/get_text =
The hands of the statue close on what seems to be a real weapon rather than one in stone.
This must be the hero's legendary weapon! The prize you have been looking for!
With trembling hands you release the weapon from the stone and hold {c%s{n in your hands!
{gThis concludes this tutorial. From here you can either continue to explore the castle (hint: this weapon
works better against the castle guardian than any you might have found earlier) or you can exit.{n
#------------------------------------------------------------
#
# Outro - end of the tutorial
#
#------------------------------------------------------------
#
@dig End of tutorial;end;tut#17 : tutorial_world.rooms.OutroRoom = Exit tutorial;exit;end
#
# All weapons from the rack gets an automatic alias the same as the rack_id. This we can
# use to check if any such weapon is in inventory before unlocking the exit.
#
@lock Exit tutorial: view:holds(rack_sarcophagus) ; traverse:holds(rack_sarcophagus)
#
# to tutorial outro
@tel tut#17
#
# this quits the tutorial and cleans up all variables that was .
@desc
{gThanks for trying out this little Evennia tutorial!
The examples given here are of course just scraping the surface of what
can be done. The tutorial focuses more on showing various techniques than any sort of
novel storytelling or challenging gameplay. The full README and source code for the
tutorial world can be found in {wcontrib/tutorial_world{g.
If you went through the tutorial quest once, it can be interesting to
do it again to explore the various possibilities and rooms you might not have come across yet,
maybe with the source code to one side. If you play as superuser (user #1) the mobile will
ignore you and teleport rooms etc will not affect you (this will also disable all locks, so
keep that in mind when checking functionality).{n
#
@set here/tutorial_info =
This room cleans up all temporary attributes that was put on the character during the tutorial.
#
# Tie this back to Limbo
#
@open exit back to Limbo;limbo;exit;back = #2
#
@tel 2

View file

@ -0,0 +1,347 @@
"""
This module implements a simple mobile object with
a very rudimentary AI as well as an aggressive enemy
object based on that mobile class.
"""
import random, time
from django.conf import settings
from src.objects.models import ObjectDB
from src.utils import utils
from game.gamesrc.scripts.basescript import Script
from contrib.tutorial_world import objects as tut_objects
from contrib.tutorial_world import scripts as tut_scripts
BASE_CHARACTER_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS
#------------------------------------------------------------
#
# Mob - mobile object
#
# This object utilizes exits and moves about randomly from
# room to room.
#
#------------------------------------------------------------
class Mob(tut_objects.TutorialObject):
"""
This type of mobile will roam from exit to exit at
random intervals. Simply lock exits against the is_mob attribute
to block them from the mob (lockstring = "traverse:not attr(is_mob)").
"""
def at_object_creation(self):
"This is called when the object is first created."
self.db.tutorial_info = "This is a moving object. It moves randomly from room to room."
self.scripts.add(tut_scripts.IrregularEvent)
# this is a good attribute for exits to look for, to block
# a mob from entering certain exits.
self.db.is_mob = True
self.db.last_location = None
# only when True will the mob move.
self.db.roam_mode = True
def announce_move_from(self, destination):
"Called just before moving"
self.location.msg_contents("With a cold breeze, %s drifts in the direction of %s." % (self.key, destination.key))
def announce_move_to_(self, source_location):
"Called just after arriving"
self.location.msg_contents("With a wailing sound, %s appears from the %s." % (self.key, source_location.key))
def update_irregular(self):
"Called at irregular intervals. Moves the mob."
if self.roam_mode:
exits = [ex for ex in self.location.exits if self.access(ex, "traverse")]
if exits:
# Try to make it so the mob doesn't backtrack.
new_exits = [ex for ex in exits if ex.destination != self.db.last_location]
if new_exits:
exits = new_exits
self.db.last_location = self.location
self.execute_cmd("%s" % exits[random.randint(0, len(exits) - 1)].key)
#------------------------------------------------------------
#
# Enemy - mobile attacking object
#
# An enemy is a mobile that is aggressive against players
# in its vicinity. An enemy will try to attack characters
# in the same location. It will also pursue enemies through
# exits if possible.
#
# An enemy needs to have a Weapon object in order to
# attack.
#
# This particular tutorial enemy is a ghostly apparition that can only
# be hurt by magical weapons. It will also not truly "die", but only
# teleport to another room. Players defeated by the apparition will
# conversely just be teleported to a holding room.
#
#------------------------------------------------------------
class AttackTimer(Script):
"""
This script is what makes an eneny "tick".
"""
def at_script_creation(self):
"This sets up the script"
self.key = "AttackTimer"
self.desc = "Drives an Enemy's combat."
self.interval = random.randint(10, 15) # how fast the Enemy acts
self.start_delay = True # wait self.interval before first call
self.persistent = True
def at_repeat(self):
"Called every self.interval seconds."
if self.obj.db.inactive:
return
if self.obj.db.roam_mode:
self.obj.roam()
elif self.obj.db.battle_mode:
self.obj.attack()
elif self.obj.db.pursue_mode:
self.obj.pursue()
else:
#dead mode. Wait for respawn.
dead_at = self.db.dead_at
if not dead_at:
self.db.dead_at = time.time()
if (time.time() - self.db.dead_at) > self.db.dead_timer:
self.obj.reset()
class Enemy(Mob):
"""
This is a ghostly enemy with health (hit points). Their chance to hit, damage etc is
determined by the weapon they are wielding, same as characters.
An enemy can be in four modes:
roam (inherited from Mob) - where it just moves around randomly
battle - where it stands in one place and attacks players
pursue - where it follows a player, trying to enter combat again
dead - passive and invisible until it is respawned
Upon creation, the following attributes describe the enemy's actions
desc - description
full_health - integer number > 0
defeat_location - unique name or #dbref to the location the player is taken when defeated. If not given, will remain in room.
defeat_text - text to show player when they are defeated (just before being whisped away to defeat_location)
defeat_text_room - text to show other players in room when a player is defeated
win_text - text to show player when defeating the enemy
win_text_room - text to show room when a player defeates the enemy
respawn_text - text to echo to room when the mob is reset/respawn in that room.
"""
def at_object_creation(self):
"Called at object creation."
super(Enemy, self).at_object_creation()
self.db.tutorial_info = "This moving object will attack players in the same room."
# state machine modes
self.db.roam_mode = True
self.db.battle_mode = False
self.db.pursue_mode = False
self.db.dead_mode = False
# health (change this at creation time)
self.db.full_health = 20
self.db.health = 20
self.db.dead_at = time.time()
self.db.dead_timer = 100 # how long to stay dead
self.db.inactive = True # this is used during creation to make sure the mob doesn't move away
# store the last player to hit
self.db.last_attacker = None
# where to take defeated enemies
self.db.defeat_location = "darkcell"
self.scripts.add(AttackTimer)
def update_irregular(self):
"the irregular event is inherited from Mob class"
strings = self.db.irregular_echoes
if strings:
self.location.msg_contents(strings[random.randint(0, len(strings) - 1)])
def roam(self):
"Called by Attack timer. Will move randomly as long as exits are open."
# in this mode, the mob is healed.
self.db.health = self.db.full_health
players = [obj for obj in self.location.contents
if utils.inherits_from(obj, BASE_CHARACTER_TYPECLASS) and not obj.is_superuser]
if players:
# we found players in the room. Attack.
self.roam_mode = False
self.db.battle_mode = True
self.attack()
elif random.random() < 0.2:
# no players to attack, move about randomly.
exits = [ex.destination for ex in self.location.exits if ex.access(self, "traverse")]
if exits:
# Try to make it so the mob doesn't backtrack.
new_exits = [ex for ex in exits if ex.destination != self.db.last_location]
if new_exits:
exits = new_exits
self.db.last_location = self.location
self.move_to(exits[random.randint(0, len(exits) - 1)])
else:
# no exits - a dead end room. Respawn back to start.
self.move_to(self.home)
def attack(self):
"""
This is the main mode of combat. It will try to hit players in
the location. If players are defeated, it will whisp them off
to the defeat location.
"""
last_attacker = self.db.last_attacker
players = [obj for obj in self.location.contents
if utils.inherits_from(obj, BASE_CHARACTER_TYPECLASS) and not obj.is_superuser]
if players:
# find a target
if last_attacker in players:
# prefer to attack the player last attacking.
target = last_attacker
else:
# otherwise attack a random player in location
target = players[random.randint(0, len(players) - 1)]
# try to use the weapon in hand
attack_cmds = ("thrust", "pierce", "stab", "slash", "chop")
cmd = attack_cmds[random.randint(0, len(attack_cmds) - 1)]
self.execute_cmd("%s %s" % (cmd, target))
# analyze result.
if target.db.health <= 0:
# we reduced enemy to 0 health. Whisp them off to the prison room.
tloc = ObjectDB.objects.object_search(self.db.defeat_location, global_search=True)
tstring = self.db.defeat_text
if not tstring:
tstring = "You feel your conciousness slip away ... you fall to the ground as "
tstring += "the misty apparition envelopes you ...\n The world goes black ...\n"
target.msg(tstring)
ostring = self.db.defeat_text_room
if tloc:
if not ostring:
ostring = "\n%s envelops the fallen ... and then their body is suddenly gone!" % self.key
# silently move the player to defeat location (we need to call hook manually)
target.location = tloc[0]
tloc[0].at_object_receive(target, self.location)
elif not ostring:
ostring = "%s falls to the ground!" % target.key
self.location.msg_contents(ostring, exclude=[target])
else:
# no players found, this could mean they have fled. Switch to pursue mode.
self.battle_mode = False
self.roam_mode = False
self.pursue_mode = True
def pursue(self):
"""
In pursue mode, the enemy tries to find players in adjoining rooms, preferably
those that previously attacked it.
"""
last_attacker = self.db.last_attacker
players = [obj for obj in self.location.contents if utils.inherits_from(obj, BASE_CHARACTER_TYPECLASS)]
if players:
# we found players in the room. Maybe we caught up with some, or some walked in on us
# before we had time to pursue them. Switch to battle mode.
self.battle_mode = True
self.roam_mode = False
self.pursue_mode = False
self.attack()
else:
# find all possible destinations.
destinations = [ex.destination for ex in self.location.exits if ex.access(self, "traverse")]
# find all players in the possible destinations. OBS-we cannot just use the player's
# current position to move the Enemy; this might have changed when the move is performed,
# causing the enemy to teleport out of bounds.
players = {}
for dest in destinations:
for obj in [o for o in dest.contents if utils.inherits_from(o, BASE_CHARACTER_TYPECLASS)]:
players[obj] = dest
if players:
# we found targets. Move to intercept.
if last_attacker in players:
# preferably the one that last attacked us
self.move_to(players[last_attacker])
else:
# otherwise randomly.
key = players.keys()[random.randint(0, len(players) - 1)]
self.move_to(players[key])
else:
# we found no players nearby. Return to roam mode.
self.battle_mode = False
self.roam_mode = True
self.pursue_mode = False
def at_hit(self, weapon, attacker, damage):
"""
Called when this object is hit by an enemy's weapon
Should return True if enemy is defeated, False otherwise.
In the case of players attacking, we handle all the events
and information from here, so the return value is not used.
"""
self.db.last_attacker = attacker
if not self.battle_mode:
# we were attacked, so switch to battle mode.
self.db.roam_mode = False
self.db.pursue_mode = False
self.db.battle_mode = True
#self.scripts.add(AttackTimer)
if not weapon.db.magic:
# In the tutorial, the enemy is a ghostly apparition, so
# only magical weapons can harm it.
string = self.db.weapon_ineffective_text
if not string:
string = "Your weapon just passes through your enemy, causing no effect!"
attacker.msg(string)
return
else:
# an actual hit
health = float(self.db.health)
health -= damage
self.db.health = health
if health <= 0:
string = self.db.win_text
if not string:
string = "After your last hit, %s folds in on itself, it seems to fade away into nothingness. " % self.key
string += "In a moment there is nothing left but the echoes of its screams. But you have a "
string += "feeling it is only temporarily weakened. "
string += "You fear it's only a matter of time before it materializes somewhere again."
attacker.msg(string)
string = self.db.win_text_room
if not string:
string = "After %s's last hit, %s folds in on itself, it seems to fade away into nothingness. " % (attacker.name, self.key)
string += "In a moment there is nothing left but the echoes of its screams. But you have a "
string += "feeling it is only temporarily weakened. "
string += "You fear it's only a matter of time before it materializes somewhere again."
self.location.msg_contents(string, exclude=[attacker])
# put enemy in dead mode and hide it from view. IrregularEvent(or a world reset) will bring it back later.
self.db.roam_mode = False
self.db.pursue_mode = False
self.db.battle_mode = False
self.db.dead_mode = True
self.db.dead_at = time.time()
self.location = None
return False
def reset(self):
"If the mob was 'dead', respawn it to its home position and reset all modes and damage."
if self.db.dead_mode:
self.db.health = self.db.full_health
self.db.roam_mode = True
self.db.pursue_mode = False
self.db.battle_mode = False
self.db.dead_mode = False
self.location = self.home
string = self.db.respawn_text
if not string:
string = "%s fades into existence from out of thin air. It's looking pissed." % self.key
self.location.msg_contents(string)

View file

@ -0,0 +1,902 @@
"""
TutorialWorld - basic objects - Griatch 2011
This module holds all "dead" object definitions for
the tutorial world. Object-commands and -cmdsets
are also defined here, together with the object.
Objects:
TutorialObject
Readable
Obelisk
LightSource
CrumblingWall
Weapon
"""
import time, random
from src.utils import utils, create
from game.gamesrc.objects.baseobjects import Object, Exit
from game.gamesrc.commands.basecommand import Command
from game.gamesrc.commands.basecmdset import CmdSet
from game.gamesrc.scripts.basescript import Script
#------------------------------------------------------------
#
# TutorialObject
#
# The TutorialObject is the base class for all items
# in the tutorial. They have an attribute "tutorial_info"
# on them that a global tutorial command can use to extract
# interesting behind-the scenes information about the object.
#
# TutorialObjects may also be "reset". What the reset means
# is up to the object. It can be the resetting of the world
# itself, or the removal of an inventory item from a
# character's inventory when leaving the tutorial, for example.
#
#------------------------------------------------------------
class TutorialObject(Object):
"""
This is the baseclass for all objects in the tutorial.
"""
def at_object_creation(self):
"Called when the object is first created."
super(TutorialObject, self).at_object_creation()
self.db.tutorial_info = "No tutorial info is available for this object."
#self.db.last_reset = time.time()
def reset(self):
"Resets the object, whatever that may mean."
self.location = self.home
#------------------------------------------------------------
#
# Readable - an object one can "read".
#
#------------------------------------------------------------
class CmdRead(Command):
"""
Usage:
read [obj]
Read some text.
"""
key = "read"
locks = "cmd:all()"
help_category = "TutorialWorld"
def func(self):
"Implement the read command."
if self.args:
obj = self.caller.search(self.args.strip())
else:
obj = self.obj
if not obj:
return
# we want an attribute read_text to be defined.
readtext = obj.db.readable_text
if readtext:
string = "You read {C%s{n:\n %s" % (obj.key, readtext)
else:
string = "There is nothing to read on %s." % obj.key
self.caller.msg(string)
class CmdSetReadable(CmdSet):
"CmdSet for readables"
def at_cmdset_creation(self):
"called when object is created."
self.add(CmdRead())
class Readable(TutorialObject):
"""
This object defines some attributes and defines a read method on itself.
"""
def at_object_creation(self):
"Called when object is created"
super(Readable, self).at_object_creation()
self.db.tutorial_info = "This is an object with a 'read' command defined in a command set on itself."
self.db.readable_text = "There is no text written on %s." % self.key
# define a command on the object.
self.cmdset.add_default(CmdSetReadable, permanent=True)
#------------------------------------------------------------
#
# Climbable object
#
# The climbable object works so that once climbed, it sets
# a flag on the climber to show that it was climbed. A simple
# command 'climb' handles the actual climbing.
#
#------------------------------------------------------------
class CmdClimb(Command):
"""
Usage:
climb <object>
"""
key = "climb"
locks = "cmd:all()"
help_category = "TutorialWorld"
def func(self):
"Implements function"
if not self.args:
self.caller.msg("What do you want to climb?")
return
obj = self.caller.search(self.args.strip())
if not obj:
return
if obj != self.obj:
self.caller.msg("Try as you might, you cannot climb that.")
return
ostring = self.obj.db.climb_text
if not ostring:
ostring = "You climb %s. Having looked around, you climb down again." % self.obj.name
self.caller.msg(ostring)
self.caller.db.last_climbed = self.obj
class CmdSetClimbable(CmdSet):
"Climbing cmdset"
def at_cmdset_creation(self):
"populate set"
self.add(CmdClimb())
class Climbable(TutorialObject):
"A climbable object."
def at_object_creation(self):
"Called at initial creation only"
self.cmdset.add_default(CmdSetClimbable, permanent=True)
#------------------------------------------------------------
#
# Obelisk - a unique item
#
# The Obelisk is an object with a modified return_appearance
# method that causes it to look slightly different every
# time one looks at it. Since what you actually see
# is a part of a game puzzle, the act of looking also
# stores a key attribute on the looking object for later
# reference.
#
#------------------------------------------------------------
OBELISK_DESCS = ["You can briefly make out the image of {ba woman with a blue bird{n.",
"You for a moment see the visage of {ba woman on a horse{n.",
"For the briefest moment you make out an engraving of {ba regal woman wearing a crown{n.",
"You think you can see the outline of {ba flaming shield{n in the stone.",
"The surface for a moment seems to portray {ba woman fighting a beast{n."]
class Obelisk(TutorialObject):
"""
This object changes its description randomly.
"""
def at_object_creation(self):
"Called when object is created."
super(Obelisk, self).at_object_creation()
self.db.tutorial_info = "This object changes its desc randomly, and makes sure to remember which one you saw."
# make sure this can never be picked up
self.locks.add("get:false()")
def return_appearance(self, caller):
"Overload the default version of this hook."
clueindex = random.randint(0, len(OBELISK_DESCS)-1)
# set this description
string = "The surface of the obelisk seem to waver, shift and writhe under your gaze, with "
string += "different scenes and structures appearing whenever you look at it. "
self.db.desc = string + OBELISK_DESCS[clueindex]
# remember that this was the clue we got.
caller.db.puzzle_clue = clueindex
# call the parent function as normal (this will use db.desc we just set)
return super(Obelisk, self).return_appearance(caller)
#------------------------------------------------------------
#
# LightSource
#
# This object that emits light and can be
# turned on or off. It must be carried to use and has only
# a limited burn-time.
# When burned out, it will remove itself from the carrying
# character's inventory.
#
#------------------------------------------------------------
class StateLightSourceOn(Script):
"""
This script controls how long the light source is burning. When
it runs out of fuel, the lightsource goes out.
"""
def at_script_creation(self):
"Called at creation of script."
self.key = "lightsourceBurn"
self.desc = "Keeps lightsources burning."
self.start_delay = True # only fire after self.interval s.
self.repeats = 1 # only run once.
self.persistent = True # survive a server reboot.
def at_start(self):
"Called at script start - this can also happen if server is restarted."
self.interval = self.obj.db.burntime
self.db.script_started = time.time()
def at_stop(self):
"""
Since this script stops after only 1 "repeat", we can use this hook
instead of at_repeat(). Since the user may also turn off the light
prematurely, this hook will also be called in that case.
"""
# calculate remaining burntime
time_burnt = time.time() - self.db.script_started
burntime = self.interval - time_burnt
self.obj.db.burntime = burntime
if burntime <= 0:
# no burntime left. Reset the object.
self.obj.reset()
def is_valid(self):
"This script is only valid as long as the lightsource burns."
return self.obj.db.is_active
class CmdLightSourceOn(Command):
"""
Switches on the lightsource.
"""
key = "on"
aliases = ["switch on", "turn on", "light"]
locks = "cmd:holds()" # only allow if command.obj is carried by caller.
help_category = "TutorialWorld"
def func(self):
"Implements the command"
if self.obj.db.is_active:
self.caller.msg("%s is already burning." % self.obj.key)
else:
# set lightsource to active
self.obj.db.is_active = True
# activate the script to track burn-time.
self.obj.scripts.add(StateLightSourceOn)
self.caller.msg("{gYou light {C%s.{n" % self.obj.key)
self.caller.location.msg_contents("%s lights %s!" % (self.caller, self.obj.key), exclude=[self.caller])
# we run script validation on the room to make light/dark states tick.
self.caller.location.scripts.validate()
# look around
self.caller.execute_cmd("look")
class CmdLightSourceOff(Command):
"""
Switch off the lightsource.
"""
key = "off"
aliases = ["switch off", "turn off", "dowse"]
locks = "cmd:holds()" # only allow if command.obj is carried by caller.
help_category = "TutorialWorld"
def func(self):
"Implements the command "
if not self.obj.db.is_active:
self.caller.msg("%s is not burning." % self.obj.key)
else:
# set lightsource to inactive
self.obj.db.is_active = False
# validating the scripts will kill it now that is_active=False.
self.obj.scripts.validate()
self.caller.msg("{GYou dowse {C%s.{n" % self.obj.key)
self.caller.location.msg_contents("%s dowses %s." % (self.caller, self.obj.key), exclude=[self.caller])
self.caller.location.scripts.validate()
self.caller.execute_cmd("look")
# we run script validation on the room to make light/dark states tick.
class CmdSetLightSource(CmdSet):
"CmdSet for the lightsource commands"
key = "lightsource_cmdset"
def at_cmdset_creation(self):
"called at cmdset creation"
self.add(CmdLightSourceOn())
self.add(CmdLightSourceOff())
class LightSource(TutorialObject):
"""
This implements a light source object.
When burned out, lightsource will be moved to its home - which by default is the
location it was first created at.
"""
def at_object_creation(self):
"Called when object is first created."
super(LightSource, self).at_object_creation()
self.db.tutorial_info = "This object can be turned on off and has a timed script controlling it."
self.db.is_active = False
self.db.burntime = 60 # 1 minute
self.db.desc = "A splinter of wood with remnants of resin on it, enough for burning."
# add commands
self.cmdset.add_default(CmdSetLightSource, permanent=True)
def reset(self):
"""
Can be called by tutorial world runner, or by the script when the lightsource
has burned out.
"""
if self.db.burntime <= 0:
# light burned out. Since the lightsources's "location" should be
# a character, notify them this way.
try:
loc = self.location.location
except AttributeError:
loc = self.location
loc.msg_contents("{c%s{n {Rburns out.{n" % self.key)
self.db.is_active = False
try:
# validate in holders current room, if possible
self.location.location.scripts.validate()
except AttributeError:
# maybe it was dropped, try validating at current location.
try:
self.location.scripts.validate()
except AttributeError,e:
pass
self.delete()
#------------------------------------------------------------
#
# Crumbling wall - unique exit
#
# This implements a simple puzzle exit that needs to be
# accessed with commands before one can get to traverse it.
#
# The puzzle is currently simply to move roots (that have
# presumably covered the wall) aside until a button for a
# secret door is revealed. The original position of the
# roots blocks the button, so they have to be moved to a certain
# position - when they have, the "press button" command
# is made available and the Exit is made traversable.
#
#------------------------------------------------------------
# There are four roots - two horizontal and two vertically
# running roots. Each can have three positions: top/middle/bottom
# and left/middle/right respectively. There can be any number of
# roots hanging through the middle position, but only one each
# along the sides. The goal is to make the center position clear.
# (yes, it's really as simple as it sounds, just move the roots
# to each side to "win". This is just a tutorial, remember?)
class CmdShiftRoot(Command):
"""
Shifts roots around.
shift blue root left/right
shift red root left/right
shift yellow root up/down
shift green root up/down
"""
key = "shift"
aliases = ["move"]
# the locattr() lock looks for the attribute is_dark on the current room.
locks = "cmd:not locattr(is_dark)"
help_category = "TutorialWorld"
def parse(self):
"custom parser; split input by spaces"
self.arglist = self.args.strip().split()
def func(self):
"""
Implement the command.
blue/red - vertical roots
yellow/green - horizontal roots
"""
if not self.arglist:
self.caller.msg("What do you want to move, and in what direction?")
return
if "root" in self.arglist:
self.arglist.remove("root")
# we accept arguments on the form <color> <direction>
if not len(self.arglist) > 1:
self.caller.msg("You must define which colour of root you want to move, and in which direction.")
return
color = self.arglist[0].lower()
direction = self.arglist[1].lower()
# get current root positions dict
root_pos = self.obj.db.root_pos
if not color in root_pos:
self.caller.msg("No such root to move.")
return
# first, vertical roots (red/blue) - can be moved left/right
if color == "red":
if direction == "left":
root_pos[color] = max(-1, root_pos[color] - 1)
self.caller.msg("You shift the reddish root to the left.")
if root_pos[color] != 0 and root_pos[color] == root_pos["blue"]:
root_pos["blue"] += 1
self.caller.msg("The root with blue flowers gets in the way and is pushed to the right.")
elif direction == "right":
root_pos[color] = min(1, root_pos[color] + 1)
self.caller.msg("You shove the reddish root to the right.")
if root_pos[color] != 0 and root_pos[color] == root_pos["blue"]:
root_pos["blue"] -= 1
self.caller.msg("The root with blue flowers gets in the way and is pushed to the left.")
else:
self.caller.msg("You cannot move the root in that direction.")
elif color == "blue":
if direction == "left":
root_pos[color] = max(-1, root_pos[color] - 1)
self.caller.msg("You shift the root with small blue flowers to the left.")
if root_pos[color] != 0 and root_pos[color] == root_pos["red"]:
root_pos["red"] += 1
self.caller.msg("The reddish root is to big to fit as well, so that one falls away to the left.")
elif direction == "right":
root_pos[color] = min(1, root_pos[color] + 1)
self.caller.msg("You shove the root adorned with small blue flowers to the right.")
if root_pos[color] != 0 and root_pos[color] == root_pos["red"]:
root_pos["red"] -= 1
self.caller.msg("The thick reddish root gets in the way and is pushed back to the left.")
else:
self.caller.msg("You cannot move the root in that direction.")
# now the horizontal roots (yellow/green). They can be moved up/down
elif color == "yellow":
if direction == "up":
root_pos[color] = max(-1, root_pos[color] - 1)
self.caller.msg("You shift the root with small yellow flowers upwards.")
if root_pos[color] != 0 and root_pos[color] == root_pos["green"]:
root_pos["green"] += 1
self.caller.msg("The green weedy root falls down.")
elif direction == "down":
root_pos[color] = min(1, root_pos[color] +1)
self.caller.msg("You shove the root adorned with small yellow flowers downwards.")
if root_pos[color] != 0 and root_pos[color] == root_pos["green"]:
root_pos["green"] -= 1
self.caller.msg("The weedy green root is shifted upwards to make room.")
else:
self.caller.msg("You cannot move the root in that direction.")
elif color == "green":
if direction == "up":
root_pos[color] = max(-1, root_pos[color] - 1)
self.caller.msg("You shift the weedy green root upwards.")
if root_pos[color] != 0 and root_pos[color] == root_pos["yellow"]:
root_pos["yellow"] += 1
self.caller.msg("The root with yellow flowers falls down.")
elif direction == "down":
root_pos[color] = min(1, root_pos[color] + 1)
self.caller.msg("You shove the weedy green root downwards.")
if root_pos[color] != 0 and root_pos[color] == root_pos["yellow"]:
root_pos["yellow"] -= 1
self.caller.msg("The root with yellow flowers gets in the way and is pushed upwards.")
else:
self.caller.msg("You cannot move the root in that direction.")
# store new position
self.obj.db.root_pos = root_pos
# check victory condition
if root_pos.values().count(0) == 0: # no roots in middle position
self.caller.db.crumbling_wall_found_button = True
self.caller.msg("Holding aside the root you think you notice something behind it ...")
class CmdPressButton(Command):
"""
Presses a button.
"""
key = "press"
aliases = ["press button", "button", "push", "push button"]
locks = "cmd:attr(crumbling_wall_found_button) and not locattr(is_dark)" # only accessible if the button was found and there is light.
help_category = "TutorialWorld"
def func(self):
"Implements the command"
if self.caller.db.crumbling_wall_found_exit:
# we already pushed the button
self.caller.msg("The button folded away when the secret passage opened. You cannot push it again.")
return
# pushing the button
string = "You move your fingers over the suspicious depression, then gives it a "
string += "decisive push. First nothing happens, then there is a rumble and a hidden "
string += "{wpassage{n opens, dust and pebbles rumbling as part of the wall moves aside."
# we are done - this will make the exit traversable!
self.caller.db.crumbling_wall_found_exit = True
# this will make it into a proper exit
eloc = self.caller.search(self.obj.db.destination, global_search=True)
if not eloc:
self.caller.msg("The exit leads nowhere, there's just more stone behind it ...")
return
self.obj.destination = eloc
self.caller.msg(string)
class CmdSetCrumblingWall(CmdSet):
"Group the commands for crumblingWall"
key = "crumblingwall_cmdset"
def at_cmdset_creation(self):
"called when object is first created."
self.add(CmdShiftRoot())
self.add(CmdPressButton())
class CrumblingWall(TutorialObject, Exit):
"""
The CrumblingWall can be examined in various
ways, but only if a lit light source is in the room. The traversal
itself is blocked by a traverse: lock on the exit that only
allows passage if a certain attribute is set on the trying
player.
Important attribute
destination - this property must be set to make this a valid exit
whenever the button is pushed (this hides it as an exit
until it actually is)
"""
def at_object_creation(self):
"called when the object is first created."
super(CrumblingWall, self).at_object_creation()
self.aliases = ["secret passage", "crack", "opening", "secret door"]
# this is assigned first when pushing button, so assign this at creation time!
self.db.destination = 2
# locks on the object directly transfer to the exit "command"
self.locks.add("cmd:not locattr(is_dark)")
self.db.tutorial_info = "This is an Exit with a conditional traverse-lock. Try to shift the roots around."
# the lock is important for this exit; we only allow passage if we "found exit".
self.locks.add("traverse:attr(crumbling_wall_found_exit)")
# set cmdset
self.cmdset.add(CmdSetCrumblingWall, permanent=True)
# starting root positions. H1/H2 are the horizontally hanging roots, V1/V2 the
# vertically hanging ones. Each can have three positions: (-1, 0, 1) where
# 0 means the middle position. yellow/green are horizontal roots and red/blue vertical.
# all may have value 0, but never any other identical value.
self.db.root_pos = {"yellow":0, "green":0, "red":0, "blue":0}
def _translate_position(self, root, ipos):
"Translates the position into words"
rootnames = {"red": "The {rreddish{n vertical-hanging root ",
"blue": "The thick vertical root with {bblue{n flowers ",
"yellow": "The thin horizontal-hanging root with {yyellow{n flowers ",
"green": "The weedy {ggreen{n horizontal root "}
vpos = {-1: "hangs far to the {wleft{n on the wall.",
0: "hangs straight down the {wmiddle{n of the wall.",
1: "hangs far to the {wright{n of the wall."}
hpos = {-1: "covers the {wupper{n part of the wall.",
0: "passes right over the {wmiddle{n of the wall.",
1: "nearly touches the floor, near the {wbottom{n of the wall."}
if root in ("yellow", "green"):
string = rootnames[root] + hpos[ipos]
else:
string = rootnames[root] + vpos[ipos]
return string
def return_appearance(self, caller):
"This is called when someone looks at the wall. We need to echo the current root positions."
if caller.db.crumbling_wall_found_button:
string = "Having moved all the roots aside, you find that the center of the wall, "
string += "previously hidden by the vegetation, hid a curious square depression. It was maybe once "
string += "concealed and made to look a part of the wall, but with the crumbling of stone around it,"
string += "it's now easily identifiable as some sort of button."
else:
string = "The wall is old and covered with roots that here and there have permeated the stone. "
string += "The roots (or whatever they are - some of them are covered in small non-descript flowers) "
string += "crisscross the wall, making it hard to clearly see its stony surface.\n"
for key, pos in self.db.root_pos.items():
string += "\n" + self._translate_position(key, pos)
self.db.desc = string
# call the parent to continue execution (will use desc we just set)
return super(CrumblingWall, self).return_appearance(caller)
def at_after_traverse(self, traverser, source_location):
"This is called after we traversed this exit. Cleans up and resets the puzzle."
del traverser.db.crumbling_wall_found_button
del traverser.db.crumbling_wall_found_exit
self.reset()
def at_failed_traverse(self, traverser):
"This is called if the player fails to pass the Exit."
traverser.msg("No matter how you try, you cannot force yourself through %s." % self.key)
def reset(self):
"Called by tutorial world runner, or whenever someone successfully traversed the Exit."
self.location.msg_contents("The secret door closes abruptly, roots falling back into place.")
for obj in self.location.contents:
# clear eventual puzzle-solved attribues on everyone that didn't get out in time. They
# have to try again.
del obj.db.crumbling_wall_found_exit
# Reset the roots with some random starting positions for the roots:
start_pos = [{"yellow":1, "green":0, "red":0, "blue":0},
{"yellow":0, "green":0, "red":0, "blue":0},
{"yellow":0, "green":1, "red":-1, "blue":0},
{"yellow":1, "green":0, "red":0, "blue":0},
{"yellow":0, "green":0, "red":0, "blue":1}]
self.db.root_pos = start_pos[random.randint(0, 4)]
self.destination = None
#------------------------------------------------------------
#
# Weapon - object type
#
# A weapon is necessary in order to fight in the tutorial
# world. A weapon (which here is assumed to be a bladed
# melee weapon for close combat) has three commands,
# stab, slash and defend. Weapons also have a property "magic"
# to determine if they are usable against certain enemies.
#
# Since Characters don't have special skills in the tutorial,
# we let the weapon itself determine how easy/hard it is
# to hit with it, and how much damage it can do.
#
#------------------------------------------------------------
class CmdAttack(Command):
"""
Attack the enemy. Commands:
stab <enemy>
slash <enemy>
parry
stab - (thrust) makes a lot of damage but is harder to hit with.
slash - is easier to land, but does not make as much damage.
parry - forgoes your attack but will make you harder to hit on next enemy attack.
"""
# this is an example of implementing many commands as a single command class,
# using the given command alias to separate between them.
key = "attack"
aliases = ["hit","kill", "fight", "thrust", "pierce", "stab", "slash", "chop", "parry", "defend"]
locks = "cmd:all()"
help_category = "TutorialWorld"
def func(self):
"Implements the stab"
cmdstring = self.cmdstring
if cmdstring in ("attack", "fight"):
string = "How do you want to fight? Choose one of 'stab', 'slash' or 'defend'."
self.caller.msg(string)
return
# parry mode
if cmdstring in ("parry", "defend"):
string = "You raise your weapon in a defensive pose, ready to block the next enemy attack."
self.caller.msg(string)
self.caller.db.combat_parry_mode = True
self.caller.location.msg_contents("%s takes a defensive stance" % self.caller, exclude=[self.caller])
return
if not self.args:
self.caller.msg("Who do you attack?")
return
target = self.caller.search(self.args.strip())
if not target:
return
string = ""
tstring = ""
ostring = ""
if cmdstring in ("thrust", "pierce", "stab"):
hit = float(self.obj.db.hit) * 0.7 # modified due to stab
damage = self.obj.db.damage * 2 # modified due to stab
string = "You stab with %s. " % self.obj.key
tstring = "%s stabs at you with %s. " % (self.caller.key, self.obj.key)
ostring = "%s stabs at %s with %s. " % (self.caller.key, target.key, self.obj.key)
self.caller.db.combat_parry_mode = False
elif cmdstring in ("slash", "chop"):
hit = float(self.obj.db.hit) # un modified due to slash
damage = self.obj.db.damage # un modified due to slash
string = "You slash with %s. " % self.obj.key
tstring = "%s slash at you with %s. " % (self.caller.key, self.obj.key)
ostring = "%s slash at %s with %s. " % (self.caller.key, target.key, self.obj.key)
self.caller.db.combat_parry_mode = False
else:
self.caller.msg("You fumble with your weapon, unable to choose an appropriate action...")
self.caller.location.msg_contents("%s fumbles with their weapon." % self.obj.key)
self.caller.db.combat_parry_mode = False
return
if target.db.combat_parry_mode:
# target is defensive; even harder to hit!
hit *= 0.5
if random.random() <= hit:
self.caller.msg(string + "{gIt's a hit!{n")
target.msg(tstring + "{rIt's a hit!{n")
self.caller.location.msg_contents(ostring + "It's a hit!", exclude=[target,self.caller])
# call enemy hook
if hasattr(target, "at_hit"):
# should return True if target is defeated, False otherwise.
return target.at_hit(self.obj, self.caller, damage)
elif target.db.health:
target.db.health -= damage
if target.db.health <= 0:
# enemy is down!
return True
else:
return False
else:
# sorry, impossible to fight this enemy ...
self.caller.msg("The enemy seems unaffacted.")
return False
else:
self.caller.msg(string + "{rYou miss.{n")
target.msg(tstring + "{gThey miss you.{n")
self.caller.location.msg_contents(ostring + "They miss.", exclude=[target, self.caller])
class CmdSetWeapon(CmdSet):
"Holds the attack command."
def at_cmdset_creation(self):
"called at first object creation."
self.add(CmdAttack())
class Weapon(TutorialObject):
"""
This defines a bladed weapon.
Important attributes (set at creation):
hit - chance to hit (0-1)
parry - chance to parry (0-1)
damage - base damage given (modified by hit success and type of attack) (0-10)
"""
def at_object_creation(self):
"Called at first creation of the object"
super(Weapon, self).at_object_creation()
self.db.hit = 0.4 # hit chance
self.db.parry = 0.8 # parry chance
self.damage = 8.0
self.magic = False
self.cmdset.add_default(CmdSetWeapon, permanent=True)
def reset(self):
"When reset, the weapon is simply deleted, unless it has a place to return to."
if self.location.has_player and self.home == self.location:
self.location.msg_contents("%s suddenly and magically fades into nothingness, as if it was never there ..." % self.key)
self.delete()
else:
self.location = self.home
#------------------------------------------------------------
#
# Weapon rack - spawns weapons
#
#------------------------------------------------------------
class CmdGetWeapon(Command):
"""
Usage:
get weapon
This will try to obtain a weapon from the container.
"""
key = "get"
aliases = "get weapon"
locks = "cmd:all()"
help_cateogory = "TutorialWorld"
def func(self):
"Implement the command"
rack_id = self.obj.db.rack_id
if eval("self.caller.db.%s" % rack_id):
# we don't allow to take more than one weapon from rack.
self.caller.msg("%s has no more to offer." % self.obj.name)
else:
dmg, name, aliases, desc, magic = self.obj.randomize_type()
new_weapon = create.create_object(Weapon, key=name, aliases=aliases,location=self.caller)
new_weapon.db.rack_id = rack_id
new_weapon.db.damage = dmg
new_weapon.db.desc = desc
new_weapon.db.magic = magic
ostring = self.obj.db.get_text
if not ostring:
ostring = "You pick up %s."
if '%s' in ostring:
self.caller.msg(ostring % name)
else:
self.caller.msg(ostring)
# tag the caller so they cannot keep taking objects from the rack.
exec("self.caller.db.%s = True" % rack_id)
class CmdSetWeaponRack(CmdSet):
"group the rack cmd"
key = "weaponrack_cmdset"
mergemode = "Replace"
def at_cmdset_creation(self):
self.add(CmdGetWeapon())
class WeaponRack(TutorialObject):
"""
This will spawn a new weapon for the player unless the player already has one from this rack.
attribute to set at creation:
min_dmg - the minimum damage of objects from this rack
max_dmg - the maximum damage of objects from this rack
magic - if weapons should be magical (have the magic flag set)
get_text - the echo text to return when getting the weapon. Give '%s' to include the name of the weapon.
"""
def at_object_creation(self):
"called at creation"
self.cmdset.add_default(CmdSetWeaponRack, permanent=True)
self.rack_id = "weaponrack_1"
self.db.min_dmg = 1.0
self.db.max_dmg = 4.0
self.db.magic = False
def randomize_type(self):
"""
this returns a random weapon
"""
min_dmg = float(self.db.min_dmg)
max_dmg = float(self.db.max_dmg)
magic = bool(self.db.magic)
dmg = min_dmg + random.random()*(max_dmg - min_dmg)
aliases = [self.db.rack_id, "weapon"]
if dmg < 1.5:
name = "Knife"
desc = "A rusty kitchen knife. Better than nothing."
elif dmg < 2.0:
name = "Rusty dagger"
desc = "A double-edged dagger with nicked edge. It has a wooden handle."
elif dmg < 3.0:
name = "Sword"
desc = "A rusty shortsword. It has leather wrapped around the handle."
elif dmg < 4.0:
name = "Club"
desc = "A heavy wooden club with some rusty spikes in it."
elif dmg < 5.0:
name = "Ornate Longsword"
aliases.extend(["longsword","ornate"])
desc = "A fine longsword."
elif dmg < 6.0:
name = "Runeaxe"
aliases.extend(["rune","axe"])
desc = "A single-bladed axe, heavy but yet easy to use."
elif dmg < 7.0:
name = "Broadsword named Thruning"
aliases.extend(["thruning","broadsword"])
desc = "This heavy bladed weapon is marked with the name 'Thruning'. It is very powerful in skilled hands."
elif dmg < 8.0:
name = "Silver Warhammer"
aliases.append("warhammer")
desc = "A heavy war hammer with silver ornaments. This huge weapon causes massive damage."
elif dmg < 9.0:
name = "Slayer Waraxe"
aliases.extend(["waraxe","slayer"])
desc = "A huge double-bladed axe marked with the runes for 'Slayer'. It has more runic inscriptions on its head, which you cannot decipher."
elif dmg < 10.0:
name = "The Ghostblade"
aliases.append("ghostblade")
desc = "This massive sword is large as you are tall. Its metal shine with a bluish glow."
else:
name = "The Hawkblade"
aliases.append("hawkblade")
desc = "White surges of magical power runs up and down this runic blade. The hawks depicted on its hilt almost seems to have a life of their own."
if dmg < 9 and magic:
desc += "\nThe metal seems to glow faintly, as if imbued with more power than what is immediately apparent."
return dmg, name, aliases, desc, magic

View file

@ -0,0 +1,677 @@
"""
Room Typeclasses for the TutorialWorld.
"""
import random
from src.commands.cmdset import CmdSet
from src.utils import create, utils
from src.objects.models import ObjectDB
from game.gamesrc.scripts.basescript import Script
from game.gamesrc.commands.basecommand import Command
from game.gamesrc.objects.baseobjects import Room
from contrib.tutorial_world import scripts as tut_scripts
from contrib.tutorial_world.objects import LightSource, TutorialObject
#------------------------------------------------------------
#
# Tutorial room - parent room class
#
# This room is the parent of all rooms in the tutorial.
# It defines a tutorial command on itself (available to
# all who is in a tutorial room).
#
#------------------------------------------------------------
class CmdTutorial(Command):
"""
Get help during the tutorial
Usage:
tutorial [obj]
This command allows you to get behind-the-scenes info
about an object or the current location.
"""
key = "tutorial"
aliases = ["tut"]
locks = "cmd:all()"
help_category = "TutorialWorld"
def func(self):
"""
All we do is to scan the current location for an attribute
called `tutorial_info` and display that.
"""
caller = self.caller
if not self.args:
target = self.obj # this is the room object the command is defined on
else:
target = caller.search(self.args.strip())
if not target:
return
helptext = target.db.tutorial_info
if helptext:
caller.msg("{G%s{n" % helptext)
else:
caller.msg("{RSorry, there is no tutorial help available here.{n")
class TutorialRoomCmdSet(CmdSet):
"Implements the simple tutorial cmdset"
key = "tutorial_cmdset"
def at_cmdset_creation(self):
"add the tutorial cmd"
self.add(CmdTutorial())
class TutorialRoom(Room):
"""
This is the base room type for all rooms in the tutorial world.
It defines a cmdset on itself for reading tutorial info about the location.
"""
def at_object_creation(self):
"Called when room is first created"
self.db.tutorial_info = "This is a tutorial room. It allows you to use the 'tutorial' command."
self.cmdset.add_default(TutorialRoomCmdSet)
def reset(self):
"Can be called by the tutorial runner."
pass
#------------------------------------------------------------
#
# Weather room - scripted room
#
# The weather room is called by a script at
# irregular intervals. The script is generally useful
# and so is split out into tutorialworld.scripts.
#
#------------------------------------------------------------
class WeatherRoom(TutorialRoom):
"""
This should probably better be called a rainy room...
This sets up an outdoor room typeclass. At irregular intervals,
the effects of weather will show in the room. Outdoor rooms should
inherit from this.
"""
def at_object_creation(self):
"Called when object is first created."
super(WeatherRoom, self).at_object_creation()
# we use the imported IrregularEvent script
self.scripts.add(tut_scripts.IrregularEvent)
self.db.tutorial_info = \
"This room has a Script running that has it echo a weather-related message at irregular intervals."
def update_irregular(self):
"create a tuple of possible texts to return."
strings = (
"The rain coming down from the iron-grey sky intensifies.",
"A gush of wind throws the rain right in your face. Despite your cloak you shiver.",
"The rainfall eases a bit and the sky momentarily brightens.",
"For a moment it looks like the rain is slowing, then it begins anew with renewed force.",
"The rain pummels you with large, heavy drops. You hear the rumble of thunder in the distance.",
"The wind is picking up, howling around you, throwing water droplets in your face. It's cold.",
"Bright fingers of lightning flash over the sky, moments later followed by a deafening rumble.",
"It rains so hard you can hardly see your hand in front of you. You'll soon be drenched to the bone.",
"Lightning strikes in several thundering bolts, striking the trees in the forest to your west.",
"You hear the distant howl of what sounds like some sort of dog or wolf.",
"Large clouds rush across the sky, throwing their load of rain over the world.")
# get a random value so we can select one of the strings above. Send this to the room.
irand = random.randint(0, 15)
if irand > 10:
return # don't return anything, to add more randomness
self.msg_contents("{w%s{n" % strings[irand])
#-----------------------------------------------------------------------------------
#
# Dark Room - a scripted room
#
# This room limits the movemenets of its denizens unless they carry a and active
# LightSource object (LightSource is defined in tutorialworld.objects.LightSource)
#
#-----------------------------------------------------------------------------------
class CmdLookDark(Command):
"""
Look around in darkness
Usage:
look
Looks in darkness
"""
key = "look"
aliases = ["l", 'feel', 'feel around', 'fiddle']
locks = "cmd:all()"
help_category = "TutorialWorld"
def func(self):
"Implement the command."
caller = self.caller
# we don't have light, grasp around blindly.
messages = ("It's pitch black. You fumble around but cannot find anything.",
"You don't see a thing. You feel around, managing to bump your fingers hard against something. Ouch!",
"You don't see a thing! Blindly grasping the air around you, you find nothing.",
"It's totally dark here. You almost stumble over some un-evenness in the ground.",
"You are completely blind. For a moment you think you hear someone breathing nearby ... \n ... surely you must be mistaken.",
"Blind, you think you find some sort of object on the ground, but it turns out to be just a stone.",
"Blind, you bump into a wall. The wall seems to be covered with some sort of vegetation, but its too damp to burn.",
"You can't see anything, but the air is damp. It feels like you are far underground.")
irand = random.randint(0, 10)
if irand < len(messages):
caller.msg(messages[irand])
else:
# check so we don't already carry a lightsource.
carried_lights = [obj for obj in caller.contents if utils.inherits_from(obj, LightSource)]
if carried_lights:
string = "You don't want to stumble around in blindness anymore. You already found what you need. Let's get light already!"
caller.msg(string)
return
#if we are lucky, we find the light source.
lightsources = [obj for obj in self.obj.contents if utils.inherits_from(obj, LightSource)]
if lightsources:
lightsource = lightsources[0]
else:
# create the light source from scratch.
lightsource = create.create_object(LightSource, key="torch")
lightsource.location = caller
string = "Your fingers bump against a piece of wood in a corner. Smelling it you sense the faint smell of tar. A {c%s{n!"
string += "\nYou pick it up, holding it firmly. Now you just need to {wlight{n it using the flint and steel you carry with you."
caller.msg(string % lightsource.key)
class CmdDarkHelp(Command):
"""
Help command for the dark state.
"""
key = "help"
locks = "cmd:all()"
help_category = "TutorialWorld"
def func(self):
"Implements the help command."
string = "Can't help you until you find some light! Try feeling around for something to burn."
string += " You cannot give up even if you don't find anything right away."
self.caller.msg(string)
# the nomatch system command will give a suitable error when we cannot find the normal commands.
from src.commands.default.syscommands import CMD_NOMATCH
class CmdDarkNoMatch(Command):
"This is called when there is no match"
key = CMD_NOMATCH
locks = "cmd:all()"
def func(self):
"Implements the command."
self.caller.msg("Until you find some light, there's not much you can do. Try feeling around.")
class DarkCmdSet(CmdSet):
"Groups the commands."
key = "darkroom_cmdset"
mergetype = "Replace" # completely remove all other commands
def at_cmdset_creation(self):
"populates the cmdset."
self.add(CmdTutorial())
self.add(CmdLookDark())
self.add(CmdDarkHelp())
self.add(CmdDarkNoMatch())
#
# Darkness room two-state system
#
class DarkState(Script):
"""
The darkness state is a script that keeps tabs on when
a player in the room carries an active light source. It places
a new, very restrictive cmdset (DarkCmdSet) on all the players
in the room whenever there is no light in it. Upon turning on
a light, the state switches off and moves to LightState.
"""
def at_script_creation(self):
"This setups the script"
self.key = "tutorial_darkness_state"
self.desc = "A dark room"
self.persistent = True
def at_start(self):
"called when the script is first starting up."
for char in [char for char in self.obj.contents if char.has_player]:
if char.is_superuser:
char.msg("You are Superuser, so you are not affected by the dark state.")
else:
char.cmdset.add(DarkCmdSet)
char.msg("The room is pitch dark! You are likely to be eaten by a Grue.")
def is_valid(self):
"is valid only as long as noone in the room has lit the lantern."
return not self.obj.is_lit()
def at_stop(self):
"Someone turned on a light. This state dies. Switch to LightState."
for char in [char for char in self.obj.contents if char.has_player]:
char.cmdset.delete(DarkCmdSet)
self.obj.db.is_dark = False
self.obj.scripts.add(LightState)
class LightState(Script):
"""
This is the counterpart to the Darkness state. It is active when the lantern is on.
"""
def at_script_creation(self):
"Called when script is first created."
self.key = "tutorial_light_state"
self.desc = "A room lit up"
self.persistent = True
def is_valid(self):
"This state is only valid as long as there is an active light source in the room."
return self.obj.is_lit()
def at_stop(self):
"Light disappears. This state dies. Return to DarknessState."
self.obj.db.is_dark = True
self.obj.scripts.add(DarkState)
class DarkRoom(TutorialRoom):
"""
A dark room. This tries to start the DarkState script on all
objects entering. The script is responsible for making sure it is
valid (that is, that there is no light source shining in the room).
"""
def is_lit(self):
"""
Helper method to check if the room is lit up. It checks all
characters in room to see if they carry an active object of
type LightSource.
"""
return any([any([True for obj in char.contents
if utils.inherits_from(obj, LightSource) and obj.is_active])
for char in self.contents if char.has_player])
def at_object_creation(self):
"Called when object is first created."
super(DarkRoom, self).at_object_creation()
self.db.tutorial_info = "This is a room with custom command sets on itself."
# this variable is set by the scripts. It makes for an easy flag to look for
# by other game elements (such as the crumbling wall in the tutorial)
self.db.is_dark = True
# the room starts dark.
self.scripts.add(DarkState)
def at_object_receive(self, character, source_location):
"Called when an object enters the room. We crank the wheels to make sure scripts are synced."
if character.has_player:
if not self.is_lit() and not character.is_superuser:
character.cmdset.add(DarkCmdSet)
if character.db.health and character.db.health <= 0:
# heal character coming here from being defeated by mob.
health = character.db.health_max
if not health:
health = 20
character.db.health = health
self.scripts.validate()
def at_object_leave(self, character, target_location):
"In case people leave with the light, we make sure to update the states accordingly."
character.cmdset.delete(DarkCmdSet) # in case we are teleported away
self.scripts.validate()
#------------------------------------------------------------
#
# Teleport room - puzzle room
#
# This is a sort of puzzle room that requires a certain
# attribute on the entering character to be the same as
# an attribute of the room. If not, the character will
# be teleported away to a target location. This is used
# by the Obelisk - grave chamber puzzle, where one must
# have looked at the obelisk to get an attribute set on
# oneself, and then pick the grave chamber with the
# matching imagery for this attribute.
#
#------------------------------------------------------------
class TeleportRoom(TutorialRoom):
"""
Teleporter - puzzle room.
Important attributes (set at creation):
puzzle_key - which attr to look for on character
puzzle_value - what char.db.puzzle_key must be set to
teleport_to - where to teleport to in case of failure to match
"""
def at_object_creation(self):
"Called at first creation"
super(TeleportRoom, self).at_object_creation()
# what character.db.puzzle_clue must be set to, to avoid teleportation.
self.db.puzzle_value = 1
# the target of the success teleportation. Can be a dbref or a unique room name.
self.db.success_teleport_to = "treasure room"
# the target of the failure teleportation.
self.db.failure_teleport_to = "dark cell"
def at_object_receive(self, character, source_location):
"This hook is called by the engine whenever the player is moved into this room."
if not character.has_player or character.is_superuser:
# only act on player characters.
return
#print character.db.puzzle_clue, self.db.puzzle_value
if character.db.puzzle_clue != self.db.puzzle_value:
# we didn't pass the puzzle. See if we can teleport.
teleport_to = self.db.failure_teleport_to # this is a room name
else:
# passed the puzzle
teleport_to = self.db.success_teleport_to # this is a room name
results = ObjectDB.objects.object_search(teleport_to, global_search=True)
if not results or len(results) > 1:
# we cannot move anywhere since no valid target was found.
print "no valid teleport target for %s was found." % teleport_to
return
if character.player.is_superuser:
# superusers don't get teleported
character.msg("Superuser block: You would have been teleported to %s." % results[0])
return
# teleport
character.execute_cmd("look")
character.location = results[0] # stealth move
character.location.at_object_receive(character, self)
#------------------------------------------------------------
#
# Bridge - unique room
#
# Defines a special west-eastward "bridge"-room, a large room it takes
# several steps to cross. It is complete with custom commands and a
# chance of falling off the bridge. This room has no regular exits,
# instead the exiting are handled by custom commands set on the player
# upon first entering the room.
#
# Since one can enter the bridge room from both ends, it is
# divided into five steps:
# westroom <- 0 1 2 3 4 -> eastroom
#
#------------------------------------------------------------
class CmdEast(Command):
"""
Try to cross the bridge eastwards.
"""
key = "east"
aliases = ["e"]
locks = "cmd:all()"
help_category = "TutorialWorld"
def func(self):
"move forward"
caller = self.caller
bridge_step = min(5, caller.db.tutorial_bridge_position + 1)
if bridge_step > 4:
# we have reached the far east end of the bridge. Move to the east room.
eexit = ObjectDB.objects.object_search(self.obj.db.east_exit)
if eexit:
caller.move_to(eexit[0])
else:
caller.msg("No east exit was found for this room. Contact an admin.")
return
caller.db.tutorial_bridge_position = bridge_step
caller.execute_cmd("look")
# go back across the bridge
class CmdWest(Command):
"""
Go back across the bridge westwards.
"""
key = "west"
aliases = ["w"]
locks = "cmd:all()"
help_category = "TutorialWorld"
def func(self):
"move forward"
caller = self.caller
bridge_step = max(-1, caller.db.tutorial_bridge_position - 1)
if bridge_step < 0:
# we have reached the far west end of the bridge. Move to the west room.
wexit = ObjectDB.objects.object_search(self.obj.db.west_exit)
if wexit:
caller.move_to(wexit[0])
else:
caller.msg("No west exit was found for this room. Contact an admin.")
return
caller.db.tutorial_bridge_position = bridge_step
caller.execute_cmd("look")
class CmdLookBridge(Command):
"""
looks around at the bridge.
"""
key = 'look'
aliases = ["l"]
locks = "cmd:all()"
help_category = "TutorialWorld"
def func(self):
"Looking around, including a chance to fall."
bridge_position = self.caller.db.tutorial_bridge_position
messages =("You are standing {wvery close to the the bridge's western foundation{n. If you go west you will be back on solid ground ...",
"The bridge slopes precariously where it extends eastwards towards the lowest point - the center point of the hang bridge.",
"You are {whalfways{n out on the unstable bridge.",
"The bridge slopes precariously where it extends westwards towards the lowest point - the center point of the hang bridge.",
"You are standing {wvery close to the bridge's eastern foundation{n. If you go east you will be back on solid ground ...")
moods = ("The bridge sways in the wind.", "The hanging bridge creaks dangerously.",
"You clasp the ropes firmly as the bridge sways and creaks under you.",
"From the castle you hear a distant howling sound, like that of a large dog or other beast.",
"The bridge creaks under your feet. Those planks does not seem very sturdy.",
"Far below you the ocean roars and throws its waves against the cliff, as if trying its best to reach you.",
"Parts of the bridge come loose behind you, falling into the chasm far below!",
"A gust of wind causes the bridge to sway precariously.",
"Under your feet a plank comes loose, tumbling down. For a moment you dangle over the abyss ...",
"The section of rope you hold onto crumble in your hands, parts of it breaking apart. You sway trying to regain balance.")
message = "{c%s{n\n" % self.obj.key + messages[bridge_position] + "\n" + moods[random.randint(0, len(moods) - 1)]
self.caller.msg(message)
# there is a chance that we fall if we are on the western or central part of the bridge.
if bridge_position < 3 and random.random() < 0.2 and not self.caller.is_superuser:
# we fall on 20% of the times.
fexit = ObjectDB.objects.object_search(self.obj.db.fall_exit)
if fexit:
string = "\n Suddenly the plank you stand on gives way under your feet! You fall!"
string += "\n You try to grab hold of an adjoining plank, but all you manage to do is to "
string += "divert your fall westwards, towards the cliff face. This is going to hurt ... "
string += "\n ... The world goes dark ...\n"
# note that we move silently so as to not call look hooks (this is a little trick to leave
# the player with the "world goes dark ..." message, giving them ample time to read it. They
# have to manually call look to find out their new location). Thus we also call the
# at_object_leave hook manually (otherwise this is done by move_to()).
self.caller.msg("{r%s{n" % string)
self.obj.at_object_leave(self.caller, fexit)
self.caller.location = fexit[0] # stealth move, without any other hook calls.
# custom help command
class CmdBridgeHelp(Command):
"""
Overwritten help command
"""
key = "help"
aliases = ["h"]
locks = "cmd:all()"
help_category = "Tutorial world"
def func(self):
"Implements the command."
string = "You are trying hard not to fall off the bridge ..."
string += "\n\nWhat you can do is trying to cross the bridge {weast{n "
string += "or try to get back to the mainland {wwest{n)."
self.caller.msg(string)
class BridgeCmdSet(CmdSet):
"This groups the bridge commands. We will store it on the room."
key = "Bridge commands"
priority = 1 # this gives it precedence over the normal look/help commands.
def at_cmdset_creation(self):
self.add(CmdTutorial())
self.add(CmdEast())
self.add(CmdWest())
self.add(CmdLookBridge())
self.add(CmdBridgeHelp())
class BridgeRoom(TutorialRoom):
"""
The bridge room implements an unsafe bridge. It also enters the player into a
state where they get new commands so as to try to cross the bridge.
We want this to result in the player getting a special set of
commands related to crossing the bridge. The result is that it will take several
steps to cross it, despite it being represented by only a single room.
We divide the bridge into steps:
self.db.west_exit - - | - - self.db.east_exit
0 1 2 3 4
The position is handled by a variabled stored on the player when entering and giving
special move commands will increase/decrease the counter until the bridge is crossed.
"""
def at_object_creation(self):
"Setups the room"
super(BridgeRoom, self).at_object_creation()
# at irregular intervals, this will call self.update_irregular()
self.scripts.add(tut_scripts.IrregularEvent)
# this identifies the exits from the room (should be the command
# needed to leave through that exit). These are defaults, but you
# could of course also change them after the room has been created.
self.db.west_exit = "cliff"
self.db.east_exit = "gate"
self.db.fall_exit = "cliffledge"
# add the cmdset on the room.
self.cmdset.add_default(BridgeCmdSet)
self.db.tutorial_info = \
"""The bridge seem large but is actually only a single room that assigns custom west/east commands."""
def update_irregular(self):
"""
This is called at irregular intervals and makes the passage
over the bridge a little more interesting.
"""
strings = (
"The rain intensifies, making the planks of the bridge even more slippery.",
"A gush of wind throws the rain right in your face.",
"The rainfall eases a bit and the sky momentarily brightens.",
"The bridge shakes under the thunder of a closeby thunder strike.",
"The rain pummels you with large, heavy drops. You hear the distinct howl of a large hound in the distance.",
"The wind is picking up, howling around you and causing the bridge to sway from side to side.",
"Some sort of large bird sweeps by overhead, giving off an eery screech. Soon it has disappeared in the gloom.",
"The bridge sways from side to side in the wind.")
self.msg_contents("{w%s{n" % strings[random.randint(0, 7)])
def at_object_receive(self, character, source_location):
"""
This hook is called by the engine whenever the player is moved
into this room.
"""
if character.has_player:
# we only run this if the entered object is indeed a player object.
# check so our east/west exits are correctly defined.
wexit = ObjectDB.objects.object_search(self.db.west_exit)
eexit = ObjectDB.objects.object_search(self.db.east_exit)
fexit = ObjectDB.objects.object_search(self.db.fall_exit)
if not wexit or not eexit or not fexit:
character.msg("The bridge's exits are not properly configured. Contact an admin. Forcing west-end placement.")
character.db.tutorial_bridge_position = 0
return
if source_location == eexit[0]:
character.db.tutorial_bridge_position = 4
else:
character.db.tutorial_bridge_position = 0
def at_object_leave(self, character, target_location):
"""
This is triggered when the player leaves the bridge room.
"""
if character.has_player:
# clean up the position attribute
del character.db.tutorial_bridge_position
#-----------------------------------------------------------
#
# Intro Room - unique room
#
# This room marks the start of the tutorial. It sets up properties on the player char
# that is needed for the tutorial.
#
#------------------------------------------------------------
class IntroRoom(TutorialRoom):
"""
Intro room
properties to customize:
char_health - integer > 0 (default 20)
"""
def at_object_receive(self, character, source_location):
"""
Assign properties on characters
"""
# setup
health = self.db.char_health
if not health:
health = 20
if character.has_player:
character.db.health = health
character.db.health_max = health
if character.is_superuser:
string = "-"*78
string += "\nWARNING: YOU ARE PLAYING AS A SUPERUSER (%s). TO EXPLORE NORMALLY YOU NEED " % character.key
string += "\nTO CREATE AND LOG IN AS A REGULAR USER INSTEAD. IF YOU CONTINUE, KNOW THAT "
string += "\nMANY FUNCTIONS AND PUZZLES WILL IGNORE THE PRESENCE OF A SUPERUSER.\n"
string += "-"*78
character.msg("{r%s{n" % string)
#------------------------------------------------------------
#
# Outro room - unique room
#
# Cleans up the character from all tutorial-related properties.
#
#------------------------------------------------------------
class OutroRoom(TutorialRoom):
"""
Outro room.
"""
def at_object_receive(self, character, source_location):
"""
Do cleanup.
"""
if character.has_player:
del character.db.health
del character.db.has_climbed
del character.db.puzzle_clue
del character.db.combat_parry_mode
del character.db.tutorial_bridge_position
for tut_obj in [obj for obj in character.contents if utils.inherits_from(obj, TutorialObject)]:
tut_obj.reset()

View file

@ -0,0 +1,109 @@
"""
This defines some generally useful scripts for the tutorial world.
"""
import random
from game.gamesrc.scripts.basescript import Script
#------------------------------------------------------------
#
# IrregularEvent - script firing at random intervals
#
# This is a generally useful script for updating
# objects at irregular intervals. This is used by as diverse
# entities as Weather rooms and mobs.
#
#
#
#------------------------------------------------------------
class IrregularEvent(Script):
"""
This script, which should be tied to a particular object upon
instantiation, calls update_irregular on the object at random
intervals.
"""
def at_script_creation(self):
"This setups the script"
self.key = "update_irregular"
self.desc = "Updates at irregular intervals"
self.interval = random.randint(30, 70) # interval to call.
self.start_delay = True # wait at least self.interval seconds before calling at_repeat the first time
self.persistent = True
# this attribute determines how likely it is the
# 'update_irregular' method gets called on self.obj (value is
# 0.0-1.0 with 1.0 meaning it being called every time.)
self.db.random_chance = 0.2
def at_repeat(self):
"This gets called every self.interval seconds."
rand = random.random()
if rand <= self.db.random_chance:
try:
self.obj.update_irregular()
except Exception:
pass
class FastIrregularEvent(IrregularEvent):
"A faster updating irregular event"
def at_script_creation(self):
super(FastIrregularEvent, self).at_script_creation()
self.interval = 5 # every 5 seconds, 1/5 chance of firing
#------------------------------------------------------------
#
# Tutorial world Runner - root reset timer for TutorialWorld
#
# This is a runner that resets the world
#
#------------------------------------------------------------
# #
# # This sets up a reset system -- it resets the entire tutorial_world domain
# # and all objects inheriting from it back to an initial state, MORPG style. This is useful in order for
# # different players to explore it without finding things missing.
# #
# # Note that this will of course allow a single player to end up with multiple versions of objects if
# # they just wait around between resets; In a real game environment this would have to be resolved e.g.
# # with custom versions of the 'get' command not accepting doublets.
# #
# # setting up an event for reseting the world.
# UPDATE_INTERVAL = 60 * 10 # Measured in seconds
# #This is a list of script parent objects that subscribe to the reset functionality.
# RESET_SUBSCRIBERS = ["examples.tutorial_world.p_weapon_rack",
# "examples.tutorial_world.p_mob"]
# class EventResetTutorialWorld(Script):
# """
# This calls the reset function on all subscribed objects
# """
# def __init__(self):
# super(EventResetTutorialWorld, self).__init__()
# self.name = 'reset_tutorial_world'
# #this you see when running @ps in game:
# self.description = 'Reset the tutorial world .'
# self.interval = UPDATE_INTERVAL
# self.persistent = True
# def event_function(self):
# """
# This is called every self.interval seconds.
# """
# #find all objects inheriting the subscribing parents
# for parent in RESET_SUBSCRIBERS:
# objects = Object.objects.global_object_script_parent_search(parent)
# for obj in objects:
# try:
# obj.scriptlink.reset()
# except:
# logger.log_errmsg(traceback.print_exc())