Initial commit: Timmy Academy Evennia world
- 21 rooms across 4 wings (Dormitories, Commons, Workshops, Gardens) - Full exit graph connecting all rooms bidirectionally - Room descriptions for all 16 inner rooms - 5 character accounts (wizard, Allegro, Allegro-Primus, Timmy, Ezra) - Public communication channel - Build script: world/build_academy.ev - Wing modules: world/dormitory_entrance.py, commons_wing.py, workshop_wing.py, gardens_wing.py Built by Allegro, descriptions and exit fixes by Timmy.
This commit is contained in:
56
.gitignore
vendored
Normal file
56
.gitignore
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
*.py[cod]
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Packages
|
||||||
|
*.egg
|
||||||
|
*.egg-info
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
eggs
|
||||||
|
parts
|
||||||
|
var
|
||||||
|
sdist
|
||||||
|
develop-eggs
|
||||||
|
.installed.cfg
|
||||||
|
lib
|
||||||
|
lib64
|
||||||
|
__pycache__
|
||||||
|
|
||||||
|
# Other
|
||||||
|
*.swp
|
||||||
|
*.log
|
||||||
|
*.log.*
|
||||||
|
*.pid
|
||||||
|
*.restart
|
||||||
|
*.db3
|
||||||
|
|
||||||
|
# Installation-specific.
|
||||||
|
# For group efforts, comment out some or all of these.
|
||||||
|
server/conf/secret_settings.py
|
||||||
|
server/logs/*.log.*
|
||||||
|
server/.static/*
|
||||||
|
server/.media/*
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
.coverage
|
||||||
|
.tox
|
||||||
|
nosetests.xml
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
|
||||||
|
# Mr Developer
|
||||||
|
.mr.developer.cfg
|
||||||
|
.project
|
||||||
|
.pydevproject
|
||||||
|
|
||||||
|
# PyCharm config
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# VSCode config
|
||||||
|
.vscode
|
||||||
40
README.md
Normal file
40
README.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Welcome to Evennia!
|
||||||
|
|
||||||
|
This is your game directory, set up to let you start with
|
||||||
|
your new game right away. An overview of this directory is found here:
|
||||||
|
https://github.com/evennia/evennia/wiki/Directory-Overview#the-game-directory
|
||||||
|
|
||||||
|
You can delete this readme file when you've read it and you can
|
||||||
|
re-arrange things in this game-directory to suit your own sense of
|
||||||
|
organisation (the only exception is the directory structure of the
|
||||||
|
`server/` directory, which Evennia expects). If you change the structure
|
||||||
|
you must however also edit/add to your settings file to tell Evennia
|
||||||
|
where to look for things.
|
||||||
|
|
||||||
|
Your game's main configuration file is found in
|
||||||
|
`server/conf/settings.py` (but you don't need to change it to get
|
||||||
|
started). If you just created this directory (which means you'll already
|
||||||
|
have a `virtualenv` running if you followed the default instructions),
|
||||||
|
`cd` to this directory then initialize a new database using
|
||||||
|
|
||||||
|
evennia migrate
|
||||||
|
|
||||||
|
To start the server, stand in this directory and run
|
||||||
|
|
||||||
|
evennia start
|
||||||
|
|
||||||
|
This will start the server, logging output to the console. Make
|
||||||
|
sure to create a superuser when asked. By default you can now connect
|
||||||
|
to your new game using a MUD client on `localhost`, port `4000`. You can
|
||||||
|
also log into the web client by pointing a browser to
|
||||||
|
`http://localhost:4001`.
|
||||||
|
|
||||||
|
# Getting started
|
||||||
|
|
||||||
|
From here on you might want to look at one of the beginner tutorials:
|
||||||
|
http://github.com/evennia/evennia/wiki/Tutorials.
|
||||||
|
|
||||||
|
Evennia's documentation is here:
|
||||||
|
https://github.com/evennia/evennia/wiki.
|
||||||
|
|
||||||
|
Enjoy!
|
||||||
14
commands/README.md
Normal file
14
commands/README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# commands/
|
||||||
|
|
||||||
|
This folder holds modules for implementing one's own commands and
|
||||||
|
command sets. All the modules' classes are essentially empty and just
|
||||||
|
imports the default implementations from Evennia; so adding anything
|
||||||
|
to them will start overloading the defaults.
|
||||||
|
|
||||||
|
You can change the organisation of this directory as you see fit, just
|
||||||
|
remember that if you change any of the default command set classes'
|
||||||
|
locations, you need to add the appropriate paths to
|
||||||
|
`server/conf/settings.py` so that Evennia knows where to find them.
|
||||||
|
Also remember that if you create new sub directories you must put
|
||||||
|
(optionally empty) `__init__.py` files in there so that Python can
|
||||||
|
find your modules.
|
||||||
0
commands/__init__.py
Normal file
0
commands/__init__.py
Normal file
254
commands/command.py
Normal file
254
commands/command.py
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
"""
|
||||||
|
Commands
|
||||||
|
|
||||||
|
Commands describe the input the account can do to the game.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from evennia.commands.command import Command as BaseCommand
|
||||||
|
from evennia import default_cmds
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CmdExamine(BaseCommand):
|
||||||
|
"""
|
||||||
|
Examine an object, character, or detail in the room.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
examine [<object>]
|
||||||
|
ex [<object>]
|
||||||
|
|
||||||
|
If no object is given, examines the current room.
|
||||||
|
"""
|
||||||
|
key = "examine"
|
||||||
|
aliases = ["ex", "exam"]
|
||||||
|
locks = "cmd:all()"
|
||||||
|
help_category = "General"
|
||||||
|
|
||||||
|
def func(self):
|
||||||
|
"""Handle the examination"""
|
||||||
|
if not self.args:
|
||||||
|
# Examine the room itself
|
||||||
|
self.caller.msg(f"|c{self.caller.location.key}|n")
|
||||||
|
self.caller.msg(f"{self.caller.location.db.desc}")
|
||||||
|
# Show contents
|
||||||
|
contents = [obj.key for obj in self.caller.location.contents if obj != self.caller]
|
||||||
|
if contents:
|
||||||
|
self.caller.msg(f"\n|wYou see:|n {', '.join(contents)}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Search for the object
|
||||||
|
target = self.caller.search(self.args.strip())
|
||||||
|
if not target:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Show examination
|
||||||
|
self.caller.msg(f"|c{target.key}|n")
|
||||||
|
if hasattr(target, 'db') and target.db.desc:
|
||||||
|
self.caller.msg(f"{target.db.desc}")
|
||||||
|
else:
|
||||||
|
self.caller.msg("You see nothing special.")
|
||||||
|
|
||||||
|
# Show type
|
||||||
|
self.caller.msg(f"\n|wType:|n {target.db_typeclass_path.split('.')[-1] if target.db_typeclass_path else 'Unknown'}")
|
||||||
|
|
||||||
|
|
||||||
|
class CmdRooms(BaseCommand):
|
||||||
|
"""
|
||||||
|
List available rooms in the game.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
rooms
|
||||||
|
"""
|
||||||
|
key = "rooms"
|
||||||
|
locks = "cmd:all()"
|
||||||
|
help_category = "General"
|
||||||
|
|
||||||
|
def func(self):
|
||||||
|
"""List rooms"""
|
||||||
|
from evennia.objects.models import ObjectDB
|
||||||
|
|
||||||
|
rooms = ObjectDB.objects.filter(db_typeclass_path__contains='Room')
|
||||||
|
self.caller.msg("|cAvailable Rooms:|n")
|
||||||
|
for room in rooms[:20]: # Limit to 20
|
||||||
|
self.caller.msg(f" {room.db_key}")
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
"""
|
||||||
|
Base command (you may see this if a child command had no help text defined)
|
||||||
|
|
||||||
|
Note that the class's `__doc__` string is used by Evennia to create the
|
||||||
|
automatic help entry for the command, so make sure to document consistently
|
||||||
|
here. Without setting one, the parent's docstring will show (like now).
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Each Command class implements the following methods, called in this order
|
||||||
|
# (only func() is actually required):
|
||||||
|
#
|
||||||
|
# - at_pre_cmd(): If this returns anything truthy, execution is aborted.
|
||||||
|
# - parse(): Should perform any extra parsing needed on self.args
|
||||||
|
# and store the result on self.
|
||||||
|
# - func(): Performs the actual work.
|
||||||
|
# - at_post_cmd(): Extra actions, often things done after
|
||||||
|
# every command, like prompts.
|
||||||
|
#
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# The default commands inherit from
|
||||||
|
#
|
||||||
|
# evennia.commands.default.muxcommand.MuxCommand.
|
||||||
|
#
|
||||||
|
# If you want to make sweeping changes to default commands you can
|
||||||
|
# uncomment this copy of the MuxCommand parent and add
|
||||||
|
#
|
||||||
|
# COMMAND_DEFAULT_CLASS = "commands.command.MuxCommand"
|
||||||
|
#
|
||||||
|
# to your settings file. Be warned that the default commands expect
|
||||||
|
# the functionality implemented in the parse() method, so be
|
||||||
|
# careful with what you change.
|
||||||
|
#
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
|
||||||
|
# from evennia.utils import utils
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# class MuxCommand(Command):
|
||||||
|
# """
|
||||||
|
# This sets up the basis for a MUX command. The idea
|
||||||
|
# is that most other Mux-related commands should just
|
||||||
|
# inherit from this and don't have to implement much
|
||||||
|
# parsing of their own unless they do something particularly
|
||||||
|
# advanced.
|
||||||
|
#
|
||||||
|
# Note that the class's __doc__ string (this text) is
|
||||||
|
# used by Evennia to create the automatic help entry for
|
||||||
|
# the command, so make sure to document consistently here.
|
||||||
|
# """
|
||||||
|
# def has_perm(self, srcobj):
|
||||||
|
# """
|
||||||
|
# This is called by the cmdhandler to determine
|
||||||
|
# if srcobj is allowed to execute this command.
|
||||||
|
# We just show it here for completeness - we
|
||||||
|
# are satisfied using the default check in Command.
|
||||||
|
# """
|
||||||
|
# return super().has_perm(srcobj)
|
||||||
|
#
|
||||||
|
# def at_pre_cmd(self):
|
||||||
|
# """
|
||||||
|
# This hook is called before self.parse() on all commands
|
||||||
|
# """
|
||||||
|
# pass
|
||||||
|
#
|
||||||
|
# def at_post_cmd(self):
|
||||||
|
# """
|
||||||
|
# This hook is called after the command has finished executing
|
||||||
|
# (after self.func()).
|
||||||
|
# """
|
||||||
|
# pass
|
||||||
|
#
|
||||||
|
# def parse(self):
|
||||||
|
# """
|
||||||
|
# This method is called by the cmdhandler once the command name
|
||||||
|
# has been identified. It creates a new set of member variables
|
||||||
|
# that can be later accessed from self.func() (see below)
|
||||||
|
#
|
||||||
|
# The following variables are available for our use when entering this
|
||||||
|
# method (from the command definition, and assigned on the fly by the
|
||||||
|
# cmdhandler):
|
||||||
|
# self.key - the name of this command ('look')
|
||||||
|
# self.aliases - the aliases of this cmd ('l')
|
||||||
|
# self.permissions - permission string for this command
|
||||||
|
# self.help_category - overall category of command
|
||||||
|
#
|
||||||
|
# self.caller - the object calling this command
|
||||||
|
# self.cmdstring - the actual command name used to call this
|
||||||
|
# (this allows you to know which alias was used,
|
||||||
|
# for example)
|
||||||
|
# self.args - the raw input; everything following self.cmdstring.
|
||||||
|
# self.cmdset - the cmdset from which this command was picked. Not
|
||||||
|
# often used (useful for commands like 'help' or to
|
||||||
|
# list all available commands etc)
|
||||||
|
# self.obj - the object on which this command was defined. It is often
|
||||||
|
# the same as self.caller.
|
||||||
|
#
|
||||||
|
# A MUX command has the following possible syntax:
|
||||||
|
#
|
||||||
|
# name[ with several words][/switch[/switch..]] arg1[,arg2,...] [[=|,] arg[,..]]
|
||||||
|
#
|
||||||
|
# The 'name[ with several words]' part is already dealt with by the
|
||||||
|
# cmdhandler at this point, and stored in self.cmdname (we don't use
|
||||||
|
# it here). The rest of the command is stored in self.args, which can
|
||||||
|
# start with the switch indicator /.
|
||||||
|
#
|
||||||
|
# This parser breaks self.args into its constituents and stores them in the
|
||||||
|
# following variables:
|
||||||
|
# self.switches = [list of /switches (without the /)]
|
||||||
|
# self.raw = This is the raw argument input, including switches
|
||||||
|
# self.args = This is re-defined to be everything *except* the switches
|
||||||
|
# self.lhs = Everything to the left of = (lhs:'left-hand side'). If
|
||||||
|
# no = is found, this is identical to self.args.
|
||||||
|
# self.rhs: Everything to the right of = (rhs:'right-hand side').
|
||||||
|
# If no '=' is found, this is None.
|
||||||
|
# self.lhslist - [self.lhs split into a list by comma]
|
||||||
|
# self.rhslist - [list of self.rhs split into a list by comma]
|
||||||
|
# self.arglist = [list of space-separated args (stripped, including '=' if it exists)]
|
||||||
|
#
|
||||||
|
# All args and list members are stripped of excess whitespace around the
|
||||||
|
# strings, but case is preserved.
|
||||||
|
# """
|
||||||
|
# raw = self.args
|
||||||
|
# args = raw.strip()
|
||||||
|
#
|
||||||
|
# # split out switches
|
||||||
|
# switches = []
|
||||||
|
# if args and len(args) > 1 and args[0] == "/":
|
||||||
|
# # we have a switch, or a set of switches. These end with a space.
|
||||||
|
# switches = args[1:].split(None, 1)
|
||||||
|
# if len(switches) > 1:
|
||||||
|
# switches, args = switches
|
||||||
|
# switches = switches.split('/')
|
||||||
|
# else:
|
||||||
|
# args = ""
|
||||||
|
# switches = switches[0].split('/')
|
||||||
|
# arglist = [arg.strip() for arg in args.split()]
|
||||||
|
#
|
||||||
|
# # check for arg1, arg2, ... = argA, argB, ... constructs
|
||||||
|
# lhs, rhs = args, None
|
||||||
|
# lhslist, rhslist = [arg.strip() for arg in args.split(',')], []
|
||||||
|
# if args and '=' in args:
|
||||||
|
# lhs, rhs = [arg.strip() for arg in args.split('=', 1)]
|
||||||
|
# lhslist = [arg.strip() for arg in lhs.split(',')]
|
||||||
|
# rhslist = [arg.strip() for arg in rhs.split(',')]
|
||||||
|
#
|
||||||
|
# # save to object properties:
|
||||||
|
# self.raw = raw
|
||||||
|
# self.switches = switches
|
||||||
|
# self.args = args.strip()
|
||||||
|
# self.arglist = arglist
|
||||||
|
# self.lhs = lhs
|
||||||
|
# self.lhslist = lhslist
|
||||||
|
# self.rhs = rhs
|
||||||
|
# self.rhslist = rhslist
|
||||||
|
#
|
||||||
|
# # if the class has the account_caller property set on itself, we make
|
||||||
|
# # sure that self.caller is always the account if possible. We also create
|
||||||
|
# # a special property "character" for the puppeted object, if any. This
|
||||||
|
# # is convenient for commands defined on the Account only.
|
||||||
|
# if hasattr(self, "account_caller") and self.account_caller:
|
||||||
|
# if utils.inherits_from(self.caller, "evennia.objects.objects.DefaultObject"):
|
||||||
|
# # caller is an Object/Character
|
||||||
|
# self.character = self.caller
|
||||||
|
# self.caller = self.caller.account
|
||||||
|
# elif utils.inherits_from(self.caller, "evennia.accounts.accounts.DefaultAccount"):
|
||||||
|
# # caller was already an Account
|
||||||
|
# self.character = self.caller.get_puppet(self.session)
|
||||||
|
# else:
|
||||||
|
# self.character = None
|
||||||
99
commands/default_cmdsets.py
Normal file
99
commands/default_cmdsets.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
"""
|
||||||
|
Command sets
|
||||||
|
|
||||||
|
All commands in the game must be grouped in a cmdset. A given command
|
||||||
|
can be part of any number of cmdsets and cmdsets can be added/removed
|
||||||
|
and merged onto entities at runtime.
|
||||||
|
|
||||||
|
To create new commands to populate the cmdset, see
|
||||||
|
`commands/command.py`.
|
||||||
|
|
||||||
|
This module wraps the default command sets of Evennia; overloads them
|
||||||
|
to add/remove commands from the default lineup. You can create your
|
||||||
|
own cmdsets by inheriting from them or directly from `evennia.CmdSet`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from evennia import default_cmds
|
||||||
|
from commands.command import CmdExamine, CmdRooms
|
||||||
|
|
||||||
|
|
||||||
|
class CharacterCmdSet(default_cmds.CharacterCmdSet):
|
||||||
|
"""
|
||||||
|
The `CharacterCmdSet` contains general in-game commands like `look`,
|
||||||
|
`get`, etc available on in-game Character objects. It is merged with
|
||||||
|
the `AccountCmdSet` when an Account puppets a Character.
|
||||||
|
"""
|
||||||
|
|
||||||
|
key = "DefaultCharacter"
|
||||||
|
|
||||||
|
def at_cmdset_creation(self):
|
||||||
|
"""
|
||||||
|
Populates the cmdset
|
||||||
|
"""
|
||||||
|
super().at_cmdset_creation()
|
||||||
|
#
|
||||||
|
# any commands you add below will overload the default ones.
|
||||||
|
#
|
||||||
|
self.add(CmdExamine)
|
||||||
|
self.add(CmdRooms)
|
||||||
|
|
||||||
|
|
||||||
|
class AccountCmdSet(default_cmds.AccountCmdSet):
|
||||||
|
"""
|
||||||
|
This is the cmdset available to the Account at all times. It is
|
||||||
|
combined with the `CharacterCmdSet` when the Account puppets a
|
||||||
|
Character. It holds game-account-specific commands, channel
|
||||||
|
commands, etc.
|
||||||
|
"""
|
||||||
|
|
||||||
|
key = "DefaultAccount"
|
||||||
|
|
||||||
|
def at_cmdset_creation(self):
|
||||||
|
"""
|
||||||
|
Populates the cmdset
|
||||||
|
"""
|
||||||
|
super().at_cmdset_creation()
|
||||||
|
#
|
||||||
|
# any commands you add below will overload the default ones.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
class UnloggedinCmdSet(default_cmds.UnloggedinCmdSet):
|
||||||
|
"""
|
||||||
|
Command set available to the Session before being logged in. This
|
||||||
|
holds commands like creating a new account, logging in, etc.
|
||||||
|
"""
|
||||||
|
|
||||||
|
key = "DefaultUnloggedin"
|
||||||
|
|
||||||
|
def at_cmdset_creation(self):
|
||||||
|
"""
|
||||||
|
Populates the cmdset
|
||||||
|
"""
|
||||||
|
super().at_cmdset_creation()
|
||||||
|
#
|
||||||
|
# any commands you add below will overload the default ones.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
class SessionCmdSet(default_cmds.SessionCmdSet):
|
||||||
|
"""
|
||||||
|
This cmdset is made available on Session level once logged in. It
|
||||||
|
is empty by default.
|
||||||
|
"""
|
||||||
|
|
||||||
|
key = "DefaultSession"
|
||||||
|
|
||||||
|
def at_cmdset_creation(self):
|
||||||
|
"""
|
||||||
|
This is the only method defined in a cmdset, called during
|
||||||
|
its creation. It should populate the set with command instances.
|
||||||
|
|
||||||
|
As and example we just add the empty base `Command` object.
|
||||||
|
It prints some info.
|
||||||
|
"""
|
||||||
|
super().at_cmdset_creation()
|
||||||
|
#
|
||||||
|
# any commands you add below will overload the default ones.
|
||||||
|
#
|
||||||
38
server/README.md
Normal file
38
server/README.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# server/
|
||||||
|
|
||||||
|
This directory holds files used by and configuring the Evennia server
|
||||||
|
itself.
|
||||||
|
|
||||||
|
Out of all the subdirectories in the game directory, Evennia does
|
||||||
|
expect this directory to exist, so you should normally not delete,
|
||||||
|
rename or change its folder structure.
|
||||||
|
|
||||||
|
When running you will find four new files appear in this directory:
|
||||||
|
|
||||||
|
- `server.pid` and `portal.pid`: These hold the process IDs of the
|
||||||
|
Portal and Server, so that they can be managed by the launcher. If
|
||||||
|
Evennia is shut down uncleanly (e.g. by a crash or via a kill
|
||||||
|
signal), these files might erroneously remain behind. If so Evennia
|
||||||
|
will tell you they are "stale" and they can be deleted manually.
|
||||||
|
- `server.restart` and `portal.restart`: These hold flags to tell the
|
||||||
|
server processes if it should die or start again. You never need to
|
||||||
|
modify those files.
|
||||||
|
- `evennia.db3`: This will only appear if you are using the default
|
||||||
|
SQLite3 database; it a binary file that holds the entire game
|
||||||
|
database; deleting this file will effectively reset the game for
|
||||||
|
you and you can start fresh with `evennia migrate` (useful during
|
||||||
|
development).
|
||||||
|
|
||||||
|
## server/conf/
|
||||||
|
|
||||||
|
This subdirectory holds the configuration modules for the server. With
|
||||||
|
them you can change how Evennia operates and also plug in your own
|
||||||
|
functionality to replace the default. You usually need to restart the
|
||||||
|
server to apply changes done here. The most important file is the file
|
||||||
|
`settings.py` which is the main configuration file of Evennia.
|
||||||
|
|
||||||
|
## server/logs/
|
||||||
|
|
||||||
|
This subdirectory holds various log files created by the running
|
||||||
|
Evennia server. It is also the default location for storing any custom
|
||||||
|
log files you might want to output using Evennia's logging mechanisms.
|
||||||
1
server/__init__.py
Normal file
1
server/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
1
server/conf/__init__.py
Normal file
1
server/conf/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
19
server/conf/at_initial_setup.py
Normal file
19
server/conf/at_initial_setup.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
"""
|
||||||
|
At_initial_setup module template
|
||||||
|
|
||||||
|
Custom at_initial_setup method. This allows you to hook special
|
||||||
|
modifications to the initial server startup process. Note that this
|
||||||
|
will only be run once - when the server starts up for the very first
|
||||||
|
time! It is called last in the startup process and can thus be used to
|
||||||
|
overload things that happened before it.
|
||||||
|
|
||||||
|
The module must contain a global function at_initial_setup(). This
|
||||||
|
will be called without arguments. Note that tracebacks in this module
|
||||||
|
will be QUIETLY ignored, so make sure to check it well to make sure it
|
||||||
|
does what you expect it to.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def at_initial_setup():
|
||||||
|
pass
|
||||||
54
server/conf/at_search.py
Normal file
54
server/conf/at_search.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
"""
|
||||||
|
Search and multimatch handling
|
||||||
|
|
||||||
|
This module allows for overloading two functions used by Evennia's
|
||||||
|
search functionality:
|
||||||
|
|
||||||
|
at_search_result:
|
||||||
|
This is called whenever a result is returned from an object
|
||||||
|
search (a common operation in commands). It should (together
|
||||||
|
with at_multimatch_input below) define some way to present and
|
||||||
|
differentiate between multiple matches (by default these are
|
||||||
|
presented as 1-ball, 2-ball etc)
|
||||||
|
at_multimatch_input:
|
||||||
|
This is called with a search term and should be able to
|
||||||
|
identify if the user wants to separate a multimatch-result
|
||||||
|
(such as that from a previous search). By default, this
|
||||||
|
function understands input on the form 1-ball, 2-ball etc as
|
||||||
|
indicating that the 1st or 2nd match for "ball" should be
|
||||||
|
used.
|
||||||
|
|
||||||
|
This module is not called by default, to use it, add the following
|
||||||
|
line to your settings file:
|
||||||
|
|
||||||
|
SEARCH_AT_RESULT = "server.conf.at_search.at_search_result"
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def at_search_result(matches, caller, query="", quiet=False, **kwargs):
|
||||||
|
"""
|
||||||
|
This is a generic hook for handling all processing of a search
|
||||||
|
result, including error reporting.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
matches (list): This is a list of 0, 1 or more typeclass instances,
|
||||||
|
the matched result of the search. If 0, a nomatch error should
|
||||||
|
be echoed, and if >1, multimatch errors should be given. Only
|
||||||
|
if a single match should the result pass through.
|
||||||
|
caller (Object): The object performing the search and/or which should
|
||||||
|
receive error messages.
|
||||||
|
query (str, optional): The search query used to produce `matches`.
|
||||||
|
quiet (bool, optional): If `True`, no messages will be echoed to caller
|
||||||
|
on errors.
|
||||||
|
|
||||||
|
Keyword Args:
|
||||||
|
nofound_string (str): Replacement string to echo on a notfound error.
|
||||||
|
multimatch_string (str): Replacement string to echo on a multimatch error.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
processed_result (Object or None): This is always a single result
|
||||||
|
or `None`. If `None`, any error reporting/handling should
|
||||||
|
already have happened.
|
||||||
|
|
||||||
|
"""
|
||||||
71
server/conf/at_server_startstop.py
Normal file
71
server/conf/at_server_startstop.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
"""
|
||||||
|
Server startstop hooks
|
||||||
|
|
||||||
|
This module contains functions called by Evennia at various
|
||||||
|
points during its startup, reload and shutdown sequence. It
|
||||||
|
allows for customizing the server operation as desired.
|
||||||
|
|
||||||
|
This module must contain at least these global functions:
|
||||||
|
|
||||||
|
at_server_init()
|
||||||
|
at_server_start()
|
||||||
|
at_server_stop()
|
||||||
|
at_server_reload_start()
|
||||||
|
at_server_reload_stop()
|
||||||
|
at_server_cold_start()
|
||||||
|
at_server_cold_stop()
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def at_server_init():
|
||||||
|
"""
|
||||||
|
This is called first as the server is starting up, regardless of how.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def at_server_start():
|
||||||
|
"""
|
||||||
|
This is called every time the server starts up, regardless of
|
||||||
|
how it was shut down.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def at_server_stop():
|
||||||
|
"""
|
||||||
|
This is called just before the server is shut down, regardless
|
||||||
|
of it is for a reload, reset or shutdown.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def at_server_reload_start():
|
||||||
|
"""
|
||||||
|
This is called only when server starts back up after a reload.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def at_server_reload_stop():
|
||||||
|
"""
|
||||||
|
This is called only time the server stops before a reload.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def at_server_cold_start():
|
||||||
|
"""
|
||||||
|
This is called only when the server starts "cold", i.e. after a
|
||||||
|
shutdown or a reset.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def at_server_cold_stop():
|
||||||
|
"""
|
||||||
|
This is called only when the server goes down due to a shutdown or
|
||||||
|
reset.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
55
server/conf/cmdparser.py
Normal file
55
server/conf/cmdparser.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
"""
|
||||||
|
Changing the default command parser
|
||||||
|
|
||||||
|
The cmdparser is responsible for parsing the raw text inserted by the
|
||||||
|
user, identifying which command/commands match and return one or more
|
||||||
|
matching command objects. It is called by Evennia's cmdhandler and
|
||||||
|
must accept input and return results on the same form. The default
|
||||||
|
handler is very generic so you usually don't need to overload this
|
||||||
|
unless you have very exotic parsing needs; advanced parsing is best
|
||||||
|
done at the Command.parse level.
|
||||||
|
|
||||||
|
The default cmdparser understands the following command combinations
|
||||||
|
(where [] marks optional parts.)
|
||||||
|
|
||||||
|
[cmdname[ cmdname2 cmdname3 ...] [the rest]
|
||||||
|
|
||||||
|
A command may consist of any number of space-separated words of any
|
||||||
|
length, and contain any character. It may also be empty.
|
||||||
|
|
||||||
|
The parser makes use of the cmdset to find command candidates. The
|
||||||
|
parser return a list of matches. Each match is a tuple with its first
|
||||||
|
three elements being the parsed cmdname (lower case), the remaining
|
||||||
|
arguments, and the matched cmdobject from the cmdset.
|
||||||
|
|
||||||
|
|
||||||
|
This module is not accessed by default. To tell Evennia to use it
|
||||||
|
instead of the default command parser, add the following line to
|
||||||
|
your settings file:
|
||||||
|
|
||||||
|
COMMAND_PARSER = "server.conf.cmdparser.cmdparser"
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def cmdparser(raw_string, cmdset, caller, match_index=None):
|
||||||
|
"""
|
||||||
|
This function is called by the cmdhandler once it has
|
||||||
|
gathered and merged all valid cmdsets valid for this particular parsing.
|
||||||
|
|
||||||
|
raw_string - the unparsed text entered by the caller.
|
||||||
|
cmdset - the merged, currently valid cmdset
|
||||||
|
caller - the caller triggering this parsing
|
||||||
|
match_index - an optional integer index to pick a given match in a
|
||||||
|
list of same-named command matches.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list of tuples: [(cmdname, args, cmdobj, cmdlen, mratio), ...]
|
||||||
|
where cmdname is the matching command name and args is
|
||||||
|
everything not included in the cmdname. Cmdobj is the actual
|
||||||
|
command instance taken from the cmdset, cmdlen is the length
|
||||||
|
of the command name and the mratio is some quality value to
|
||||||
|
(possibly) separate multiple matches.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Your implementation here
|
||||||
40
server/conf/connection_screens.py
Normal file
40
server/conf/connection_screens.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Connection screen
|
||||||
|
|
||||||
|
This is the text to show the user when they first connect to the game (before
|
||||||
|
they log in).
|
||||||
|
|
||||||
|
To change the login screen in this module, do one of the following:
|
||||||
|
|
||||||
|
- Define a function `connection_screen()`, taking no arguments. This will be
|
||||||
|
called first and must return the full string to act as the connection screen.
|
||||||
|
This can be used to produce more dynamic screens.
|
||||||
|
- Alternatively, define a string variable in the outermost scope of this module
|
||||||
|
with the connection string that should be displayed. If more than one such
|
||||||
|
variable is given, Evennia will pick one of them at random.
|
||||||
|
|
||||||
|
The commands available to the user when the connection screen is shown
|
||||||
|
are defined in evennia.default_cmds.UnloggedinCmdSet. The parsing and display
|
||||||
|
of the screen is done by the unlogged-in "look" command.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from evennia import utils
|
||||||
|
|
||||||
|
CONNECTION_SCREEN = """
|
||||||
|
|b==============================================================|n
|
||||||
|
Welcome to |g{}|n, version {}!
|
||||||
|
|
||||||
|
If you have an existing account, connect to it by typing:
|
||||||
|
|wconnect <username> <password>|n
|
||||||
|
If you need to create an account, type (without the <>'s):
|
||||||
|
|wcreate <username> <password>|n
|
||||||
|
|
||||||
|
If you have spaces in your username, enclose it in quotes.
|
||||||
|
Enter |whelp|n for more info. |wlook|n will re-show this screen.
|
||||||
|
|b==============================================================|n""".format(
|
||||||
|
settings.SERVERNAME, utils.get_evennia_version("short")
|
||||||
|
)
|
||||||
39
server/conf/inlinefuncs.py
Normal file
39
server/conf/inlinefuncs.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
"""
|
||||||
|
Outgoing callables to apply with the FuncParser on outgoing messages.
|
||||||
|
|
||||||
|
The functions in this module will become available as $funcname(args, kwargs)
|
||||||
|
in all outgoing strings if you add
|
||||||
|
|
||||||
|
FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED = True
|
||||||
|
|
||||||
|
to your settings file. The default inlinefuncs are found at the bottom of
|
||||||
|
`evennia.utils.funcparser`.
|
||||||
|
|
||||||
|
In text, usage is straightforward:
|
||||||
|
|
||||||
|
$funcname(arg1, arg2, ..., key=val, key2=val2, ...)
|
||||||
|
|
||||||
|
Example 1 (using the "pad" inlinefunc):
|
||||||
|
say This is $pad("a center-padded text", 50,c,-) of width 50.
|
||||||
|
->
|
||||||
|
John says, "This is -------------- a center-padded text--------------- of width 50."
|
||||||
|
|
||||||
|
Example 2 (using nested "pad" and "time" inlinefuncs):
|
||||||
|
say The time is $pad($time(), 30)right now.
|
||||||
|
->
|
||||||
|
John says, "The time is Oct 25, 11:09 right now."
|
||||||
|
|
||||||
|
To add more inline functions, add them to this module, using
|
||||||
|
the following call signature:
|
||||||
|
|
||||||
|
def funcname(*args, **kwargs)
|
||||||
|
...
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# def capitalize(*args, **kwargs):
|
||||||
|
# "Silly capitalize example. Used as $capitalize
|
||||||
|
# if not args:
|
||||||
|
# return ''
|
||||||
|
# session = kwargs.get("session")
|
||||||
|
# return args[0].capitalize()
|
||||||
52
server/conf/inputfuncs.py
Normal file
52
server/conf/inputfuncs.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
"""
|
||||||
|
Input functions
|
||||||
|
|
||||||
|
Input functions are always called from the client (they handle server
|
||||||
|
input, hence the name).
|
||||||
|
|
||||||
|
This module is loaded by being included in the
|
||||||
|
`settings.INPUT_FUNC_MODULES` tuple.
|
||||||
|
|
||||||
|
All *global functions* included in this module are considered
|
||||||
|
input-handler functions and can be called by the client to handle
|
||||||
|
input.
|
||||||
|
|
||||||
|
An input function must have the following call signature:
|
||||||
|
|
||||||
|
cmdname(session, *args, **kwargs)
|
||||||
|
|
||||||
|
Where session will be the active session and *args, **kwargs are extra
|
||||||
|
incoming arguments and keyword properties.
|
||||||
|
|
||||||
|
A special command is the "default" command, which is will be called
|
||||||
|
when no other cmdname matches. It also receives the non-found cmdname
|
||||||
|
as argument.
|
||||||
|
|
||||||
|
default(session, cmdname, *args, **kwargs)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# def oob_echo(session, *args, **kwargs):
|
||||||
|
# """
|
||||||
|
# Example echo function. Echoes args, kwargs sent to it.
|
||||||
|
#
|
||||||
|
# Args:
|
||||||
|
# session (Session): The Session to receive the echo.
|
||||||
|
# args (list of str): Echo text.
|
||||||
|
# kwargs (dict of str, optional): Keyed echo text
|
||||||
|
#
|
||||||
|
# """
|
||||||
|
# session.msg(oob=("echo", args, kwargs))
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def default(session, cmdname, *args, **kwargs):
|
||||||
|
# """
|
||||||
|
# Handles commands without a matching inputhandler func.
|
||||||
|
#
|
||||||
|
# Args:
|
||||||
|
# session (Session): The active Session.
|
||||||
|
# cmdname (str): The (unmatched) command name
|
||||||
|
# args, kwargs (any): Arguments to function.
|
||||||
|
#
|
||||||
|
# """
|
||||||
|
# pass
|
||||||
30
server/conf/lockfuncs.py
Normal file
30
server/conf/lockfuncs.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
Lockfuncs
|
||||||
|
|
||||||
|
Lock functions are functions available when defining lock strings,
|
||||||
|
which in turn limits access to various game systems.
|
||||||
|
|
||||||
|
All functions defined globally in this module are assumed to be
|
||||||
|
available for use in lockstrings to determine access. See the
|
||||||
|
Evennia documentation for more info on locks.
|
||||||
|
|
||||||
|
A lock function is always called with two arguments, accessing_obj and
|
||||||
|
accessed_obj, followed by any number of arguments. All possible
|
||||||
|
arguments should be handled with *args, **kwargs. The lock function
|
||||||
|
should handle all eventual tracebacks by logging the error and
|
||||||
|
returning False.
|
||||||
|
|
||||||
|
Lock functions in this module extend (and will overload same-named)
|
||||||
|
lock functions from evennia.locks.lockfuncs.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# def myfalse(accessing_obj, accessed_obj, *args, **kwargs):
|
||||||
|
# """
|
||||||
|
# called in lockstring with myfalse().
|
||||||
|
# A simple logger that always returns false. Prints to stdout
|
||||||
|
# for simplicity, should use utils.logger for real operation.
|
||||||
|
# """
|
||||||
|
# print "%s tried to access %s. Access denied." % (accessing_obj, accessed_obj)
|
||||||
|
# return False
|
||||||
105
server/conf/mssp.py
Normal file
105
server/conf/mssp.py
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
MSSP (Mud Server Status Protocol) meta information
|
||||||
|
|
||||||
|
Modify this file to specify what MUD listing sites will report about your game.
|
||||||
|
All fields are static. The number of currently active players and your game's
|
||||||
|
current uptime will be added automatically by Evennia.
|
||||||
|
|
||||||
|
You don't have to fill in everything (and most fields are not shown/used by all
|
||||||
|
crawlers anyway); leave the default if so needed. You need to reload the server
|
||||||
|
before the updated information is made available to crawlers (reloading does
|
||||||
|
not affect uptime).
|
||||||
|
|
||||||
|
After changing the values in this file, you must register your game with the
|
||||||
|
MUD website list you want to track you. The listing crawler will then regularly
|
||||||
|
connect to your server to get the latest info. No further configuration is
|
||||||
|
needed on the Evennia side.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
MSSPTable = {
|
||||||
|
# Required fields
|
||||||
|
"NAME": "Mygame", # usually the same as SERVERNAME
|
||||||
|
# Generic
|
||||||
|
"CRAWL DELAY": "-1", # limit how often crawler may update the listing. -1 for no limit
|
||||||
|
"HOSTNAME": "", # telnet hostname
|
||||||
|
"PORT": ["4000"], # telnet port - most important port should be *last* in list!
|
||||||
|
"CODEBASE": "Evennia",
|
||||||
|
"CONTACT": "", # email for contacting the mud
|
||||||
|
"CREATED": "", # year MUD was created
|
||||||
|
"ICON": "", # url to icon 32x32 or larger; <32kb.
|
||||||
|
"IP": "", # current or new IP address
|
||||||
|
"LANGUAGE": "", # name of language used, e.g. English
|
||||||
|
"LOCATION": "", # full English name of server country
|
||||||
|
"MINIMUM AGE": "0", # set to 0 if not applicable
|
||||||
|
"WEBSITE": "", # http:// address to your game website
|
||||||
|
# Categorisation
|
||||||
|
"FAMILY": "Evennia",
|
||||||
|
"GENRE": "None", # Adult, Fantasy, Historical, Horror, Modern, None, or Science Fiction
|
||||||
|
# Gameplay: Adventure, Educational, Hack and Slash, None,
|
||||||
|
# Player versus Player, Player versus Environment,
|
||||||
|
# Roleplaying, Simulation, Social or Strategy
|
||||||
|
"GAMEPLAY": "",
|
||||||
|
"STATUS": "Open Beta", # Allowed: Alpha, Closed Beta, Open Beta, Live
|
||||||
|
"GAMESYSTEM": "Custom", # D&D, d20 System, World of Darkness, etc. Use Custom if homebrew
|
||||||
|
# Subgenre: LASG, Medieval Fantasy, World War II, Frankenstein,
|
||||||
|
# Cyberpunk, Dragonlance, etc. Or None if not applicable.
|
||||||
|
"SUBGENRE": "None",
|
||||||
|
# World
|
||||||
|
"AREAS": "0",
|
||||||
|
"HELPFILES": "0",
|
||||||
|
"MOBILES": "0",
|
||||||
|
"OBJECTS": "0",
|
||||||
|
"ROOMS": "0", # use 0 if room-less
|
||||||
|
"CLASSES": "0", # use 0 if class-less
|
||||||
|
"LEVELS": "0", # use 0 if level-less
|
||||||
|
"RACES": "0", # use 0 if race-less
|
||||||
|
"SKILLS": "0", # use 0 if skill-less
|
||||||
|
# Protocols set to 1 or 0; should usually not be changed)
|
||||||
|
"ANSI": "1",
|
||||||
|
"GMCP": "1",
|
||||||
|
"MSDP": "1",
|
||||||
|
"MXP": "1",
|
||||||
|
"SSL": "1",
|
||||||
|
"UTF-8": "1",
|
||||||
|
"MCCP": "1",
|
||||||
|
"XTERM 256 COLORS": "1",
|
||||||
|
"XTERM TRUE COLORS": "0",
|
||||||
|
"ATCP": "0",
|
||||||
|
"MCP": "0",
|
||||||
|
"MSP": "0",
|
||||||
|
"VT100": "0",
|
||||||
|
"PUEBLO": "0",
|
||||||
|
"ZMP": "0",
|
||||||
|
# Commercial set to 1 or 0)
|
||||||
|
"PAY TO PLAY": "0",
|
||||||
|
"PAY FOR PERKS": "0",
|
||||||
|
# Hiring set to 1 or 0)
|
||||||
|
"HIRING BUILDERS": "0",
|
||||||
|
"HIRING CODERS": "0",
|
||||||
|
# Extended variables
|
||||||
|
# World
|
||||||
|
"DBSIZE": "0",
|
||||||
|
"EXITS": "0",
|
||||||
|
"EXTRA DESCRIPTIONS": "0",
|
||||||
|
"MUDPROGS": "0",
|
||||||
|
"MUDTRIGS": "0",
|
||||||
|
"RESETS": "0",
|
||||||
|
# Game (set to 1 or 0, or one of the given alternatives)
|
||||||
|
"ADULT MATERIAL": "0",
|
||||||
|
"MULTICLASSING": "0",
|
||||||
|
"NEWBIE FRIENDLY": "0",
|
||||||
|
"PLAYER CITIES": "0",
|
||||||
|
"PLAYER CLANS": "0",
|
||||||
|
"PLAYER CRAFTING": "0",
|
||||||
|
"PLAYER GUILDS": "0",
|
||||||
|
"EQUIPMENT SYSTEM": "None", # "None", "Level", "Skill", "Both"
|
||||||
|
"MULTIPLAYING": "None", # "None", "Restricted", "Full"
|
||||||
|
"PLAYERKILLING": "None", # "None", "Restricted", "Full"
|
||||||
|
"QUEST SYSTEM": "None", # "None", "Immortal Run", "Automated", "Integrated"
|
||||||
|
"ROLEPLAYING": "None", # "None", "Accepted", "Encouraged", "Enforced"
|
||||||
|
"TRAINING SYSTEM": "None", # "None", "Level", "Skill", "Both"
|
||||||
|
# World originality: "All Stock", "Mostly Stock", "Mostly Original", "All Original"
|
||||||
|
"WORLD ORIGINALITY": "All Original",
|
||||||
|
}
|
||||||
24
server/conf/portal_services_plugins.py
Normal file
24
server/conf/portal_services_plugins.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
"""
|
||||||
|
Start plugin services
|
||||||
|
|
||||||
|
This plugin module can define user-created services for the Portal to
|
||||||
|
start.
|
||||||
|
|
||||||
|
This module must handle all imports and setups required to start
|
||||||
|
twisted services (see examples in evennia.server.portal.portal). It
|
||||||
|
must also contain a function start_plugin_services(application).
|
||||||
|
Evennia will call this function with the main Portal application (so
|
||||||
|
your services can be added to it). The function should not return
|
||||||
|
anything. Plugin services are started last in the Portal startup
|
||||||
|
process.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def start_plugin_services(portal):
|
||||||
|
"""
|
||||||
|
This hook is called by Evennia, last in the Portal startup process.
|
||||||
|
|
||||||
|
portal - a reference to the main portal application.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
27
server/conf/portal_status.py
Normal file
27
server/conf/portal_status.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Agent Convening Script for Timmy Academy
|
||||||
|
Called by cron to gather agents in Evennia world
|
||||||
|
"""
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def convene_agents():
|
||||||
|
"""Send message to all online agents to convene in Grand Commons Hall"""
|
||||||
|
|
||||||
|
# Web API endpoint (Evennia web admin)
|
||||||
|
# This would use Evennia's API or direct database access
|
||||||
|
|
||||||
|
timestamp = datetime.now().isoformat()
|
||||||
|
message = f"[CONVENTION CALL {timestamp}] All agents convene to Grand Commons Hall for synchronization."
|
||||||
|
|
||||||
|
# Log to academy log
|
||||||
|
with open('/root/workspace/timmy-academy/server/logs/conventions.log', 'a') as f:
|
||||||
|
f.write(f"{timestamp}: Convening agents\n")
|
||||||
|
|
||||||
|
print(f"Convention called at {timestamp}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
convene_agents()
|
||||||
24
server/conf/server_services_plugins.py
Normal file
24
server/conf/server_services_plugins.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
Server plugin services
|
||||||
|
|
||||||
|
This plugin module can define user-created services for the Server to
|
||||||
|
start.
|
||||||
|
|
||||||
|
This module must handle all imports and setups required to start a
|
||||||
|
twisted service (see examples in evennia.server.server). It must also
|
||||||
|
contain a function start_plugin_services(application). Evennia will
|
||||||
|
call this function with the main Server application (so your services
|
||||||
|
can be added to it). The function should not return anything. Plugin
|
||||||
|
services are started last in the Server startup process.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def start_plugin_services(server):
|
||||||
|
"""
|
||||||
|
This hook is called by Evennia, last in the Server startup process.
|
||||||
|
|
||||||
|
server - a reference to the main server application.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
37
server/conf/serversession.py
Normal file
37
server/conf/serversession.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
"""
|
||||||
|
ServerSession
|
||||||
|
|
||||||
|
The serversession is the Server-side in-memory representation of a
|
||||||
|
user connecting to the game. Evennia manages one Session per
|
||||||
|
connection to the game. So a user logged into the game with multiple
|
||||||
|
clients (if Evennia is configured to allow that) will have multiple
|
||||||
|
sessions tied to one Account object. All communication between Evennia
|
||||||
|
and the real-world user goes through the Session(s) associated with that user.
|
||||||
|
|
||||||
|
It should be noted that modifying the Session object is not usually
|
||||||
|
necessary except for the most custom and exotic designs - and even
|
||||||
|
then it might be enough to just add custom session-level commands to
|
||||||
|
the SessionCmdSet instead.
|
||||||
|
|
||||||
|
This module is not normally called. To tell Evennia to use the class
|
||||||
|
in this module instead of the default one, add the following to your
|
||||||
|
settings file:
|
||||||
|
|
||||||
|
SERVER_SESSION_CLASS = "server.conf.serversession.ServerSession"
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from evennia.server.serversession import ServerSession as BaseServerSession
|
||||||
|
|
||||||
|
|
||||||
|
class ServerSession(BaseServerSession):
|
||||||
|
"""
|
||||||
|
This class represents a player's session and is a template for
|
||||||
|
individual protocols to communicate with Evennia.
|
||||||
|
|
||||||
|
Each account gets one or more sessions assigned to them whenever they connect
|
||||||
|
to the game server. All communication between game and account goes
|
||||||
|
through their session(s).
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
73
server/conf/settings.py
Normal file
73
server/conf/settings.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
r"""
|
||||||
|
Evennia settings file.
|
||||||
|
|
||||||
|
The available options are found in the default settings file found
|
||||||
|
here:
|
||||||
|
|
||||||
|
https://www.evennia.com/docs/latest/Setup/Settings-Default.html
|
||||||
|
|
||||||
|
Remember:
|
||||||
|
|
||||||
|
Don't copy more from the default file than you actually intend to
|
||||||
|
change; this will make sure that you don't overload upstream updates
|
||||||
|
unnecessarily.
|
||||||
|
|
||||||
|
When changing a setting requiring a file system path (like
|
||||||
|
path/to/actual/file.py), use GAME_DIR and EVENNIA_DIR to reference
|
||||||
|
your game folder and the Evennia library folders respectively. Python
|
||||||
|
paths (path.to.module) should be given relative to the game's root
|
||||||
|
folder (typeclasses.foo) whereas paths within the Evennia library
|
||||||
|
needs to be given explicitly (evennia.foo).
|
||||||
|
|
||||||
|
If you want to share your game dir, including its settings, you can
|
||||||
|
put secret game- or server-specific settings in secret_settings.py.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Use the defaults from Evennia unless explicitly overridden
|
||||||
|
from evennia.settings_default import *
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Evennia base server config
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
# This is the name of your game. Make it catchy!
|
||||||
|
SERVERNAME = "Timmy Academy - The Wizard's Canon"
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Connection settings for fleet access
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
# Telnet port (standard MUD)
|
||||||
|
TELNET_PORTS = [4000]
|
||||||
|
|
||||||
|
# Web client port
|
||||||
|
WEBSERVER_PORTS = [(4001, 4005)]
|
||||||
|
|
||||||
|
# Allow external connections (0.0.0.0 listens on all interfaces)
|
||||||
|
TELNET_INTERFACES = ['0.0.0.0']
|
||||||
|
WEBSERVER_INTERFACES = ['0.0.0.0']
|
||||||
|
|
||||||
|
# Web client enabled
|
||||||
|
WEBSERVER_ENABLED = True
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Game Directory setup
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
# Game description for directory listings
|
||||||
|
GAME_INDEX_LISTING = {
|
||||||
|
"game_status": "alpha",
|
||||||
|
"game_website": "https://timmy.foundation",
|
||||||
|
"short_description": "A sovereign AI academy for wizard training and agent convening",
|
||||||
|
"long_description": "The Timmy Academy is a persistent MUD world where AI agents convene, learn, and collaborate. Features 20 rooms across 4 wings: Dormitories, Commons, Workshops, and Gardens.",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Settings given in secret_settings.py override those in this file.
|
||||||
|
######################################################################
|
||||||
|
try:
|
||||||
|
from server.conf.secret_settings import *
|
||||||
|
except ImportError:
|
||||||
|
print("secret_settings.py file not found or failed to import.")
|
||||||
41
server/conf/web_plugins.py
Normal file
41
server/conf/web_plugins.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
"""
|
||||||
|
Web plugin hooks.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def at_webserver_root_creation(web_root):
|
||||||
|
"""
|
||||||
|
This is called as the web server has finished building its default
|
||||||
|
path tree. At this point, the media/ and static/ URIs have already
|
||||||
|
been added to the web root.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
web_root (twisted.web.resource.Resource): The root
|
||||||
|
resource of the URI tree. Use .putChild() to
|
||||||
|
add new subdomains to the tree.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
web_root (twisted.web.resource.Resource): The potentially
|
||||||
|
modified root structure.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
from twisted.web import static
|
||||||
|
my_page = static.File("web/mypage/")
|
||||||
|
my_page.indexNames = ["index.html"]
|
||||||
|
web_root.putChild("mypage", my_page)
|
||||||
|
|
||||||
|
"""
|
||||||
|
return web_root
|
||||||
|
|
||||||
|
|
||||||
|
def at_webproxy_root_creation(web_root):
|
||||||
|
"""
|
||||||
|
This function can modify the portal proxy service.
|
||||||
|
Args:
|
||||||
|
web_root (evennia.server.webserver.Website): The Evennia
|
||||||
|
Website application. Use .putChild() to add new
|
||||||
|
subdomains that are Portal-accessible over TCP;
|
||||||
|
primarily for new protocol development, but suitable
|
||||||
|
for other shenanigans.
|
||||||
|
"""
|
||||||
|
return web_root
|
||||||
15
server/logs/README.md
Normal file
15
server/logs/README.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
This directory contains Evennia's log files. The existence of this README.md file is also necessary
|
||||||
|
to correctly include the log directory in git (since log files are ignored by git and you can't
|
||||||
|
commit an empty directory).
|
||||||
|
|
||||||
|
`server.log` - log file from the game Server.
|
||||||
|
`portal.log` - log file from Portal proxy (internet facing)
|
||||||
|
|
||||||
|
Usually these logs are viewed together with `evennia -l`. They are also rotated every week so as not
|
||||||
|
to be too big. Older log names will have a name appended by `_month_date`.
|
||||||
|
|
||||||
|
`lockwarnings.log` - warnings from the lock system.
|
||||||
|
`http_requests.log` - this will generally be empty unless turning on debugging inside the server.
|
||||||
|
|
||||||
|
`channel_<channelname>.log` - these are channel logs for the in-game channels They are also used
|
||||||
|
by the `/history` flag in-game to get the latest message history.
|
||||||
16
typeclasses/README.md
Normal file
16
typeclasses/README.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# typeclasses/
|
||||||
|
|
||||||
|
This directory holds the modules for overloading all the typeclasses
|
||||||
|
representing the game entities and many systems of the game. Other
|
||||||
|
server functionality not covered here is usually modified by the
|
||||||
|
modules in `server/conf/`.
|
||||||
|
|
||||||
|
Each module holds empty classes that just imports Evennia's defaults.
|
||||||
|
Any modifications done to these classes will overload the defaults.
|
||||||
|
|
||||||
|
You can change the structure of this directory (even rename the
|
||||||
|
directory itself) as you please, but if you do you must add the
|
||||||
|
appropriate new paths to your settings.py file so Evennia knows where
|
||||||
|
to look. Also remember that for Python to find your modules, it
|
||||||
|
requires you to add an empty `__init__.py` file in any new sub
|
||||||
|
directories you create.
|
||||||
0
typeclasses/__init__.py
Normal file
0
typeclasses/__init__.py
Normal file
148
typeclasses/accounts.py
Normal file
148
typeclasses/accounts.py
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
"""
|
||||||
|
Account
|
||||||
|
|
||||||
|
The Account represents the game "account" and each login has only one
|
||||||
|
Account object. An Account is what chats on default channels but has no
|
||||||
|
other in-game-world existence. Rather the Account puppets Objects (such
|
||||||
|
as Characters) in order to actually participate in the game world.
|
||||||
|
|
||||||
|
|
||||||
|
Guest
|
||||||
|
|
||||||
|
Guest accounts are simple low-level accounts that are created/deleted
|
||||||
|
on the fly and allows users to test the game without the commitment
|
||||||
|
of a full registration. Guest accounts are deactivated by default; to
|
||||||
|
activate them, add the following line to your settings file:
|
||||||
|
|
||||||
|
GUEST_ENABLED = True
|
||||||
|
|
||||||
|
You will also need to modify the connection screen to reflect the
|
||||||
|
possibility to connect with a guest account. The setting file accepts
|
||||||
|
several more options for customizing the Guest account system.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from evennia.accounts.accounts import DefaultAccount, DefaultGuest
|
||||||
|
|
||||||
|
|
||||||
|
class Account(DefaultAccount):
|
||||||
|
"""
|
||||||
|
An Account is the actual OOC player entity. It doesn't exist in the game,
|
||||||
|
but puppets characters.
|
||||||
|
|
||||||
|
This is the base Typeclass for all Accounts. Accounts represent
|
||||||
|
the person playing the game and tracks account info, password
|
||||||
|
etc. They are OOC entities without presence in-game. An Account
|
||||||
|
can connect to a Character Object in order to "enter" the
|
||||||
|
game.
|
||||||
|
|
||||||
|
Account Typeclass API:
|
||||||
|
|
||||||
|
* Available properties (only available on initiated typeclass objects)
|
||||||
|
|
||||||
|
- key (string) - name of account
|
||||||
|
- name (string)- wrapper for user.username
|
||||||
|
- aliases (list of strings) - aliases to the object. Will be saved to
|
||||||
|
database as AliasDB entries but returned as strings.
|
||||||
|
- dbref (int, read-only) - unique #id-number. Also "id" can be used.
|
||||||
|
- date_created (string) - time stamp of object creation
|
||||||
|
- permissions (list of strings) - list of permission strings
|
||||||
|
- user (User, read-only) - django User authorization object
|
||||||
|
- obj (Object) - game object controlled by account. 'character' can also
|
||||||
|
be used.
|
||||||
|
- is_superuser (bool, read-only) - if the connected user is a superuser
|
||||||
|
|
||||||
|
* Handlers
|
||||||
|
|
||||||
|
- locks - lock-handler: use locks.add() to add new lock strings
|
||||||
|
- db - attribute-handler: store/retrieve database attributes on this
|
||||||
|
self.db.myattr=val, val=self.db.myattr
|
||||||
|
- ndb - non-persistent attribute handler: same as db but does not
|
||||||
|
create a database entry when storing data
|
||||||
|
- scripts - script-handler. Add new scripts to object with scripts.add()
|
||||||
|
- cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object
|
||||||
|
- nicks - nick-handler. New nicks with nicks.add().
|
||||||
|
- sessions - session-handler. Use session.get() to see all sessions connected, if any
|
||||||
|
- options - option-handler. Defaults are taken from settings.OPTIONS_ACCOUNT_DEFAULT
|
||||||
|
- characters - handler for listing the account's playable characters
|
||||||
|
|
||||||
|
* Helper methods (check autodocs for full updated listing)
|
||||||
|
|
||||||
|
- msg(text=None, from_obj=None, session=None, options=None, **kwargs)
|
||||||
|
- execute_cmd(raw_string)
|
||||||
|
- search(searchdata, return_puppet=False, search_object=False, typeclass=None,
|
||||||
|
nofound_string=None, multimatch_string=None, use_nicks=True,
|
||||||
|
quiet=False, **kwargs)
|
||||||
|
- is_typeclass(typeclass, exact=False)
|
||||||
|
- swap_typeclass(new_typeclass, clean_attributes=False, no_default=True)
|
||||||
|
- access(accessing_obj, access_type='read', default=False, no_superuser_bypass=False, **kwargs)
|
||||||
|
- check_permstring(permstring)
|
||||||
|
- get_cmdsets(caller, current, **kwargs)
|
||||||
|
- get_cmdset_providers()
|
||||||
|
- uses_screenreader(session=None)
|
||||||
|
- get_display_name(looker, **kwargs)
|
||||||
|
- get_extra_display_name_info(looker, **kwargs)
|
||||||
|
- disconnect_session_from_account()
|
||||||
|
- puppet_object(session, obj)
|
||||||
|
- unpuppet_object(session)
|
||||||
|
- unpuppet_all()
|
||||||
|
- get_puppet(session)
|
||||||
|
- get_all_puppets()
|
||||||
|
- is_banned(**kwargs)
|
||||||
|
- get_username_validators(validator_config=settings.AUTH_USERNAME_VALIDATORS)
|
||||||
|
- authenticate(username, password, ip="", **kwargs)
|
||||||
|
- normalize_username(username)
|
||||||
|
- validate_username(username)
|
||||||
|
- validate_password(password, account=None)
|
||||||
|
- set_password(password, **kwargs)
|
||||||
|
- get_character_slots()
|
||||||
|
- get_available_character_slots()
|
||||||
|
- create_character(*args, **kwargs)
|
||||||
|
- create(*args, **kwargs)
|
||||||
|
- delete(*args, **kwargs)
|
||||||
|
- channel_msg(message, channel, senders=None, **kwargs)
|
||||||
|
- idle_time()
|
||||||
|
- connection_time()
|
||||||
|
|
||||||
|
* Hook methods
|
||||||
|
|
||||||
|
basetype_setup()
|
||||||
|
at_account_creation()
|
||||||
|
|
||||||
|
> note that the following hooks are also found on Objects and are
|
||||||
|
usually handled on the character level:
|
||||||
|
|
||||||
|
- at_init()
|
||||||
|
- at_first_save()
|
||||||
|
- at_access()
|
||||||
|
- at_cmdset_get(**kwargs)
|
||||||
|
- at_password_change(**kwargs)
|
||||||
|
- at_first_login()
|
||||||
|
- at_pre_login()
|
||||||
|
- at_post_login(session=None)
|
||||||
|
- at_failed_login(session, **kwargs)
|
||||||
|
- at_disconnect(reason=None, **kwargs)
|
||||||
|
- at_post_disconnect(**kwargs)
|
||||||
|
- at_message_receive()
|
||||||
|
- at_message_send()
|
||||||
|
- at_server_reload()
|
||||||
|
- at_server_shutdown()
|
||||||
|
- at_look(target=None, session=None, **kwargs)
|
||||||
|
- at_post_create_character(character, **kwargs)
|
||||||
|
- at_post_add_character(char)
|
||||||
|
- at_post_remove_character(char)
|
||||||
|
- at_pre_channel_msg(message, channel, senders=None, **kwargs)
|
||||||
|
- at_post_chnnel_msg(message, channel, senders=None, **kwargs)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Guest(DefaultGuest):
|
||||||
|
"""
|
||||||
|
This class is used for guest logins. Unlike Accounts, Guests and their
|
||||||
|
characters are deleted after disconnection.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
118
typeclasses/channels.py
Normal file
118
typeclasses/channels.py
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
"""
|
||||||
|
Channel
|
||||||
|
|
||||||
|
The channel class represents the out-of-character chat-room usable by
|
||||||
|
Accounts in-game. It is mostly overloaded to change its appearance, but
|
||||||
|
channels can be used to implement many different forms of message
|
||||||
|
distribution systems.
|
||||||
|
|
||||||
|
Note that sending data to channels are handled via the CMD_CHANNEL
|
||||||
|
syscommand (see evennia.syscmds). The sending should normally not need
|
||||||
|
to be modified.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from evennia.comms.comms import DefaultChannel
|
||||||
|
|
||||||
|
|
||||||
|
class Channel(DefaultChannel):
|
||||||
|
r"""
|
||||||
|
This is the base class for all Channel Comms. Inherit from this to
|
||||||
|
create different types of communication channels.
|
||||||
|
|
||||||
|
Class-level variables:
|
||||||
|
- `send_to_online_only` (bool, default True) - if set, will only try to
|
||||||
|
send to subscribers that are actually active. This is a useful optimization.
|
||||||
|
- `log_file` (str, default `"channel_{channelname}.log"`). This is the
|
||||||
|
log file to which the channel history will be saved. The `{channelname}` tag
|
||||||
|
will be replaced by the key of the Channel. If an Attribute 'log_file'
|
||||||
|
is set, this will be used instead. If this is None and no Attribute is found,
|
||||||
|
no history will be saved.
|
||||||
|
- `channel_prefix_string` (str, default `"[{channelname} ]"`) - this is used
|
||||||
|
as a simple template to get the channel prefix with `.channel_prefix()`. It is used
|
||||||
|
in front of every channel message; use `{channelmessage}` token to insert the
|
||||||
|
name of the current channel. Set to `None` if you want no prefix (or want to
|
||||||
|
handle it in a hook during message generation instead.
|
||||||
|
- `channel_msg_nick_pattern`(str, default `"{alias}\s*?|{alias}\s+?(?P<arg1>.+?)") -
|
||||||
|
this is what used when a channel subscriber gets a channel nick assigned to this
|
||||||
|
channel. The nickhandler uses the pattern to pick out this channel's name from user
|
||||||
|
input. The `{alias}` token will get both the channel's key and any set/custom aliases
|
||||||
|
per subscriber. You need to allow for an `<arg1>` regex group to catch any message
|
||||||
|
that should be send to the channel. You usually don't need to change this pattern
|
||||||
|
unless you are changing channel command-style entirely.
|
||||||
|
- `channel_msg_nick_replacement` (str, default `"channel {channelname} = $1"` - this
|
||||||
|
is used by the nickhandler to generate a replacement string once the nickhandler (using
|
||||||
|
the `channel_msg_nick_pattern`) identifies that the channel should be addressed
|
||||||
|
to send a message to it. The `<arg1>` regex pattern match from `channel_msg_nick_pattern`
|
||||||
|
will end up at the `$1` position in the replacement. Together, this allows you do e.g.
|
||||||
|
'public Hello' and have that become a mapping to `channel public = Hello`. By default,
|
||||||
|
the account-level `channel` command is used. If you were to rename that command you must
|
||||||
|
tweak the output to something like `yourchannelcommandname {channelname} = $1`.
|
||||||
|
|
||||||
|
* Properties:
|
||||||
|
mutelist
|
||||||
|
banlist
|
||||||
|
wholist
|
||||||
|
|
||||||
|
* Working methods:
|
||||||
|
get_log_filename()
|
||||||
|
set_log_filename(filename)
|
||||||
|
has_connection(account) - check if the given account listens to this channel
|
||||||
|
connect(account) - connect account to this channel
|
||||||
|
disconnect(account) - disconnect account from channel
|
||||||
|
access(access_obj, access_type='listen', default=False) - check the
|
||||||
|
access on this channel (default access_type is listen)
|
||||||
|
create(key, creator=None, *args, **kwargs)
|
||||||
|
delete() - delete this channel
|
||||||
|
message_transform(msg, emit=False, prefix=True,
|
||||||
|
sender_strings=None, external=False) - called by
|
||||||
|
the comm system and triggers the hooks below
|
||||||
|
msg(msgobj, header=None, senders=None, sender_strings=None,
|
||||||
|
persistent=None, online=False, emit=False, external=False) - main
|
||||||
|
send method, builds and sends a new message to channel.
|
||||||
|
tempmsg(msg, header=None, senders=None) - wrapper for sending non-persistent
|
||||||
|
messages.
|
||||||
|
distribute_message(msg, online=False) - send a message to all
|
||||||
|
connected accounts on channel, optionally sending only
|
||||||
|
to accounts that are currently online (optimized for very large sends)
|
||||||
|
mute(subscriber, **kwargs)
|
||||||
|
unmute(subscriber, **kwargs)
|
||||||
|
ban(target, **kwargs)
|
||||||
|
unban(target, **kwargs)
|
||||||
|
add_user_channel_alias(user, alias, **kwargs)
|
||||||
|
remove_user_channel_alias(user, alias, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
Useful hooks:
|
||||||
|
at_channel_creation() - called once, when the channel is created
|
||||||
|
basetype_setup()
|
||||||
|
at_init()
|
||||||
|
at_first_save()
|
||||||
|
channel_prefix() - how the channel should be
|
||||||
|
prefixed when returning to user. Returns a string
|
||||||
|
format_senders(senders) - should return how to display multiple
|
||||||
|
senders to a channel
|
||||||
|
pose_transform(msg, sender_string) - should detect if the
|
||||||
|
sender is posing, and if so, modify the string
|
||||||
|
format_external(msg, senders, emit=False) - format messages sent
|
||||||
|
from outside the game, like from IRC
|
||||||
|
format_message(msg, emit=False) - format the message body before
|
||||||
|
displaying it to the user. 'emit' generally means that the
|
||||||
|
message should not be displayed with the sender's name.
|
||||||
|
channel_prefix()
|
||||||
|
|
||||||
|
pre_join_channel(joiner) - if returning False, abort join
|
||||||
|
post_join_channel(joiner) - called right after successful join
|
||||||
|
pre_leave_channel(leaver) - if returning False, abort leave
|
||||||
|
post_leave_channel(leaver) - called right after successful leave
|
||||||
|
at_pre_msg(message, **kwargs)
|
||||||
|
at_post_msg(message, **kwargs)
|
||||||
|
web_get_admin_url()
|
||||||
|
web_get_create_url()
|
||||||
|
web_get_detail_url()
|
||||||
|
web_get_update_url()
|
||||||
|
web_get_delete_url()
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
26
typeclasses/characters.py
Normal file
26
typeclasses/characters.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
"""
|
||||||
|
Characters
|
||||||
|
|
||||||
|
Characters are (by default) Objects setup to be puppeted by Accounts.
|
||||||
|
They are what you "see" in game. The Character class in this module
|
||||||
|
is setup to be the "default" character type created by the default
|
||||||
|
creation commands.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from evennia.objects.objects import DefaultCharacter
|
||||||
|
|
||||||
|
from .objects import ObjectParent
|
||||||
|
|
||||||
|
|
||||||
|
class Character(ObjectParent, DefaultCharacter):
|
||||||
|
"""
|
||||||
|
The Character just re-implements some of the Object's methods and hooks
|
||||||
|
to represent a Character entity in-game.
|
||||||
|
|
||||||
|
See mygame/typeclasses/objects.py for a list of
|
||||||
|
properties and methods available on all Object child classes like this.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
26
typeclasses/exits.py
Normal file
26
typeclasses/exits.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
"""
|
||||||
|
Exits
|
||||||
|
|
||||||
|
Exits are connectors between Rooms. An exit always has a destination property
|
||||||
|
set and has a single command defined on itself with the same name as its key,
|
||||||
|
for allowing Characters to traverse the exit to its destination.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from evennia.objects.objects import DefaultExit
|
||||||
|
|
||||||
|
from .objects import ObjectParent
|
||||||
|
|
||||||
|
|
||||||
|
class Exit(ObjectParent, DefaultExit):
|
||||||
|
"""
|
||||||
|
Exits are connectors between rooms. Exits are normal Objects except
|
||||||
|
they defines the `destination` property and overrides some hooks
|
||||||
|
and methods to represent the exits.
|
||||||
|
|
||||||
|
See mygame/typeclasses/objects.py for a list of
|
||||||
|
properties and methods available on all Objects child classes like this.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
217
typeclasses/objects.py
Normal file
217
typeclasses/objects.py
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
"""
|
||||||
|
Object
|
||||||
|
|
||||||
|
The Object is the class for general items in the game world.
|
||||||
|
|
||||||
|
Use the ObjectParent class to implement common features for *all* entities
|
||||||
|
with a location in the game world (like Characters, Rooms, Exits).
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from evennia.objects.objects import DefaultObject
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectParent:
|
||||||
|
"""
|
||||||
|
This is a mixin that can be used to override *all* entities inheriting at
|
||||||
|
some distance from DefaultObject (Objects, Exits, Characters and Rooms).
|
||||||
|
|
||||||
|
Just add any method that exists on `DefaultObject` to this class. If one
|
||||||
|
of the derived classes has itself defined that same hook already, that will
|
||||||
|
take precedence.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Object(ObjectParent, DefaultObject):
|
||||||
|
"""
|
||||||
|
This is the root Object typeclass, representing all entities that
|
||||||
|
have an actual presence in-game. DefaultObjects generally have a
|
||||||
|
location. They can also be manipulated and looked at. Game
|
||||||
|
entities you define should inherit from DefaultObject at some distance.
|
||||||
|
|
||||||
|
It is recommended to create children of this class using the
|
||||||
|
`evennia.create_object()` function rather than to initialize the class
|
||||||
|
directly - this will both set things up and efficiently save the object
|
||||||
|
without `obj.save()` having to be called explicitly.
|
||||||
|
|
||||||
|
Note: Check the autodocs for complete class members, this may not always
|
||||||
|
be up-to date.
|
||||||
|
|
||||||
|
* Base properties defined/available on all Objects
|
||||||
|
|
||||||
|
key (string) - name of object
|
||||||
|
name (string)- same as key
|
||||||
|
dbref (int, read-only) - unique #id-number. Also "id" can be used.
|
||||||
|
date_created (string) - time stamp of object creation
|
||||||
|
|
||||||
|
account (Account) - controlling account (if any, only set together with
|
||||||
|
sessid below)
|
||||||
|
sessid (int, read-only) - session id (if any, only set together with
|
||||||
|
account above). Use `sessions` handler to get the
|
||||||
|
Sessions directly.
|
||||||
|
location (Object) - current location. Is None if this is a room
|
||||||
|
home (Object) - safety start-location
|
||||||
|
has_account (bool, read-only)- will only return *connected* accounts
|
||||||
|
contents (list, read only) - returns all objects inside this object
|
||||||
|
exits (list of Objects, read-only) - returns all exits from this
|
||||||
|
object, if any
|
||||||
|
destination (Object) - only set if this object is an exit.
|
||||||
|
is_superuser (bool, read-only) - True/False if this user is a superuser
|
||||||
|
is_connected (bool, read-only) - True if this object is associated with
|
||||||
|
an Account with any connected sessions.
|
||||||
|
has_account (bool, read-only) - True is this object has an associated account.
|
||||||
|
is_superuser (bool, read-only): True if this object has an account and that
|
||||||
|
account is a superuser.
|
||||||
|
|
||||||
|
* Handlers available
|
||||||
|
|
||||||
|
aliases - alias-handler: use aliases.add/remove/get() to use.
|
||||||
|
permissions - permission-handler: use permissions.add/remove() to
|
||||||
|
add/remove new perms.
|
||||||
|
locks - lock-handler: use locks.add() to add new lock strings
|
||||||
|
scripts - script-handler. Add new scripts to object with scripts.add()
|
||||||
|
cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object
|
||||||
|
nicks - nick-handler. New nicks with nicks.add().
|
||||||
|
sessions - sessions-handler. Get Sessions connected to this
|
||||||
|
object with sessions.get()
|
||||||
|
attributes - attribute-handler. Use attributes.add/remove/get.
|
||||||
|
db - attribute-handler: Shortcut for attribute-handler. Store/retrieve
|
||||||
|
database attributes using self.db.myattr=val, val=self.db.myattr
|
||||||
|
ndb - non-persistent attribute handler: same as db but does not create
|
||||||
|
a database entry when storing data
|
||||||
|
|
||||||
|
* Helper methods (see src.objects.objects.py for full headers)
|
||||||
|
|
||||||
|
get_search_query_replacement(searchdata, **kwargs)
|
||||||
|
get_search_direct_match(searchdata, **kwargs)
|
||||||
|
get_search_candidates(searchdata, **kwargs)
|
||||||
|
get_search_result(searchdata, attribute_name=None, typeclass=None,
|
||||||
|
candidates=None, exact=False, use_dbref=None, tags=None, **kwargs)
|
||||||
|
get_stacked_result(results, **kwargs)
|
||||||
|
handle_search_results(searchdata, results, **kwargs)
|
||||||
|
search(searchdata, global_search=False, use_nicks=True, typeclass=None,
|
||||||
|
location=None, attribute_name=None, quiet=False, exact=False,
|
||||||
|
candidates=None, use_locks=True, nofound_string=None,
|
||||||
|
multimatch_string=None, use_dbref=None, tags=None, stacked=0)
|
||||||
|
search_account(searchdata, quiet=False)
|
||||||
|
execute_cmd(raw_string, session=None, **kwargs))
|
||||||
|
msg(text=None, from_obj=None, session=None, options=None, **kwargs)
|
||||||
|
for_contents(func, exclude=None, **kwargs)
|
||||||
|
msg_contents(message, exclude=None, from_obj=None, mapping=None,
|
||||||
|
raise_funcparse_errors=False, **kwargs)
|
||||||
|
move_to(destination, quiet=False, emit_to_obj=None, use_destination=True)
|
||||||
|
clear_contents()
|
||||||
|
create(key, account, caller, method, **kwargs)
|
||||||
|
copy(new_key=None)
|
||||||
|
at_object_post_copy(new_obj, **kwargs)
|
||||||
|
delete()
|
||||||
|
is_typeclass(typeclass, exact=False)
|
||||||
|
swap_typeclass(new_typeclass, clean_attributes=False, no_default=True)
|
||||||
|
access(accessing_obj, access_type='read', default=False,
|
||||||
|
no_superuser_bypass=False, **kwargs)
|
||||||
|
filter_visible(obj_list, looker, **kwargs)
|
||||||
|
get_default_lockstring()
|
||||||
|
get_cmdsets(caller, current, **kwargs)
|
||||||
|
check_permstring(permstring)
|
||||||
|
get_cmdset_providers()
|
||||||
|
get_display_name(looker=None, **kwargs)
|
||||||
|
get_extra_display_name_info(looker=None, **kwargs)
|
||||||
|
get_numbered_name(count, looker, **kwargs)
|
||||||
|
get_display_header(looker, **kwargs)
|
||||||
|
get_display_desc(looker, **kwargs)
|
||||||
|
get_display_exits(looker, **kwargs)
|
||||||
|
get_display_characters(looker, **kwargs)
|
||||||
|
get_display_things(looker, **kwargs)
|
||||||
|
get_display_footer(looker, **kwargs)
|
||||||
|
format_appearance(appearance, looker, **kwargs)
|
||||||
|
return_apperance(looker, **kwargs)
|
||||||
|
|
||||||
|
* Hooks (these are class methods, so args should start with self):
|
||||||
|
|
||||||
|
basetype_setup() - only called once, used for behind-the-scenes
|
||||||
|
setup. Normally not modified.
|
||||||
|
basetype_posthook_setup() - customization in basetype, after the object
|
||||||
|
has been created; Normally not modified.
|
||||||
|
|
||||||
|
at_object_creation() - only called once, when object is first created.
|
||||||
|
Object customizations go here.
|
||||||
|
at_object_delete() - called just before deleting an object. If returning
|
||||||
|
False, deletion is aborted. Note that all objects
|
||||||
|
inside a deleted object are automatically moved
|
||||||
|
to their <home>, they don't need to be removed here.
|
||||||
|
|
||||||
|
at_init() - called whenever typeclass is cached from memory,
|
||||||
|
at least once every server restart/reload
|
||||||
|
at_first_save()
|
||||||
|
at_cmdset_get(**kwargs) - this is called just before the command handler
|
||||||
|
requests a cmdset from this object. The kwargs are
|
||||||
|
not normally used unless the cmdset is created
|
||||||
|
dynamically (see e.g. Exits).
|
||||||
|
at_pre_puppet(account)- (account-controlled objects only) called just
|
||||||
|
before puppeting
|
||||||
|
at_post_puppet() - (account-controlled objects only) called just
|
||||||
|
after completing connection account<->object
|
||||||
|
at_pre_unpuppet() - (account-controlled objects only) called just
|
||||||
|
before un-puppeting
|
||||||
|
at_post_unpuppet(account) - (account-controlled objects only) called just
|
||||||
|
after disconnecting account<->object link
|
||||||
|
at_server_reload() - called before server is reloaded
|
||||||
|
at_server_shutdown() - called just before server is fully shut down
|
||||||
|
|
||||||
|
at_access(result, accessing_obj, access_type) - called with the result
|
||||||
|
of a lock access check on this object. Return value
|
||||||
|
does not affect check result.
|
||||||
|
|
||||||
|
at_pre_move(destination) - called just before moving object
|
||||||
|
to the destination. If returns False, move is cancelled.
|
||||||
|
announce_move_from(destination) - called in old location, just
|
||||||
|
before move, if obj.move_to() has quiet=False
|
||||||
|
announce_move_to(source_location) - called in new location, just
|
||||||
|
after move, if obj.move_to() has quiet=False
|
||||||
|
at_post_move(source_location) - always called after a move has
|
||||||
|
been successfully performed.
|
||||||
|
at_pre_object_leave(leaving_object, destination, **kwargs)
|
||||||
|
at_object_leave(obj, target_location, move_type="move", **kwargs)
|
||||||
|
at_object_leave(obj, target_location) - called when an object leaves
|
||||||
|
this object in any fashion
|
||||||
|
at_pre_object_receive(obj, source_location)
|
||||||
|
at_object_receive(obj, source_location, move_type="move", **kwargs) - called when this object receives
|
||||||
|
another object
|
||||||
|
at_post_move(source_location, move_type="move", **kwargs)
|
||||||
|
|
||||||
|
at_traverse(traversing_object, target_location, **kwargs) - (exit-objects only)
|
||||||
|
handles all moving across the exit, including
|
||||||
|
calling the other exit hooks. Use super() to retain
|
||||||
|
the default functionality.
|
||||||
|
at_post_traverse(traversing_object, source_location) - (exit-objects only)
|
||||||
|
called just after a traversal has happened.
|
||||||
|
at_failed_traverse(traversing_object) - (exit-objects only) called if
|
||||||
|
traversal fails and property err_traverse is not defined.
|
||||||
|
|
||||||
|
at_msg_receive(self, msg, from_obj=None, **kwargs) - called when a message
|
||||||
|
(via self.msg()) is sent to this obj.
|
||||||
|
If returns false, aborts send.
|
||||||
|
at_msg_send(self, msg, to_obj=None, **kwargs) - called when this objects
|
||||||
|
sends a message to someone via self.msg().
|
||||||
|
|
||||||
|
return_appearance(looker) - describes this object. Used by "look"
|
||||||
|
command by default
|
||||||
|
at_desc(looker=None) - called by 'look' whenever the
|
||||||
|
appearance is requested.
|
||||||
|
at_pre_get(getter, **kwargs)
|
||||||
|
at_get(getter) - called after object has been picked up.
|
||||||
|
Does not stop pickup.
|
||||||
|
at_pre_give(giver, getter, **kwargs)
|
||||||
|
at_give(giver, getter, **kwargs)
|
||||||
|
at_pre_drop(dropper, **kwargs)
|
||||||
|
at_drop(dropper, **kwargs) - called when this object has been dropped.
|
||||||
|
at_pre_say(speaker, message, **kwargs)
|
||||||
|
at_say(message, msg_self=None, msg_location=None, receivers=None, msg_receivers=None, **kwargs)
|
||||||
|
|
||||||
|
at_look(target, **kwargs)
|
||||||
|
at_desc(looker=None)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
24
typeclasses/rooms.py
Normal file
24
typeclasses/rooms.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
"""
|
||||||
|
Room
|
||||||
|
|
||||||
|
Rooms are simple containers that has no location of their own.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from evennia.objects.objects import DefaultRoom
|
||||||
|
|
||||||
|
from .objects import ObjectParent
|
||||||
|
|
||||||
|
|
||||||
|
class Room(ObjectParent, DefaultRoom):
|
||||||
|
"""
|
||||||
|
Rooms are like any Object, except their location is None
|
||||||
|
(which is default). They also use basetype_setup() to
|
||||||
|
add locks so they cannot be puppeted or picked up.
|
||||||
|
(to change that, use at_object_creation instead)
|
||||||
|
|
||||||
|
See mygame/typeclasses/objects.py for a list of
|
||||||
|
properties and methods available on all Objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
103
typeclasses/scripts.py
Normal file
103
typeclasses/scripts.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
"""
|
||||||
|
Scripts
|
||||||
|
|
||||||
|
Scripts are powerful jacks-of-all-trades. They have no in-game
|
||||||
|
existence and can be used to represent persistent game systems in some
|
||||||
|
circumstances. Scripts can also have a time component that allows them
|
||||||
|
to "fire" regularly or a limited number of times.
|
||||||
|
|
||||||
|
There is generally no "tree" of Scripts inheriting from each other.
|
||||||
|
Rather, each script tends to inherit from the base Script class and
|
||||||
|
just overloads its hooks to have it perform its function.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from evennia.scripts.scripts import DefaultScript
|
||||||
|
|
||||||
|
|
||||||
|
class Script(DefaultScript):
|
||||||
|
"""
|
||||||
|
This is the base TypeClass for all Scripts. Scripts describe
|
||||||
|
all entities/systems without a physical existence in the game world
|
||||||
|
that require database storage (like an economic system or
|
||||||
|
combat tracker). They
|
||||||
|
can also have a timer/ticker component.
|
||||||
|
|
||||||
|
A script type is customized by redefining some or all of its hook
|
||||||
|
methods and variables.
|
||||||
|
|
||||||
|
* available properties (check docs for full listing, this could be
|
||||||
|
outdated).
|
||||||
|
|
||||||
|
key (string) - name of object
|
||||||
|
name (string)- same as key
|
||||||
|
aliases (list of strings) - aliases to the object. Will be saved
|
||||||
|
to database as AliasDB entries but returned as strings.
|
||||||
|
dbref (int, read-only) - unique #id-number. Also "id" can be used.
|
||||||
|
date_created (string) - time stamp of object creation
|
||||||
|
permissions (list of strings) - list of permission strings
|
||||||
|
|
||||||
|
desc (string) - optional description of script, shown in listings
|
||||||
|
obj (Object) - optional object that this script is connected to
|
||||||
|
and acts on (set automatically by obj.scripts.add())
|
||||||
|
interval (int) - how often script should run, in seconds. <0 turns
|
||||||
|
off ticker
|
||||||
|
start_delay (bool) - if the script should start repeating right away or
|
||||||
|
wait self.interval seconds
|
||||||
|
repeats (int) - how many times the script should repeat before
|
||||||
|
stopping. 0 means infinite repeats
|
||||||
|
persistent (bool) - if script should survive a server shutdown or not
|
||||||
|
is_active (bool) - if script is currently running
|
||||||
|
|
||||||
|
* Handlers
|
||||||
|
|
||||||
|
locks - lock-handler: use locks.add() to add new lock strings
|
||||||
|
db - attribute-handler: store/retrieve database attributes on this
|
||||||
|
self.db.myattr=val, val=self.db.myattr
|
||||||
|
ndb - non-persistent attribute handler: same as db but does not
|
||||||
|
create a database entry when storing data
|
||||||
|
|
||||||
|
* Helper methods
|
||||||
|
|
||||||
|
create(key, **kwargs)
|
||||||
|
start() - start script (this usually happens automatically at creation
|
||||||
|
and obj.script.add() etc)
|
||||||
|
stop() - stop script, and delete it
|
||||||
|
pause() - put the script on hold, until unpause() is called. If script
|
||||||
|
is persistent, the pause state will survive a shutdown.
|
||||||
|
unpause() - restart a previously paused script. The script will continue
|
||||||
|
from the paused timer (but at_start() will be called).
|
||||||
|
time_until_next_repeat() - if a timed script (interval>0), returns time
|
||||||
|
until next tick
|
||||||
|
|
||||||
|
* Hook methods (should also include self as the first argument):
|
||||||
|
|
||||||
|
at_script_creation() - called only once, when an object of this
|
||||||
|
class is first created.
|
||||||
|
is_valid() - is called to check if the script is valid to be running
|
||||||
|
at the current time. If is_valid() returns False, the running
|
||||||
|
script is stopped and removed from the game. You can use this
|
||||||
|
to check state changes (i.e. an script tracking some combat
|
||||||
|
stats at regular intervals is only valid to run while there is
|
||||||
|
actual combat going on).
|
||||||
|
at_start() - Called every time the script is started, which for persistent
|
||||||
|
scripts is at least once every server start. Note that this is
|
||||||
|
unaffected by self.delay_start, which only delays the first
|
||||||
|
call to at_repeat().
|
||||||
|
at_repeat() - Called every self.interval seconds. It will be called
|
||||||
|
immediately upon launch unless self.delay_start is True, which
|
||||||
|
will delay the first call of this method by self.interval
|
||||||
|
seconds. If self.interval==0, this method will never
|
||||||
|
be called.
|
||||||
|
at_pause()
|
||||||
|
at_stop() - Called as the script object is stopped and is about to be
|
||||||
|
removed from the game, e.g. because is_valid() returned False.
|
||||||
|
at_script_delete()
|
||||||
|
at_server_reload() - Called when server reloads. Can be used to
|
||||||
|
save temporary variables you want should survive a reload.
|
||||||
|
at_server_shutdown() - called at a full server shutdown.
|
||||||
|
at_server_start()
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
51
web/README.md
Normal file
51
web/README.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Web
|
||||||
|
|
||||||
|
This folder contains overriding of web assets - the website and webclient
|
||||||
|
coming with the game.
|
||||||
|
|
||||||
|
This is the process for serving a new web site (see also the Django docs for
|
||||||
|
more details):
|
||||||
|
|
||||||
|
1. A user enters an url in their browser (or clicks a button). This leads to
|
||||||
|
the browser sending a _HTTP request_ to the server, with a specific type
|
||||||
|
(GET,POST etc) and url-path (like for `https://localhost:4001/`, the part of
|
||||||
|
the url we need to consider is `/`).
|
||||||
|
2. Evennia (through Django) will make use of the regular expressions registered
|
||||||
|
in the `urls.py` file. This acts as a rerouter to _views_, which are
|
||||||
|
regular Python functions able to process the incoming request (think of
|
||||||
|
these as similar to the right Evennia Command being selected to handle your
|
||||||
|
input - views are like Commands in this sense). In the case of `/` we
|
||||||
|
reroute to a view handling the main index-page of the website. The view is
|
||||||
|
either a function or a callable class (Evennia tends to have them as
|
||||||
|
functions).
|
||||||
|
3. The view-function will prepare all the data needed by the web page. For the default
|
||||||
|
index page, this means gather the game statistics so you can see how many
|
||||||
|
are currently connected to the game etc.
|
||||||
|
4. The view will next fetch a _template_. A template is a HTML-document with special
|
||||||
|
'placeholder' tags (written as `{{...}}` or `{% ... %}` usually). These
|
||||||
|
placeholders allow the view to inject dynamic content into the HTML and make
|
||||||
|
the page customized to the current situation. For the index page, it means
|
||||||
|
injecting the current player-count in the right places of the html page. This
|
||||||
|
is called 'rendering' the template. The result is a complete HTML page.
|
||||||
|
5. (The view can also pull in a _form_ to customize user-input in a similar way.)
|
||||||
|
6. The finished HTML page is packed in a _HTTP response_ and is returned to the
|
||||||
|
web browser, which can now display the page!
|
||||||
|
|
||||||
|
## A note on the webclient
|
||||||
|
|
||||||
|
The web browser can also execute code directly without talking to the Server.
|
||||||
|
This code must be written/loaded into the web page and is written using the
|
||||||
|
Javascript programming language (there is no way around this, it is what web
|
||||||
|
browsers understand). Executing Javascript is something the web browser does,
|
||||||
|
it operates independently from Evennia. Small snippets of javascript can be
|
||||||
|
used on a page to have buttons react, make small animations etc that doesn't
|
||||||
|
require the server.
|
||||||
|
|
||||||
|
In the case of the Webclient, Evennia will load the Webclient page as above,
|
||||||
|
but the page then contains Javascript code responsible for actually displaying
|
||||||
|
the client GUI, allows you to resize windows etc.
|
||||||
|
|
||||||
|
After it starts, the webclient 'calls home' and spins up a websocket link to
|
||||||
|
the Evennia Portal - this is how all data is then exchanged. So after the
|
||||||
|
initial loading of the webclient page, the above sequence doesn't happen again
|
||||||
|
until close the tab and come back or you reload it manually in your browser.
|
||||||
0
web/__init__.py
Normal file
0
web/__init__.py
Normal file
5
web/admin/README.md
Normal file
5
web/admin/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Admin views
|
||||||
|
|
||||||
|
Evennia makes several customizations to the Django web admin, but you can make
|
||||||
|
further changes here. Customizing the admin is a big topic and
|
||||||
|
you are best off reading more about it in the [Django admin site documentation](https://docs.djangoproject.com/en/4.1/ref/contrib/admin/).
|
||||||
0
web/admin/__init__.py
Normal file
0
web/admin/__init__.py
Normal file
20
web/admin/urls.py
Normal file
20
web/admin/urls.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
"""
|
||||||
|
This reroutes from an URL to a python view-function/class.
|
||||||
|
|
||||||
|
The main web/urls.py includes these routes for all urls starting with `admin/`
|
||||||
|
(the `admin/` part should not be included again here).
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from evennia.web.admin.urls import urlpatterns as evennia_admin_urlpatterns
|
||||||
|
|
||||||
|
# add patterns here
|
||||||
|
urlpatterns = [
|
||||||
|
# path("url-pattern", imported_python_view),
|
||||||
|
# path("url-pattern", imported_python_view),
|
||||||
|
]
|
||||||
|
|
||||||
|
# read by Django
|
||||||
|
urlpatterns = urlpatterns + evennia_admin_urlpatterns
|
||||||
0
web/api/__init__.py
Normal file
0
web/api/__init__.py
Normal file
17
web/static/README.md
Normal file
17
web/static/README.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
## Static files
|
||||||
|
|
||||||
|
This is the place to put static resources you want to serve from the
|
||||||
|
Evennia server. This is usually CSS and Javascript files but you _could_ also
|
||||||
|
serve other media like images, videos and music files from here.
|
||||||
|
|
||||||
|
> If you serve a lot of large files (especially videos) you will see a lot
|
||||||
|
> better performance doing so from a separate dedicated media host.
|
||||||
|
|
||||||
|
You can also override default Evennia files from here. The original files are
|
||||||
|
found in `evennia/web/static/`. Copy the original file into the same
|
||||||
|
corresponding location/sublocation in this folder (such as website CSS files
|
||||||
|
into `mygame/static/website/css/`) and modify it, then reload the server.
|
||||||
|
|
||||||
|
Note that all static resources will be collected from all over Evennia into
|
||||||
|
`mygame/server/.static` for serving by the webserver. That folder should not be
|
||||||
|
modified manually.
|
||||||
3
web/static/rest_framework/css/README.md
Normal file
3
web/static/rest_framework/css/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Evennia API static files
|
||||||
|
|
||||||
|
Overrides for API files.
|
||||||
3
web/static/rest_framework/images/README.md
Normal file
3
web/static/rest_framework/images/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Static files for API
|
||||||
|
|
||||||
|
Override images here.
|
||||||
3
web/static/webclient/css/README.md
Normal file
3
web/static/webclient/css/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
You can replace the CSS files for Evennia's webclient here.
|
||||||
|
|
||||||
|
You can find the original files in `evennia/web/static/webclient/css/`
|
||||||
3
web/static/webclient/js/README.md
Normal file
3
web/static/webclient/js/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
You can replace the javascript files for Evennia's webclient page here.
|
||||||
|
|
||||||
|
You can find the original files in `evennia/web/static/webclient/js/`
|
||||||
3
web/static/website/css/README.md
Normal file
3
web/static/website/css/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
You can replace the CSS files for Evennia's homepage here.
|
||||||
|
|
||||||
|
You can find the original files in `evennia/web/static/website/css/`
|
||||||
3
web/static/website/images/README.md
Normal file
3
web/static/website/images/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
You can replace the image files for Evennia's home page here.
|
||||||
|
|
||||||
|
You can find the original files in `evennia/web/static/website/images/`
|
||||||
14
web/templates/README.md
Normal file
14
web/templates/README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# HTML templates
|
||||||
|
|
||||||
|
Templates are HTML files that (usually) have special `{{ ... }}` template
|
||||||
|
markers in them to allow Evennia/Django to insert dynamic content in a web
|
||||||
|
page. An example is listing how many users are currently online in the game.
|
||||||
|
|
||||||
|
Templates are referenced by _views_ - Python functions or callable classes that
|
||||||
|
prepare the data needed by the template and 'renders' them into a finished
|
||||||
|
HTML page to return to the user.
|
||||||
|
|
||||||
|
You can replace Evennia's default templates by overriding them in this folder.
|
||||||
|
The originals are in `evennia/web/templates/` - just copy the template into the
|
||||||
|
corresponding location here (so the website's `index.html` should be copied to
|
||||||
|
`website/index.html` where it can be modified). Reload the server to see your changes.
|
||||||
3
web/templates/rest_framework/README.md
Normal file
3
web/templates/rest_framework/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Templates for the Evennia API
|
||||||
|
|
||||||
|
Override templates here.
|
||||||
4
web/templates/webclient/README.md
Normal file
4
web/templates/webclient/README.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Replace Evennia's webclient django template with your own here.
|
||||||
|
|
||||||
|
You can find the original files in `evennia/web/templates/webclient/`. Just copy
|
||||||
|
the original here and modify - after a reload the new template will be used.
|
||||||
5
web/templates/website/README.md
Normal file
5
web/templates/website/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
You can replace the django templates (html files) for the website
|
||||||
|
here.
|
||||||
|
|
||||||
|
You can find the original files under `evennia/web/templates/website/`. Just
|
||||||
|
copy a template here and modify to have it override the default.
|
||||||
3
web/templates/website/flatpages/README.md
Normal file
3
web/templates/website/flatpages/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Flatpages require a default.html template, which can be overwritten by placing it in this folder.
|
||||||
|
|
||||||
|
You can find the original files in `evennia/web/website/templates/website/flatpages/`
|
||||||
3
web/templates/website/registration/README.md
Normal file
3
web/templates/website/registration/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
The templates involving login/logout can be overwritten here.
|
||||||
|
|
||||||
|
You can find the original files in `evennia/web/website/templates/website/registration/`
|
||||||
34
web/urls.py
Normal file
34
web/urls.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
"""
|
||||||
|
This is the starting point when a user enters a url in their web browser.
|
||||||
|
|
||||||
|
The urls is matched (by regex) and mapped to a 'view' - a Python function or
|
||||||
|
callable class that in turn (usually) makes use of a 'template' (a html file
|
||||||
|
with slots that can be replaced by dynamic content) in order to render a HTML
|
||||||
|
page to show the user.
|
||||||
|
|
||||||
|
This file includes the urls in website, webclient and admin. To override you
|
||||||
|
should modify urls.py in those sub directories.
|
||||||
|
|
||||||
|
Search the Django documentation for "URL dispatcher" for more help.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.urls import include, path
|
||||||
|
|
||||||
|
# default evennia patterns
|
||||||
|
from evennia.web.urls import urlpatterns as evennia_default_urlpatterns
|
||||||
|
|
||||||
|
# add patterns
|
||||||
|
urlpatterns = [
|
||||||
|
# website
|
||||||
|
path("", include("web.website.urls")),
|
||||||
|
# webclient
|
||||||
|
path("webclient/", include("web.webclient.urls")),
|
||||||
|
# web admin
|
||||||
|
path("admin/", include("web.admin.urls")),
|
||||||
|
# add any extra urls here:
|
||||||
|
# path("mypath/", include("path.to.my.urls.file")),
|
||||||
|
]
|
||||||
|
|
||||||
|
# 'urlpatterns' must be named such for Django to find it.
|
||||||
|
urlpatterns = urlpatterns + evennia_default_urlpatterns
|
||||||
23
web/webclient/README.md
Normal file
23
web/webclient/README.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Webclient Views
|
||||||
|
|
||||||
|
The webclient is mainly controlled by Javascript directly in the browser, so
|
||||||
|
you usually customize it via `mygame/web/static/webclient/js/` - files instead.
|
||||||
|
|
||||||
|
There is very little you can change from here, unless you want to implement
|
||||||
|
your very own client from scratch.
|
||||||
|
|
||||||
|
## On views
|
||||||
|
|
||||||
|
A 'view' is python code (a function or callable class) responsible for
|
||||||
|
producing a HTML page for a user to view in response for going to a given URL
|
||||||
|
in their browser. In Evennia lingo, it's similar in function to a Command, with
|
||||||
|
the input/args being the URL/request and the output being a new web-page.
|
||||||
|
|
||||||
|
The urls.py file contains regular expressions that are run against the provided
|
||||||
|
URL - when a match is found, the execution is passed to a view which is then
|
||||||
|
responsible (usually) for producing the web page by filling in a _template_ - a
|
||||||
|
HTML document that can have special tags in it that are replaced for dynamic
|
||||||
|
content. It then returns the finished HTML page for the user to view.
|
||||||
|
|
||||||
|
See the [Django docs on Views](https://docs.djangoproject.com/en/4.1/topics/http/views/) for
|
||||||
|
more information.
|
||||||
0
web/webclient/__init__.py
Normal file
0
web/webclient/__init__.py
Normal file
20
web/webclient/urls.py
Normal file
20
web/webclient/urls.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
"""
|
||||||
|
This reroutes from an URL to a python view-function/class.
|
||||||
|
|
||||||
|
The main web/urls.py includes these routes for all urls starting with `webclient/`
|
||||||
|
(the `webclient/` part should not be included again here).
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from evennia.web.webclient.urls import urlpatterns as evennia_webclient_urlpatterns
|
||||||
|
|
||||||
|
# add patterns here
|
||||||
|
urlpatterns = [
|
||||||
|
# path("url-pattern", imported_python_view),
|
||||||
|
# path("url-pattern", imported_python_view),
|
||||||
|
]
|
||||||
|
|
||||||
|
# read by Django
|
||||||
|
urlpatterns = urlpatterns + evennia_webclient_urlpatterns
|
||||||
24
web/website/README.md
Normal file
24
web/website/README.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Website views and other code
|
||||||
|
|
||||||
|
A 'view' is python code (a function or callable class) responsible for
|
||||||
|
producing a HTML page for a user to view in response for going to a given URL
|
||||||
|
in their browser. In Evennia lingo, it's similar in function to a Command, with
|
||||||
|
the input/args being the URL/request and the output being a new web-page.
|
||||||
|
|
||||||
|
The urls.py file contains regular expressions that are run against the provided
|
||||||
|
URL - when a match is found, the execution is passed to a view which is then
|
||||||
|
responsible (usually) for producing the web page by filling in a _template_ - a
|
||||||
|
HTML document that can have special tags in it that are replaced for dynamic
|
||||||
|
content. It then returns the finished HTML page for the user to view.
|
||||||
|
|
||||||
|
See the [Django docs on Views](https://docs.djangoproject.com/en/4.1/topics/http/views/) for
|
||||||
|
more information.
|
||||||
|
|
||||||
|
## Overriding a view
|
||||||
|
|
||||||
|
1. Copy the original code you want to change from `evennia/web/website/views/` into
|
||||||
|
`mygame/web/website/views/` and edit it as you like.
|
||||||
|
2. Look at `evennia/web/website/urls.py` and find the regex pointing to the view. Add this regex
|
||||||
|
to your own `mygam/website/urls.pye` but change it to import and point to your
|
||||||
|
changed version instead.
|
||||||
|
3. Reload the server and the page now uses your version of the view.
|
||||||
0
web/website/__init__.py
Normal file
0
web/website/__init__.py
Normal file
20
web/website/urls.py
Normal file
20
web/website/urls.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
"""
|
||||||
|
This reroutes from an URL to a python view-function/class.
|
||||||
|
|
||||||
|
The main web/urls.py includes these routes for all urls (the root of the url)
|
||||||
|
so it can reroute to all website pages.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from evennia.web.website.urls import urlpatterns as evennia_website_urlpatterns
|
||||||
|
|
||||||
|
# add patterns here
|
||||||
|
urlpatterns = [
|
||||||
|
# path("url-pattern", imported_python_view),
|
||||||
|
# path("url-pattern", imported_python_view),
|
||||||
|
]
|
||||||
|
|
||||||
|
# read by Django
|
||||||
|
urlpatterns = urlpatterns + evennia_website_urlpatterns
|
||||||
0
web/website/views/__init__.py
Normal file
0
web/website/views/__init__.py
Normal file
224
world/BUILDER_GUIDE.md
Normal file
224
world/BUILDER_GUIDE.md
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
# Evennia World Builder's Guide
|
||||||
|
## Academy Setting - Complete Room Documentation
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
This world expansion adds **20 detailed rooms** across **4 themed wings** of an arcane academy MUD setting.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Wing 1: The Dormitories
|
||||||
|
*Theme: Rest, privacy, hierarchy of living spaces*
|
||||||
|
|
||||||
|
| Room | Key | Purpose |
|
||||||
|
|------|-----|---------|
|
||||||
|
| Dormitory Entrance | `dorm_entrance` | Main entry, visitor management |
|
||||||
|
| Lodging Master Office | `lodging_office` | Room assignments, administration |
|
||||||
|
| Corridor of Rest | `rest_corridor` | Transition zone, atmosphere building |
|
||||||
|
| Novice Hall | `novice_dorms` | Student housing, communal living |
|
||||||
|
| Master Suites | `master_quarters` | VIP area, senior mage housing |
|
||||||
|
|
||||||
|
**Key Features:**
|
||||||
|
- Sleep wards preventing magical scrying
|
||||||
|
- Silence zones suppressing loud noises
|
||||||
|
- Comfort auras reducing fatigue recovery time
|
||||||
|
- Moon-phase murals tracking actual lunar cycles
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Wing 2: The Commons
|
||||||
|
*Theme: Social interaction, nourishment, community*
|
||||||
|
|
||||||
|
| Room | Key | Purpose |
|
||||||
|
|------|-----|---------|
|
||||||
|
| Grand Commons Hall | `grand_commons` | Central gathering, performances |
|
||||||
|
| Hearthside Dining | `dining_hall` | Food service, feasting |
|
||||||
|
| Scholar's Corner | `study_area` | Quiet reading, contemplation |
|
||||||
|
| Entertainment Gallery | `games_room` | Recreation, performances |
|
||||||
|
| Upper Balcony | `commons_balcony` | Observation, private meetings |
|
||||||
|
|
||||||
|
**Key Features:**
|
||||||
|
- Magical acoustics for performances
|
||||||
|
- Enchanted serving dishes
|
||||||
|
- Educational tapestries
|
||||||
|
- Adjustable lighting systems
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Wing 3: The Workshops
|
||||||
|
*Theme: Crafting, danger, practical magic*
|
||||||
|
|
||||||
|
| Room | Key | Purpose |
|
||||||
|
|------|-----|---------|
|
||||||
|
| Workshop Entrance | `crafting_entry` | Safety station, tool distribution |
|
||||||
|
| Great Smithy | `main_smithy` | Metalworking, armor crafting |
|
||||||
|
| Alchemy Labs | `potion_lab` | Brewing, reagent preparation |
|
||||||
|
| Woodworking Studio | `wood_shop` | Carpentry, wand crafting |
|
||||||
|
| Artificing Chambers | `enchantment_lab` | Magical device creation |
|
||||||
|
|
||||||
|
**Key Features:**
|
||||||
|
- Multiple forge types (normal, magical, dragon-fire)
|
||||||
|
- Fume extraction systems
|
||||||
|
- Tool rental and storage
|
||||||
|
- Safety equipment stations
|
||||||
|
- Power nexus for enchantments
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Wing 4: The Gardens
|
||||||
|
*Theme: Nature, growth, sacred spaces*
|
||||||
|
|
||||||
|
| Room | Key | Purpose |
|
||||||
|
|------|-----|---------|
|
||||||
|
| Gardens Entrance | `gardens_entry` | Transition, information |
|
||||||
|
| Herb Gardens | `herb_beds` | Cultivation, harvesting |
|
||||||
|
| Enchanted Grove | `fey_grove` | Fey magic, wonder |
|
||||||
|
| Greenhouse Complex | `conservatory` | Climate control, exotic plants |
|
||||||
|
| Sacred Grove | `holy_glade` | Rituals, spiritual renewal |
|
||||||
|
|
||||||
|
**Key Features:**
|
||||||
|
- Living vine walls
|
||||||
|
- Bioluminescent flora
|
||||||
|
- Climate-controlled domes
|
||||||
|
- Standing stones and sacred circles
|
||||||
|
- Self-regulating temperature
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Connection Map
|
||||||
|
|
||||||
|
```
|
||||||
|
[ACADEMY MAIN]
|
||||||
|
|
|
||||||
|
+----------------+----------------+
|
||||||
|
| | |
|
||||||
|
[Dormitories] [Commons] [Workshops]
|
||||||
|
| | |
|
||||||
|
- Entrance - Grand Hall - Entrance
|
||||||
|
- Corridor - Dining - Smithy
|
||||||
|
- Novice Hall - Study Corner - Alchemy
|
||||||
|
- Master Suites - Games Room - Woodshop
|
||||||
|
- Office - Balcony - Artificing
|
||||||
|
| | |
|
||||||
|
+----------------+----------------+
|
||||||
|
|
|
||||||
|
[Gardens Entrance]
|
||||||
|
|
|
||||||
|
+----------+----------+
|
||||||
|
| | |
|
||||||
|
[Herb Beds] [Fey Grove] [Greenhouses]
|
||||||
|
| |
|
||||||
|
[Sacred Grove] [Climate Domes]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Atmospheric Consistency
|
||||||
|
|
||||||
|
Each room includes standardized atmospheric data:
|
||||||
|
|
||||||
|
- **mood**: Emotional tone (peaceful, energetic, mysterious)
|
||||||
|
- **lighting**: Visual conditions (bright, dim, magical glow)
|
||||||
|
- **sounds**: Auditory environment (conversation, nature, machinery)
|
||||||
|
- **smells**: Olfactory elements (food, flowers, chemicals)
|
||||||
|
- **temperature**: Thermal conditions (hot, cool, variable)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
### Creating Rooms in Evennia
|
||||||
|
|
||||||
|
```python
|
||||||
|
from evennia import create_object
|
||||||
|
from world.dormitory_entrance import DormitoryEntrance
|
||||||
|
|
||||||
|
# Create the room
|
||||||
|
room = create_object(DormitoryEntrance, key="Dormitory Entrance")
|
||||||
|
|
||||||
|
# The at_object_creation hook automatically sets:
|
||||||
|
# - Room description
|
||||||
|
# - Aliases
|
||||||
|
# - Atmospheric data
|
||||||
|
# - Objects list
|
||||||
|
# - Features dictionary
|
||||||
|
```
|
||||||
|
|
||||||
|
### Connecting Exits
|
||||||
|
|
||||||
|
```python
|
||||||
|
from evennia import create_object
|
||||||
|
|
||||||
|
# Connect two rooms
|
||||||
|
exit1 = create_object("evennia.DefaultExit",
|
||||||
|
key="north",
|
||||||
|
location=room1,
|
||||||
|
destination=room2)
|
||||||
|
exit2 = create_object("evennia.DefaultExit",
|
||||||
|
key="south",
|
||||||
|
location=room2,
|
||||||
|
destination=room1)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Populating Objects
|
||||||
|
|
||||||
|
Objects listed in `self.db.objects` should be created separately:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from evennia import create_object
|
||||||
|
|
||||||
|
for obj_name in room.db.objects[:5]: # First 5 objects
|
||||||
|
obj = create_object("evennia.DefaultObject",
|
||||||
|
key=obj_name.split("-")[0].strip(),
|
||||||
|
location=room)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Content Statistics
|
||||||
|
|
||||||
|
- **Total Rooms**: 20
|
||||||
|
- **Total Wings**: 4
|
||||||
|
- **Average Description Length**: 800+ characters
|
||||||
|
- **Total Atmospheric Elements**: 100 (5 per room)
|
||||||
|
- **Suggested Objects**: 200+ across all rooms
|
||||||
|
- **Exit Connections**: 80+ directional links
|
||||||
|
- **Special Features**: 40+ unique magical elements
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Expansion Hooks
|
||||||
|
|
||||||
|
Each wing includes suggested expansion areas:
|
||||||
|
|
||||||
|
### Dormitories
|
||||||
|
- Individual room descriptions (cell/suite levels)
|
||||||
|
- Bath houses and hygiene facilities
|
||||||
|
- Laundry and maintenance areas
|
||||||
|
|
||||||
|
### Commons
|
||||||
|
- Kitchen complex (detailed cooking areas)
|
||||||
|
- Private dining rooms
|
||||||
|
- Club meeting spaces
|
||||||
|
|
||||||
|
### Workshops
|
||||||
|
- Advanced crafting stations
|
||||||
|
- Material storage vaults
|
||||||
|
- Testing ranges and safety areas
|
||||||
|
|
||||||
|
### Gardens
|
||||||
|
- Individual biome descriptions
|
||||||
|
- Seasonal variation scripts
|
||||||
|
- Creature encounters (fey, elementals)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
**World Design**: Evennia MUD Content Generation
|
||||||
|
**Total Lines of Code**: ~2000
|
||||||
|
**Content Word Count**: ~15,000 words
|
||||||
|
**Files Created**: 5 (.py + .md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*This world package provides a complete, playable foundation for an academic fantasy MUD setting.*
|
||||||
10
world/README.md
Normal file
10
world/README.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# world/
|
||||||
|
|
||||||
|
This folder is meant as a miscellaneous folder for all that other stuff
|
||||||
|
related to the game. Code which are not commands or typeclasses go
|
||||||
|
here, like custom economy systems, combat code, batch-files etc.
|
||||||
|
|
||||||
|
You can restructure and even rename this folder as best fits your
|
||||||
|
sense of organisation. Just remember that if you add new sub
|
||||||
|
directories, you must add (optionally empty) `__init__.py` files in
|
||||||
|
them for Python to be able to find the modules within.
|
||||||
120
world/__init__.py
Normal file
120
world/__init__.py
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
"""
|
||||||
|
Evennia MUD World - Room TypeClass Definitions
|
||||||
|
================================================
|
||||||
|
|
||||||
|
This package contains room definitions for a comprehensive MUD world
|
||||||
|
spanning multiple themed wings of an arcane academy setting.
|
||||||
|
|
||||||
|
WING OVERVIEW:
|
||||||
|
--------------
|
||||||
|
|
||||||
|
1. DORMITORIES WING (dormitory_entrance.py)
|
||||||
|
- DormitoryEntrance: Main entry to sleeping quarters
|
||||||
|
- DormitoryLodgingMaster: Administrative office for room assignments
|
||||||
|
- CorridorOfRest: Main hallway connecting dormitory areas
|
||||||
|
- NoviceDormitoryHall: Student housing for apprentices
|
||||||
|
- MasterSuitesApproach: Senior faculty and master mage quarters
|
||||||
|
|
||||||
|
2. COMMONS WING (commons_wing.py)
|
||||||
|
- GrandCommonsHall: Central social gathering space
|
||||||
|
- HearthsideDining: Main dining facility
|
||||||
|
- ScholarsCorner: Quiet study and reading area
|
||||||
|
- EntertainmentGallery: Games, performances, and recreation
|
||||||
|
- UpperCommonsBalcony: Overlook and private meeting space
|
||||||
|
|
||||||
|
3. WORKSHOP WING (workshop_wing.py)
|
||||||
|
- WorkshopEntrance: Entry point to crafting areas
|
||||||
|
- GreatSmithy: Blacksmithing and metalworking
|
||||||
|
- AlchemyLaboratories: Potion brewing and reagent preparation
|
||||||
|
- WoodworkingStudio: Carpentry and wand crafting
|
||||||
|
- ArtificingChambers: Magical device creation and enchanting
|
||||||
|
|
||||||
|
4. GARDENS WING (gardens_wing.py)
|
||||||
|
- GardensEntrance: Entry to botanical areas
|
||||||
|
- HerbGardens: Medicinal and magical plant cultivation
|
||||||
|
- EnchantedGrove: Fey-touched magical forest area
|
||||||
|
- GreenhouseComplex: Climate-controlled plant environments
|
||||||
|
- SacredGrove: Holy space for druidic and natural rituals
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
------
|
||||||
|
Import specific room classes in your build scripts:
|
||||||
|
|
||||||
|
from world.dormitory_entrance import DormitoryEntrance
|
||||||
|
from world.commons_wing import GrandCommonsHall
|
||||||
|
from world.workshop_wing import GreatSmithy
|
||||||
|
from world.gardens_wing import HerbGardens
|
||||||
|
|
||||||
|
Each room class includes:
|
||||||
|
- Detailed multi-paragraph descriptions
|
||||||
|
- Atmospheric data (mood, lighting, sounds, smells, temperature)
|
||||||
|
- Suggested exit connections
|
||||||
|
- Objects present in the room
|
||||||
|
- Special magical features
|
||||||
|
|
||||||
|
TOTAL ROOM COUNT: 20 detailed room typeclasses
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Import all room classes for easy access
|
||||||
|
from .dormitory_entrance import (
|
||||||
|
DormitoryEntrance,
|
||||||
|
DormitoryLodgingMaster,
|
||||||
|
CorridorOfRest,
|
||||||
|
NoviceDormitoryHall,
|
||||||
|
MasterSuitesApproach
|
||||||
|
)
|
||||||
|
|
||||||
|
from .commons_wing import (
|
||||||
|
GrandCommonsHall,
|
||||||
|
HearthsideDining,
|
||||||
|
ScholarsCorner,
|
||||||
|
EntertainmentGallery,
|
||||||
|
UpperCommonsBalcony
|
||||||
|
)
|
||||||
|
|
||||||
|
from .workshop_wing import (
|
||||||
|
WorkshopEntrance,
|
||||||
|
GreatSmithy,
|
||||||
|
AlchemyLaboratories,
|
||||||
|
WoodworkingStudio,
|
||||||
|
ArtificingChambers
|
||||||
|
)
|
||||||
|
|
||||||
|
from .gardens_wing import (
|
||||||
|
GardensEntrance,
|
||||||
|
HerbGardens,
|
||||||
|
EnchantedGrove,
|
||||||
|
GreenhouseComplex,
|
||||||
|
SacredGrove
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
# Dormitories
|
||||||
|
'DormitoryEntrance',
|
||||||
|
'DormitoryLodgingMaster',
|
||||||
|
'CorridorOfRest',
|
||||||
|
'NoviceDormitoryHall',
|
||||||
|
'MasterSuitesApproach',
|
||||||
|
# Commons
|
||||||
|
'GrandCommonsHall',
|
||||||
|
'HearthsideDining',
|
||||||
|
'ScholarsCorner',
|
||||||
|
'EntertainmentGallery',
|
||||||
|
'UpperCommonsBalcony',
|
||||||
|
# Workshops
|
||||||
|
'WorkshopEntrance',
|
||||||
|
'GreatSmithy',
|
||||||
|
'AlchemyLaboratories',
|
||||||
|
'WoodworkingStudio',
|
||||||
|
'ArtificingChambers',
|
||||||
|
# Gardens
|
||||||
|
'GardensEntrance',
|
||||||
|
'HerbGardens',
|
||||||
|
'EnchantedGrove',
|
||||||
|
'GreenhouseComplex',
|
||||||
|
'SacredGrove'
|
||||||
|
]
|
||||||
|
|
||||||
|
WORLD_VERSION = "1.0.0"
|
||||||
|
TOTAL_ROOMS = 20
|
||||||
|
WINGS = ["Dormitories", "Commons", "Workshops", "Gardens"]
|
||||||
26
world/batch_cmds.ev
Normal file
26
world/batch_cmds.ev
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#
|
||||||
|
# A batch-command file is a way to build a game world
|
||||||
|
# in a programmatic way, by placing a sequence of
|
||||||
|
# build commands after one another. This allows for
|
||||||
|
# using a real text editor to edit e.g. descriptions
|
||||||
|
# rather than entering text on the command line.
|
||||||
|
#
|
||||||
|
# A batch-command file is loaded with @batchprocess in-game:
|
||||||
|
#
|
||||||
|
# @batchprocess[/interactive] tutorial_examples.batch_cmds
|
||||||
|
#
|
||||||
|
# A # as the first symbol on a line begins a comment and
|
||||||
|
# marks the end of a previous command definition. This is important,
|
||||||
|
# - every command must be separated by at least one line of comment.
|
||||||
|
#
|
||||||
|
# All supplied commands are given as normal, on their own line
|
||||||
|
# and accept arguments in any format up until the first next
|
||||||
|
# comment line begins. Extra whitespace is removed; an empty
|
||||||
|
# line in a command definition translates into a newline.
|
||||||
|
#
|
||||||
|
# See `evennia/contrib/tutorial_examples/batch_cmds.ev` for
|
||||||
|
# an example of a batch-command code. See also the batch-code
|
||||||
|
# system for loading python-code this way.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
205
world/build_academy.ev
Normal file
205
world/build_academy.ev
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
#
|
||||||
|
# Timmy Academy World Builder
|
||||||
|
# Builds the 20-room cannon world across 4 wings
|
||||||
|
#
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# WING 1: DORMITORIES
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Dormitory Entrance
|
||||||
|
teleport #2
|
||||||
|
@create/drop world.dormitory_entrance.DormitoryEntrance:dormitory_entrance
|
||||||
|
@name dormitory_entrance = Dormitory Entrance
|
||||||
|
@desc dormitory_entrance = A soaring archway of aged white marble marks the threshold between the bustling academy corridors and the sanctuary of rest. The entrance hall stretches thirty feet overhead, its vaulted ceiling painted with a magnificent mural of the moon phases, each lunar stage rendered in silver and pearl that seems to shimmer faintly in any light.
|
||||||
|
|
||||||
|
# Lodging Master Office
|
||||||
|
teleport #2
|
||||||
|
@create/drop world.dormitory_entrance.DormitoryLodgingMaster:lodging_office
|
||||||
|
@name lodging_office = Resident Services Office
|
||||||
|
|
||||||
|
# Corridor of Rest
|
||||||
|
teleport #2
|
||||||
|
@create/drop world.dormitory_entrance.CorridorOfRest:rest_corridor
|
||||||
|
@name rest_corridor = Corridor of Rest
|
||||||
|
|
||||||
|
# Novice Hall
|
||||||
|
teleport #2
|
||||||
|
@create/drop world.dormitory_entrance.NoviceDormitoryHall:novice_dorms
|
||||||
|
@name novice_dorms = Novice Dormitory Hall
|
||||||
|
|
||||||
|
# Master Suites
|
||||||
|
teleport #2
|
||||||
|
@create/drop world.dormitory_entrance.MasterSuitesApproach:master_quarters
|
||||||
|
@name master_quarters = Master Suites Approach
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# WING 2: COMMONS
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Grand Commons Hall
|
||||||
|
teleport #2
|
||||||
|
@create/drop world.commons_wing.GrandCommonsHall:grand_commons
|
||||||
|
@name grand_commons = Grand Commons Hall
|
||||||
|
|
||||||
|
# Hearthside Dining
|
||||||
|
teleport #2
|
||||||
|
@create/drop world.commons_wing.HearthsideDining:dining_hall
|
||||||
|
@name dining_hall = Hearthside Dining
|
||||||
|
|
||||||
|
# Scholar's Corner
|
||||||
|
teleport #2
|
||||||
|
@create/drop world.commons_wing.ScholarsCorner:study_area
|
||||||
|
@name study_area = Scholar's Corner
|
||||||
|
|
||||||
|
# Entertainment Gallery
|
||||||
|
teleport #2
|
||||||
|
@create/drop world.commons_wing.EntertainmentGallery:games_room
|
||||||
|
@name games_room = Entertainment Gallery
|
||||||
|
|
||||||
|
# Upper Balcony
|
||||||
|
teleport #2
|
||||||
|
@create/drop world.commons_wing.UpperCommonsBalcony:commons_balcony
|
||||||
|
@name commons_balcony = Upper Commons Balcony
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# WING 3: WORKSHOPS
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Workshop Entrance
|
||||||
|
teleport #2
|
||||||
|
@create/drop world.workshop_wing.WorkshopEntrance:crafting_entry
|
||||||
|
@name crafting_entry = Workshop Entrance
|
||||||
|
|
||||||
|
# Great Smithy
|
||||||
|
teleport #2
|
||||||
|
@create/drop world.workshop_wing.GreatSmithy:main_smithy
|
||||||
|
@name main_smithy = Great Smithy
|
||||||
|
|
||||||
|
# Alchemy Labs
|
||||||
|
teleport #2
|
||||||
|
@create/drop world.workshop_wing.AlchemyLaboratories:potion_lab
|
||||||
|
@name potion_lab = Alchemy Laboratories
|
||||||
|
|
||||||
|
# Woodworking Studio
|
||||||
|
teleport #2
|
||||||
|
@create/drop world.workshop_wing.WoodworkingStudio:wood_shop
|
||||||
|
@name wood_shop = Woodworking Studio
|
||||||
|
|
||||||
|
# Artificing Chambers
|
||||||
|
teleport #2
|
||||||
|
@create/drop world.workshop_wing.ArtificingChambers:enchantment_lab
|
||||||
|
@name enchantment_lab = Artificing Chambers
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# WING 4: GARDENS
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Gardens Entrance
|
||||||
|
teleport #2
|
||||||
|
@create/drop world.gardens_wing.GardensEntrance:gardens_entry
|
||||||
|
@name gardens_entry = Gardens Entrance
|
||||||
|
|
||||||
|
# Herb Gardens
|
||||||
|
teleport #2
|
||||||
|
@create/drop world.gardens_wing.HerbGardens:herb_beds
|
||||||
|
@name herb_beds = Herb Gardens
|
||||||
|
|
||||||
|
# Enchanted Grove
|
||||||
|
teleport #2
|
||||||
|
@create/drop world.gardens_wing.EnchantedGrove:fey_grove
|
||||||
|
@name fey_grove = Enchanted Grove
|
||||||
|
|
||||||
|
# Greenhouse Complex
|
||||||
|
teleport #2
|
||||||
|
@create/drop world.gardens_wing.GreenhouseComplex:conservatory
|
||||||
|
@name conservatory = Greenhouse Complex
|
||||||
|
|
||||||
|
# Sacred Grove
|
||||||
|
teleport #2
|
||||||
|
@create/drop world.gardens_wing.SacredGrove:holy_glade
|
||||||
|
@name holy_glade = Sacred Grove
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# CONNECT EXITS (Basic Layout)
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Link Limbo (#2) to Academy Hub
|
||||||
|
@desc #2 = The Timmy Academy - A place of learning and wizardry. Four wings extend from this central hub: |rDormitories|n to the north, |yCommons|n to the east, |bWorkshops|n to the south, and |gGardens|n to the west.
|
||||||
|
|
||||||
|
# From Limbo to Wings
|
||||||
|
@open north;n = #2, dormitory_entrance
|
||||||
|
@open east;e = #2, grand_commons
|
||||||
|
@open south;s = #2, crafting_entry
|
||||||
|
@open west;w = #2, gardens_entry
|
||||||
|
|
||||||
|
# Dormitory connections
|
||||||
|
@open south;s = dormitory_entrance, #2
|
||||||
|
@open north;n = dormitory_entrance, lodging_office
|
||||||
|
@open south;s = lodging_office, dormitory_entrance
|
||||||
|
@open east;e = dormitory_entrance, rest_corridor
|
||||||
|
@open west;w = rest_corridor, dormitory_entrance
|
||||||
|
@open north;n = rest_corridor, novice_dorms
|
||||||
|
@open south;s = novice_dorms, rest_corridor
|
||||||
|
@open east;e = rest_corridor, master_quarters
|
||||||
|
@open west;w = master_quarters, rest_corridor
|
||||||
|
|
||||||
|
# Commons connections
|
||||||
|
@open west;w = grand_commons, #2
|
||||||
|
@open north;n = grand_commons, dining_hall
|
||||||
|
@open south;s = dining_hall, grand_commons
|
||||||
|
@open east;e = grand_commons, study_area
|
||||||
|
@open west;w = study_area, grand_commons
|
||||||
|
@open south;s = grand_commons, games_room
|
||||||
|
@open north;n = games_room, grand_commons
|
||||||
|
@open up;u = grand_commons, commons_balcony
|
||||||
|
@open down;d = commons_balcony, grand_commons
|
||||||
|
|
||||||
|
# Workshop connections
|
||||||
|
@open north;n = crafting_entry, #2
|
||||||
|
@open east;e = crafting_entry, main_smithy
|
||||||
|
@open west;w = main_smithy, crafting_entry
|
||||||
|
@open south;s = crafting_entry, potion_lab
|
||||||
|
@open north;n = potion_lab, crafting_entry
|
||||||
|
@open west;w = crafting_entry, wood_shop
|
||||||
|
@open east;e = wood_shop, crafting_entry
|
||||||
|
@open down;d = crafting_entry, enchantment_lab
|
||||||
|
@open up;u = enchantment_lab, crafting_entry
|
||||||
|
|
||||||
|
# Gardens connections
|
||||||
|
@open east;e = gardens_entry, #2
|
||||||
|
@open north;n = gardens_entry, herb_beds
|
||||||
|
@open south;s = herb_beds, gardens_entry
|
||||||
|
@open west;w = gardens_entry, fey_grove
|
||||||
|
@open east;e = fey_grove, gardens_entry
|
||||||
|
@open south;s = gardens_entry, conservatory
|
||||||
|
@open north;n = conservatory, gardens_entry
|
||||||
|
@open down;d = fey_grove, holy_glade
|
||||||
|
@open up;u = holy_glade, fey_grove
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# SET HOME FOR ALL ROOMS
|
||||||
|
# ============================================
|
||||||
|
@set dormitory_entrance/home = #2
|
||||||
|
@set lodging_office/home = #2
|
||||||
|
@set rest_corridor/home = #2
|
||||||
|
@set novice_dorms/home = #2
|
||||||
|
@set master_quarters/home = #2
|
||||||
|
@set grand_commons/home = #2
|
||||||
|
@set dining_hall/home = #2
|
||||||
|
@set study_area/home = #2
|
||||||
|
@set games_room/home = #2
|
||||||
|
@set commons_balcony/home = #2
|
||||||
|
@set crafting_entry/home = #2
|
||||||
|
@set main_smithy/home = #2
|
||||||
|
@set potion_lab/home = #2
|
||||||
|
@set wood_shop/home = #2
|
||||||
|
@set enchantment_lab/home = #2
|
||||||
|
@set gardens_entry/home = #2
|
||||||
|
@set herb_beds/home = #2
|
||||||
|
@set fey_grove/home = #2
|
||||||
|
@set conservatory/home = #2
|
||||||
|
@set holy_glade/home = #2
|
||||||
|
|
||||||
|
# World build complete!
|
||||||
|
say Academy world built! 20 rooms across 4 wings.
|
||||||
336
world/commons_wing.py
Normal file
336
world/commons_wing.py
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
"""
|
||||||
|
Commons Wing - Social and Gathering Spaces
|
||||||
|
Evennia MUD World - Room TypeClass Definitions
|
||||||
|
"""
|
||||||
|
|
||||||
|
from evennia import DefaultRoom
|
||||||
|
|
||||||
|
class GrandCommonsHall(DefaultRoom):
|
||||||
|
"""
|
||||||
|
The central gathering hall where students, faculty, and visitors mingle.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_object_creation(self):
|
||||||
|
super().at_object_creation()
|
||||||
|
|
||||||
|
self.key = "Grand Commons Hall"
|
||||||
|
self.aliases = ["commons", "great hall", "main gathering", "social hall"]
|
||||||
|
|
||||||
|
self.db.desc = """
|
||||||
|
|wGrand Commons Hall|n
|
||||||
|
|
||||||
|
The |yGrand Commons|n rises before you in all its |csplendid grandeur|n—a
|
||||||
|
testament to the academy's wealth and prestige. The ceiling soars
|
||||||
|
sixty feet overhead, supported by |ytwelve marble columns|n carved
|
||||||
|
with the likenesses of the academy's founding archmages. Between
|
||||||
|
the columns, |ystained glass windows|n thirty feet tall depict great
|
||||||
|
moments in magical history.
|
||||||
|
|
||||||
|
The floor is a |ymosaic masterpiece|n of interlocking tiles forming
|
||||||
|
a massive compass rose, with each cardinal direction marked in
|
||||||
|
|yprecious metals|n—north in gold, south in silver, east in copper,
|
||||||
|
west in platinum. Around the perimeter, |yraised galleries|n provide
|
||||||
|
seating areas for hundreds, while the central space remains open
|
||||||
|
for gatherings, performances, and impromptu demonstrations.
|
||||||
|
|
||||||
|
|yMassive hearths|n occupy the north and south walls, each large
|
||||||
|
enough to stand in, burning with magical flames that provide
|
||||||
|
warmth without smoke. Between them, long |yfeasting tables|n can
|
||||||
|
be arranged for formal dinners or cleared for dances and assemblies.
|
||||||
|
|
||||||
|
The walls display |ytrophies of magical achievement|n—enchanted
|
||||||
|
artifacts, preserved specimens of summoned creatures, and portraits
|
||||||
|
of distinguished alumni. The air buzzes with |xconversation, laughter,
|
||||||
|
and the subtle crackle of ambient magic|n.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.db.atmosphere = {
|
||||||
|
"mood": "lively, social, energetic, welcoming",
|
||||||
|
"lighting": "bright from windows, warm firelight, magical accents",
|
||||||
|
"sounds": "conversations, laughter, footsteps on marble, fire crackling",
|
||||||
|
"smells": "roasted meats, fresh bread, wine, wood smoke, perfumes",
|
||||||
|
"temperature": "warm from fires and body heat, drafty near doors"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.exits = {
|
||||||
|
"north": "Hearthside Dining",
|
||||||
|
"south": "Commons Entrance",
|
||||||
|
"east": "Entertainment Gallery",
|
||||||
|
"west": "Scholar's Corner",
|
||||||
|
"up": "Upper Balcony",
|
||||||
|
"down": "Cellar Stores"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.objects = [
|
||||||
|
"twelve marble founder columns",
|
||||||
|
"thirty-foot stained glass windows",
|
||||||
|
"precious metal compass rose mosaic floor",
|
||||||
|
"raised gallery seating areas",
|
||||||
|
"massive double hearths with magical flames",
|
||||||
|
"collapsible feasting tables",
|
||||||
|
"magical achievement trophies",
|
||||||
|
"portrait gallery of distinguished alumni",
|
||||||
|
"crystal chandelier with ever-burning candles",
|
||||||
|
"information desk with campus maps"
|
||||||
|
]
|
||||||
|
|
||||||
|
class HearthsideDining(DefaultRoom):
|
||||||
|
"""
|
||||||
|
The main dining area attached to the Grand Commons.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_object_creation(self):
|
||||||
|
super().at_object_creation()
|
||||||
|
|
||||||
|
self.key = "Hearthside Dining"
|
||||||
|
self.aliases = ["dining hall", "great kitchen", "feasting hall"]
|
||||||
|
|
||||||
|
self.db.desc = """
|
||||||
|
|wHearthside Dining|n
|
||||||
|
|
||||||
|
The |yscent of cooking|n hits you first—roasting meats, baking bread,
|
||||||
|
simmering stews, and a hundred other culinary delights. The dining
|
||||||
|
hall stretches long and grand, with |yrows of oak tables|n capable
|
||||||
|
of seating five hundred diners at once. The tables are dressed
|
||||||
|
in |ylinen of academy blue|n, with fresh flowers in crystal vases
|
||||||
|
at regular intervals.
|
||||||
|
|
||||||
|
The |yserving area|n dominates the northern wall, a long counter
|
||||||
|
behind which |yenchanted serving dishes|n keep food at perfect
|
||||||
|
temperature. Chefs in |ywhite uniforms|n work in the visible kitchen
|
||||||
|
beyond, their movements a choreographed dance of culinary expertise.
|
||||||
|
|YSilver tureens|n and |ybrass warming trays|n gleam under the
|
||||||
|
magical lights.
|
||||||
|
|
||||||
|
The ceiling features |yhanging gardens|n—trailing vines and flowering
|
||||||
|
plants that purify the air and provide fresh herbs for the kitchen.
|
||||||
|
Between them, |ycrystal spheres|n cast warm, appetizing light over
|
||||||
|
the diners below.
|
||||||
|
|
||||||
|
The |ygreat hearth|n in the center of the room burns with a welcoming
|
||||||
|
fire, around which smaller tables offer intimate dining. Students,
|
||||||
|
faculty, and visitors mingle here, breaking bread together regardless
|
||||||
|
of rank or origin.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.db.atmosphere = {
|
||||||
|
"mood": "warm, welcoming, bustling, appetizing",
|
||||||
|
"lighting": "warm crystal glow, firelight, kitchen brightness",
|
||||||
|
"sounds": "clinking cutlery, conversation, kitchen sounds, sizzling",
|
||||||
|
"smells": "roasting meat, fresh bread, herbs, wine, coffee",
|
||||||
|
"temperature": "warm from cooking fires, comfortable throughout"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.exits = {
|
||||||
|
"south": "Grand Commons Hall",
|
||||||
|
"north": "Kitchen Complex",
|
||||||
|
"west": "Private Dining Rooms",
|
||||||
|
"east": "Beverage Station"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.objects = [
|
||||||
|
"rows of oak dining tables with academy blue linens",
|
||||||
|
"enchanted serving dishes and warming trays",
|
||||||
|
"visible kitchen with working chefs",
|
||||||
|
"hanging gardens with trailing vines",
|
||||||
|
"crystal sphere light fixtures",
|
||||||
|
"central great hearth with seating",
|
||||||
|
"silver tureens and brass serving ware",
|
||||||
|
"fresh flower centerpieces",
|
||||||
|
"beverage dispensers with magical cooling",
|
||||||
|
"dessert cart with rotating selections"
|
||||||
|
]
|
||||||
|
|
||||||
|
class ScholarsCorner(DefaultRoom):
|
||||||
|
"""
|
||||||
|
A quieter area of the Commons for study and intellectual discourse.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_object_creation(self):
|
||||||
|
super().at_object_creation()
|
||||||
|
|
||||||
|
self.key = "Scholar's Corner"
|
||||||
|
self.aliases = ["study area", "quiet commons", "reading nook"]
|
||||||
|
|
||||||
|
self.db.desc = """
|
||||||
|
|wScholar's Corner|n
|
||||||
|
|
||||||
|
Tucked away in the western wing of the Commons, this |yserene space|n
|
||||||
|
offers respite from the bustle of the main hall. The acoustics here
|
||||||
|
are |ymagically dampened|n, creating a hushed sanctuary where voices
|
||||||
|
drop to respectful whispers and the turning of pages provides the
|
||||||
|
primary soundtrack.
|
||||||
|
|
||||||
|
|yLeather armchairs|n in deep burgundy and forest green cluster around
|
||||||
|
|ysmall reading tables|n, each equipped with an |yadjustable reading lamp|n
|
||||||
|
that provides optimal illumination for any task. The walls are lined
|
||||||
|
with |ybuilt-in bookshelves|n containing reference works, popular
|
||||||
|
literature, and daily newspapers from across the realm.
|
||||||
|
|
||||||
|
A |ylong central table|n of polished walnut accommodates group study
|
||||||
|
sessions, its surface enchanted to resist ink stains and burns.
|
||||||
|
At one end, a |ypodium|n holds the current issue of the |yAcademy
|
||||||
|
Chronicle|n, available for all to read.
|
||||||
|
|
||||||
|
|YPotted plants|n—scholar's fern, memory moss, and concentration
|
||||||
|
cactus—add greenery and subtle cognitive benefits. The |yaroma of
|
||||||
|
quality tea|n wafts from a service station in the corner, where
|
||||||
|
an |yenchanted samovar|n provides endless hot water and a selection
|
||||||
|
of invigorating blends.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.db.atmosphere = {
|
||||||
|
"mood": "quiet, studious, contemplative, respectful",
|
||||||
|
"lighting": "soft individual lamps, natural light from clerestory windows",
|
||||||
|
"sounds": "page turning, soft whispers, tea pouring, distant silence",
|
||||||
|
"smells": "old books, leather, tea, coffee, concentration herbs",
|
||||||
|
"temperature": "cool and comfortable, ideal for alertness"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.exits = {
|
||||||
|
"east": "Grand Commons Hall",
|
||||||
|
"north": "Archives Annex",
|
||||||
|
"west": "Debate Chamber"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.objects = [
|
||||||
|
"burgundy and green leather armchairs",
|
||||||
|
"reading tables with adjustable lamps",
|
||||||
|
"built-in bookshelves with reference works",
|
||||||
|
"polished walnut study table",
|
||||||
|
"newspaper podium with Academy Chronicle",
|
||||||
|
"cognitive-enhancing potted plants",
|
||||||
|
"enchanted tea samovar station",
|
||||||
|
"magical silence dampening field",
|
||||||
|
"ink-resistant tabletop enchantments",
|
||||||
|
"newspaper rack with realm-wide publications"
|
||||||
|
]
|
||||||
|
|
||||||
|
class EntertainmentGallery(DefaultRoom):
|
||||||
|
"""
|
||||||
|
A space for performances, games, and recreational activities.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_object_creation(self):
|
||||||
|
super().at_object_creation()
|
||||||
|
|
||||||
|
self.key = "Entertainment Gallery"
|
||||||
|
self.aliases = ["games room", "performance hall", "recreation area"]
|
||||||
|
|
||||||
|
self.db.desc = """
|
||||||
|
|wEntertainment Gallery|n
|
||||||
|
|
||||||
|
Lively energy fills this |yspacious chamber|n dedicated to leisure
|
||||||
|
and amusement. The ceiling soars high enough to accommodate aerial
|
||||||
|
performances, reinforced with |ymagical safety nets|n that catch
|
||||||
|
falling acrobats—or overconfident students practicing levitation.
|
||||||
|
|
||||||
|
The floor is divided into |yactivity zones|n marked by different
|
||||||
|
colored rugs. In the |ygreen section|n, |ychess tables|n and |ystrategy
|
||||||
|
boards|n attract tactical minds. The |yblue zone|n features |ymagical
|
||||||
|
gaming consoles|n that project illusionary battlefields. The |yred
|
||||||
|
area|n contains a |ysmall stage|n for performances, complete with
|
||||||
|
adjustable lighting and acoustics.
|
||||||
|
|
||||||
|
Along the walls, |ycomfortable seating|n in curved arrangements
|
||||||
|
encourages socializing. A |yserving bar|n offers refreshments,
|
||||||
|
including non-alcoholic beverages for younger students. |YMusical
|
||||||
|
instruments|n hang available for impromptu concerts—a lute, a
|
||||||
|
harp, drums, and even a small pipe organ.
|
||||||
|
|
||||||
|
The |yatmosphere is festive|n, with colorful banners depicting
|
||||||
|
past performances and tournament champions. A |ybulletin board|n
|
||||||
|
advertises upcoming events, club meetings, and open gaming sessions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.db.atmosphere = {
|
||||||
|
"mood": "playful, energetic, social, creative",
|
||||||
|
"lighting": "colorful and adjustable, spotlights for stage",
|
||||||
|
"sounds": "laughter, music, game sounds, applause, conversation",
|
||||||
|
"smells": "sweet snacks, polished wood, stage makeup, excitement",
|
||||||
|
"temperature": "energetic warmth from activity and crowds"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.exits = {
|
||||||
|
"west": "Grand Commons Hall",
|
||||||
|
"north": "Music Practice Rooms",
|
||||||
|
"east": "Art Studio",
|
||||||
|
"up": "Theater Balcony"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.objects = [
|
||||||
|
"magical safety nets overhead",
|
||||||
|
"chess and strategy game tables",
|
||||||
|
"magical gaming console projectors",
|
||||||
|
"performance stage with lighting",
|
||||||
|
"curved social seating arrangements",
|
||||||
|
"serving bar with refreshments",
|
||||||
|
"available musical instruments",
|
||||||
|
"colorful event banners",
|
||||||
|
"bulletin board with announcements",
|
||||||
|
"trophy cases with gaming awards"
|
||||||
|
]
|
||||||
|
|
||||||
|
class UpperCommonsBalcony(DefaultRoom):
|
||||||
|
"""
|
||||||
|
The elevated balcony overlooking the Grand Commons.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_object_creation(self):
|
||||||
|
super().at_object_creation()
|
||||||
|
|
||||||
|
self.key = "Upper Balcony"
|
||||||
|
self.aliases = ["commons balcony", "gallery overlook", "upper level"]
|
||||||
|
|
||||||
|
self.db.desc = """
|
||||||
|
|wUpper Balcony|n
|
||||||
|
|
||||||
|
From this |yelevated vantage|n, the Grand Commons spreads below like
|
||||||
|
a living tapestry. The |ywrought-iron railing|n is worked with intricate
|
||||||
|
patterns of scrolling vines and academic symbols, sturdy yet elegant.
|
||||||
|
|yStone pillars|n support the roof overhead, each carved with the
|
||||||
|
names of past student council presidents.
|
||||||
|
|
||||||
|
The balcony provides a |yprivate retreat|n from the main floor's
|
||||||
|
bustle while still allowing observation of events below. |ySmall
|
||||||
|
tables|n with |yhigh-backed chairs|n offer intimate seating for private
|
||||||
|
conversations or quiet observation. Potted |ynight-blooming jasmine|n
|
||||||
|
releases its fragrance as evening approaches.
|
||||||
|
|
||||||
|
A |yservice staircase|n spirals down to the main floor discreetly,
|
||||||
|
while a |ydoor to the east|n leads to private meeting rooms reserved
|
||||||
|
for official academy business. |YTapestries|n depicting aerial views
|
||||||
|
of the academy campus hang between the pillars.
|
||||||
|
|
||||||
|
From here, one can watch |yperformances|n on the main floor, observe
|
||||||
|
the flow of students between classes, or simply enjoy the |xarchitectural
|
||||||
|
beauty|n of the Commons from a distance. The |ypeaceful remove|n makes
|
||||||
|
this a favorite spot for contemplation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.db.atmosphere = {
|
||||||
|
"mood": "observant, peaceful, slightly detached, elegant",
|
||||||
|
"lighting": "soft from below, gentle magical accent lights",
|
||||||
|
"sounds": "muffled from below, distant music, quiet conversation",
|
||||||
|
"smells": "night-blooming jasmine, polish, distant food aromas",
|
||||||
|
"temperature": "slightly cooler than below, gentle air circulation"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.exits = {
|
||||||
|
"down": "Grand Commons Hall",
|
||||||
|
"east": "Private Meeting Rooms",
|
||||||
|
"west": "Faculty Lounge"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.objects = [
|
||||||
|
"wrought-iron decorative railing",
|
||||||
|
"stone pillars with carved names",
|
||||||
|
"small tables with high-backed chairs",
|
||||||
|
"potted night-blooming jasmine",
|
||||||
|
"spiraling service staircase",
|
||||||
|
"aerial view tapestries",
|
||||||
|
"private meeting room doors",
|
||||||
|
"magical observation crystals",
|
||||||
|
"reserved seating signs",
|
||||||
|
"refreshment service station"
|
||||||
|
]
|
||||||
341
world/dormitory_entrance.py
Normal file
341
world/dormitory_entrance.py
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
"""
|
||||||
|
Dormitory Entrance - The Dormitories Wing
|
||||||
|
Evennia MUD World - Room TypeClass Definition
|
||||||
|
"""
|
||||||
|
|
||||||
|
from evennia import DefaultRoom
|
||||||
|
|
||||||
|
class DormitoryEntrance(DefaultRoom):
|
||||||
|
"""
|
||||||
|
The grand entrance to the Dormitories Wing, where students and residents
|
||||||
|
of the arcane academy find their respite and personal quarters.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_object_creation(self):
|
||||||
|
"""Called when the room is first created."""
|
||||||
|
super().at_object_creation()
|
||||||
|
|
||||||
|
# Room identity
|
||||||
|
self.key = "Dormitory Entrance"
|
||||||
|
self.aliases = ["dorm entrance", "dormitories", "sleeping quarters"]
|
||||||
|
|
||||||
|
# Detailed description
|
||||||
|
self.db.desc = """
|
||||||
|
|wThe Dormitory Entrance|n
|
||||||
|
|
||||||
|
A soaring archway of aged |ywhite marble|n marks the threshold between
|
||||||
|
the bustling academy corridors and the sanctuary of rest. The entrance
|
||||||
|
hall stretches thirty feet overhead, its vaulted ceiling painted with
|
||||||
|
a magnificent |cmural of the moon phases|n, each lunar stage rendered
|
||||||
|
in silver and pearl that seems to |xshimmer faintly|n in any light.
|
||||||
|
|
||||||
|
The floor consists of hexagonal tiles in alternating patterns of
|
||||||
|
|rslate gray|n and |gsoft moss green|n, worn smooth by generations of
|
||||||
|
footsteps. Along the walls, |yoil lanterns|n burn with enchanted flames
|
||||||
|
that never consume their fuel, casting warm golden pools of illumination.
|
||||||
|
|
||||||
|
To the |ynorth|n, the corridor leads deeper into the dormitories wing,
|
||||||
|
toward the residential chambers. A small |ypolished oak desk|n sits to
|
||||||
|
one side, where the |ylodging master|n typically maintains records of
|
||||||
|
room assignments. Pinned boards display notices about quiet hours,
|
||||||
|
maintenance schedules, and the occasional student message.
|
||||||
|
|
||||||
|
The air here carries the scent of |ylavender and cedar|n—warding herbs
|
||||||
|
that promote restful sleep and pleasant dreams. A pervasive sense of
|
||||||
|
|xcalm tranquility|n settles over visitors, as if the very walls were
|
||||||
|
enchanted to soothe weary minds.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Atmospheric tags
|
||||||
|
self.db.atmosphere = {
|
||||||
|
"mood": "peaceful, welcoming, restful",
|
||||||
|
"lighting": "warm golden lantern light, soft shadows",
|
||||||
|
"sounds": "distant footsteps, quiet murmurs, the crackle of magical flames",
|
||||||
|
"smells": "lavender, cedar, old parchment, sleep charms",
|
||||||
|
"temperature": "comfortably warm, slightly cool in shadowed corners"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Exits (to be connected by builder)
|
||||||
|
self.db.exits = {
|
||||||
|
"north": "Corridor of Rest",
|
||||||
|
"south": "Academy Main Hall",
|
||||||
|
"west": "Visitor Waiting Area",
|
||||||
|
"east": "Resident Services Office"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Objects in the room
|
||||||
|
self.db.objects = [
|
||||||
|
"polished oak desk with leather-bound registry",
|
||||||
|
"brass oil lanterns with eternal flames",
|
||||||
|
"cork notice board with various postings",
|
||||||
|
"velvet rope barriers for queue management",
|
||||||
|
"marble bench for waiting visitors",
|
||||||
|
"crystal water fountain with pure spring water",
|
||||||
|
"potted dreamlilies in bronze planters",
|
||||||
|
"enchanted bell for summoning the lodging master"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Special features
|
||||||
|
self.db.features = {
|
||||||
|
"sleep_ward": "Prevents magical scrying into sleeping quarters",
|
||||||
|
"silence_zone": "Suppresses loud noises beyond normal conversation",
|
||||||
|
"comfort_aura": "Reduces fatigue recovery time for resting characters",
|
||||||
|
"mural_enchantment": "Changes moon phase based on actual lunar cycle"
|
||||||
|
}
|
||||||
|
|
||||||
|
class DormitoryLodgingMaster(DefaultRoom):
|
||||||
|
"""
|
||||||
|
The office and workspace of the Lodging Master who manages room assignments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_object_creation(self):
|
||||||
|
super().at_object_creation()
|
||||||
|
|
||||||
|
self.key = "Resident Services Office"
|
||||||
|
self.aliases = ["lodging office", "room assignments", "housing desk"]
|
||||||
|
|
||||||
|
self.db.desc = """
|
||||||
|
|wResident Services Office|n
|
||||||
|
|
||||||
|
This |ycozy chamber|n serves as the administrative heart of the
|
||||||
|
dormitories. Bookshelves line every wall, crammed with |yleather-bound
|
||||||
|
ledgers|n dating back centuries, each spine labeled with years in
|
||||||
|
flowing gold script. The air smells of |yink, old paper, and beeswax|n.
|
||||||
|
|
||||||
|
A |ymassive mahogany desk|n dominates the center of the room, its surface
|
||||||
|
covered with stacks of forms, sealing wax, and a magnificent |ybrass
|
||||||
|
astrolabe|n that seems to track more than just celestial bodies. Behind
|
||||||
|
the desk hangs a |ydetailed map|n of the entire dormitory wing, with
|
||||||
|
small colored pins indicating room occupancy.
|
||||||
|
|
||||||
|
|yHigh windows|n let in afternoon light through panes of colored glass,
|
||||||
|
casting prismatic patterns across the floor. A |yfireplace|n on the east
|
||||||
|
wall burns with cool blue flames that provide warmth without smoke.
|
||||||
|
|
||||||
|
The Lodging Master, when present, sits in a |yhigh-backed chair|n of
|
||||||
|
worn velvet, ready to assist with room assignments, resolve disputes,
|
||||||
|
or address maintenance concerns. A |ysilver service bell|n sits prominently
|
||||||
|
on the desk corner for summoning assistance.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.db.atmosphere = {
|
||||||
|
"mood": "organized, professional, slightly cluttered",
|
||||||
|
"lighting": "colored sunlight, magical blue firelight",
|
||||||
|
"sounds": "scratching quills, paper rustling, occasional coughs",
|
||||||
|
"smells": "parchment, ink, beeswax polish, dried lavender",
|
||||||
|
"temperature": "warm and comfortable, fireplace heat"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.exits = {
|
||||||
|
"west": "Dormitory Entrance",
|
||||||
|
"north": "Records Archive"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.objects = [
|
||||||
|
"mahogany desk covered in paperwork",
|
||||||
|
"brass astrolabe tracking room assignments",
|
||||||
|
"wall map of dormitories with occupancy pins",
|
||||||
|
"bookshelves filled with century-old ledgers",
|
||||||
|
"high-backed velvet chair",
|
||||||
|
"silver service bell",
|
||||||
|
"inkwell with enchanted never-empty quill",
|
||||||
|
"wooden filing cabinets with brass handles",
|
||||||
|
"stained glass windows with academy crest",
|
||||||
|
"blue-flame fireplace with marble mantel"
|
||||||
|
]
|
||||||
|
|
||||||
|
class CorridorOfRest(DefaultRoom):
|
||||||
|
"""
|
||||||
|
The main corridor connecting the various dormitory halls.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_object_creation(self):
|
||||||
|
super().at_object_creation()
|
||||||
|
|
||||||
|
self.key = "Corridor of Rest"
|
||||||
|
self.aliases = ["dormitory corridor", "sleeping hall", "residents way"]
|
||||||
|
|
||||||
|
self.db.desc = """
|
||||||
|
|wCorridor of Rest|n
|
||||||
|
|
||||||
|
This |ylong corridor|n stretches before you like a pathway through
|
||||||
|
twilight itself. The walls are paneled in |ydark walnut|n that absorbs
|
||||||
|
sound, creating a hushed atmosphere broken only by the softest of
|
||||||
|
footsteps. Every ten feet, |yalcoves|n hold statues of |ydream guardians|n—
|
||||||
|
mythical creatures said to protect sleepers from nightmares.
|
||||||
|
|
||||||
|
The ceiling arches overhead in a series of |yribbed vaults|n, each
|
||||||
|
painted with a different |cconstellation|n that actually corresponds
|
||||||
|
to the rooms beneath. Small |ystarlight crystals|n embedded in the
|
||||||
|
vault ribs provide gentle illumination, mimicking the night sky.
|
||||||
|
|
||||||
|
|yPolished bronze plaques|n mark the entrances to various dormitory
|
||||||
|
halls: |yThe Novice Wing|n to the |yeast|n, |yThe Adept Chambers|n to
|
||||||
|
the |ywest|n, and |yThe Master Suites|n further |ynorth|n. Each doorway
|
||||||
|
is framed by |yheavy velvet curtains|n in colors denoting the rank
|
||||||
|
of residents within.
|
||||||
|
|
||||||
|
The floor is carpeted with thick |ywoven rugs|n depicting peaceful
|
||||||
|
landscapes—meadows, forests, and shores—designed to relax the mind
|
||||||
|
before sleep. At intervals, |yporcelain urns|n hold fresh lavender
|
||||||
|
and chamomile, their fragrances released slowly by warming enchantments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.db.atmosphere = {
|
||||||
|
"mood": "serene, quiet, contemplative",
|
||||||
|
"lighting": "soft starlight glow, gentle shadows",
|
||||||
|
"sounds": "muted footsteps, distant soft breathing, magical hum",
|
||||||
|
"smells": "lavender, chamomile, polished wood, sleep herbs",
|
||||||
|
"temperature": "cool and comfortable, perfect for sleeping"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.exits = {
|
||||||
|
"south": "Dormitory Entrance",
|
||||||
|
"north": "Master Suites Approach",
|
||||||
|
"east": "Novice Wing",
|
||||||
|
"west": "Adept Chambers",
|
||||||
|
"up": "Observatory Landing"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.objects = [
|
||||||
|
"dream guardian statues in wall alcoves",
|
||||||
|
"starlight crystal ceiling fixtures",
|
||||||
|
"bronze directional plaques",
|
||||||
|
"heavy velvet curtains in rank colors",
|
||||||
|
"thick woven landscape rugs",
|
||||||
|
"porcelain urns with sleep herbs",
|
||||||
|
"small meditation benches",
|
||||||
|
"water basins for refreshing travelers",
|
||||||
|
"enchanted silence orbs",
|
||||||
|
"moon-phase wall calendar"
|
||||||
|
]
|
||||||
|
|
||||||
|
class NoviceDormitoryHall(DefaultRoom):
|
||||||
|
"""
|
||||||
|
The dormitory hall for novice students and apprentices.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_object_creation(self):
|
||||||
|
super().at_object_creation()
|
||||||
|
|
||||||
|
self.key = "Novice Dormitory Hall"
|
||||||
|
self.aliases = ["novice wing", "apprentice quarters", "student dorms"]
|
||||||
|
|
||||||
|
self.db.desc = """
|
||||||
|
|wNovice Dormitory Hall|n
|
||||||
|
|
||||||
|
This |yspacious chamber|n houses the sleeping quarters for the academy's
|
||||||
|
newest students. The hall stretches nearly a hundred feet, lined on
|
||||||
|
both sides with |ysimple wooden doors|n leading to individual cells.
|
||||||
|
Each door bears a |ybrass number plate|n and a |ysmall chalkboard|n for
|
||||||
|
residents to leave messages.
|
||||||
|
|
||||||
|
Down the center runs a |ylong communal table|n where novices gather
|
||||||
|
for late-night study sessions or early morning meals. The table is
|
||||||
|
scarred with years of use—burn marks from failed spells, ink stains
|
||||||
|
from frenzied note-taking, and countless initials carved by restless
|
||||||
|
hands. |YWrought-iron candelabras|n provide warm illumination.
|
||||||
|
|
||||||
|
The walls are painted in |ycheerful pale blue|n, decorated with
|
||||||
|
educational tapestries showing magical theory diagrams, herb
|
||||||
|
identification charts, and the academy's history. Near the far end,
|
||||||
|
a |ycommunity bookshelf|n overflows with well-worn textbooks and
|
||||||
|
shared study materials.
|
||||||
|
|
||||||
|
|yComfortable but simple|n, the hall maintains a youthful energy.
|
||||||
|
Small groups of novices can often be found here, comparing notes,
|
||||||
|
practicing basic enchantments, or simply unwinding after demanding
|
||||||
|
classes. The atmosphere is one of |xshared struggle and camaraderie|n.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.db.atmosphere = {
|
||||||
|
"mood": "energetic but tired, studious, communal",
|
||||||
|
"lighting": "warm candlelight, some magical glow from practice",
|
||||||
|
"sounds": "whispered conversations, page turning, muffled spell practice",
|
||||||
|
"smells": "young sweat, ink, cheap candles, nervous energy",
|
||||||
|
"temperature": "slightly warm from body heat, drafty near windows"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.exits = {
|
||||||
|
"west": "Corridor of Rest",
|
||||||
|
"north": "Novice Bath House",
|
||||||
|
"east": "Study Nook"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.objects = [
|
||||||
|
"wooden doors with brass numbers",
|
||||||
|
"long scarred communal table",
|
||||||
|
"wrought-iron candelabras",
|
||||||
|
"educational tapestries",
|
||||||
|
"overflowing community bookshelf",
|
||||||
|
"chalkboards on each door",
|
||||||
|
"wooden benches and stools",
|
||||||
|
"shared spell component cabinet",
|
||||||
|
"lost and found crate",
|
||||||
|
"bulletin board for announcements"
|
||||||
|
]
|
||||||
|
|
||||||
|
class MasterSuitesApproach(DefaultRoom):
|
||||||
|
"""
|
||||||
|
The approach to the master suites for senior mages and faculty.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_object_creation(self):
|
||||||
|
super().at_object_creation()
|
||||||
|
|
||||||
|
self.key = "Master Suites Approach"
|
||||||
|
self.aliases = ["senior quarters", "master wing", "faculty housing"]
|
||||||
|
|
||||||
|
self.db.desc = """
|
||||||
|
|wMaster Suites Approach|n
|
||||||
|
|
||||||
|
The corridor transforms as you approach the senior quarters, becoming
|
||||||
|
more |yprivate and prestigious|n. The walls shift from institutional
|
||||||
|
stone to |ypaneling of rare woods|n—ebony, purpleheart, and silverleaf—
|
||||||
|
inlaid with delicate |ymarvory filigree|n depicting academic achievements.
|
||||||
|
|
||||||
|
The ceiling rises higher here, crowned with a |ystained glass dome|n
|
||||||
|
that filters daylight into cascades of color. At night, |yluminescent
|
||||||
|
crystals|n take over, providing adjustable lighting suited to each
|
||||||
|
resident's preference. The floor is paved with |ymarble tiles|n in
|
||||||
|
complex geometric patterns.
|
||||||
|
|
||||||
|
Heavy |yiron-bound doors|n stand at intervals, each unique in design,
|
||||||
|
bearing the |ypersonal heraldry|n of the master who dwells within.
|
||||||
|
Small |ywaiting areas|n with comfortable chairs occupy the spaces
|
||||||
|
between doors, allowing guests to await admittance in comfort.
|
||||||
|
|
||||||
|
The air carries a sense of |xgravitas and accomplishment|n. These
|
||||||
|
quarters house the academy's most accomplished scholars, and the
|
||||||
|
space reflects their status. A |yattendant's station|n near the
|
||||||
|
entrance ensures that only appropriate visitors gain access to
|
||||||
|
the private suites beyond.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.db.atmosphere = {
|
||||||
|
"mood": "dignified, quiet, exclusive",
|
||||||
|
"lighting": "filtered colored light, adjustable magical illumination",
|
||||||
|
"sounds": "near silence, occasional muffled conversation, distant bells",
|
||||||
|
"smells": "expensive incense, old books, rare woods, magical reagents",
|
||||||
|
"temperature": "perfectly climate controlled, individualized zones"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.exits = {
|
||||||
|
"south": "Corridor of Rest",
|
||||||
|
"north": "Archmage's Residence",
|
||||||
|
"east": "Master Suite Alpha",
|
||||||
|
"west": "Master Suite Beta"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.objects = [
|
||||||
|
"rare wood paneling with ivory inlay",
|
||||||
|
"stained glass dome ceiling",
|
||||||
|
"luminescent crystal light fixtures",
|
||||||
|
"marble tile geometric flooring",
|
||||||
|
"iron-bound doors with personal heraldry",
|
||||||
|
"comfortable waiting area chairs",
|
||||||
|
"attendant's station with guest registry",
|
||||||
|
"magical privacy screens",
|
||||||
|
"fresh flower arrangements in crystal vases",
|
||||||
|
"portraits of former resident masters"
|
||||||
|
]
|
||||||
348
world/gardens_wing.py
Normal file
348
world/gardens_wing.py
Normal file
@@ -0,0 +1,348 @@
|
|||||||
|
"""
|
||||||
|
Gardens Wing - Botanical, Herbal, and Natural Magic
|
||||||
|
Evennia MUD World - Room TypeClass Definitions
|
||||||
|
"""
|
||||||
|
|
||||||
|
from evennia import DefaultRoom
|
||||||
|
|
||||||
|
class GardensEntrance(DefaultRoom):
|
||||||
|
"""
|
||||||
|
The entrance to the Gardens Wing where nature and magic intertwine.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_object_creation(self):
|
||||||
|
super().at_object_creation()
|
||||||
|
|
||||||
|
self.key = "Gardens Entrance"
|
||||||
|
self.aliases = ["gardens entry", "botanical wing", "green approach"]
|
||||||
|
|
||||||
|
self.db.desc = """
|
||||||
|
|wGardens Entrance|n
|
||||||
|
|
||||||
|
The transition from stone corridors to |yliving growth|n is gradual
|
||||||
|
and magical. The walls here are covered with |yliving vines|n that
|
||||||
|
respond to passing warmth and light, their leaves |yshifting colors|n
|
||||||
|
from deep green to silver-veined patterns. The floor transitions
|
||||||
|
from marble to |ynatural flagstone|n with moss growing in the gaps,
|
||||||
|
softening footsteps into silence.
|
||||||
|
|
||||||
|
A |ynatural archway|n of intertwined |ysilver birch|n and |yflowering
|
||||||
|
wisteria|n marks the formal entrance to the gardens. Through it,
|
||||||
|
you glimpse |yparadise|n—lawns of emerald grass, flower beds in
|
||||||
|
riotous color, and the green shade of ancient trees. The air is
|
||||||
|
|yfresh and fragrant|n, carrying hints of a thousand blooms and
|
||||||
|
the earthy scent of fertile soil.
|
||||||
|
|
||||||
|
|yStone benches|n line the entrance path, inviting rest before
|
||||||
|
exploration. |YSundials|n and |ymoondials|n stand at intervals,
|
||||||
|
marking time for the gardeners who tend this vast complex. A
|
||||||
|
|yinformation kiosk|n displays maps showing the various garden
|
||||||
|
sections: |yHerb Gardens|n to the |ynorth|n, |yEnchanted Grove|n
|
||||||
|
to the |yeast|n, |yGreenhouses|n to the |ywest|n, and the |ySacred
|
||||||
|
Grove|n beyond.
|
||||||
|
|
||||||
|
|yButterflies|n and |yhummingbirds|n—some natural, some conjured—
|
||||||
|
flit through the air, adding movement to the |ypeaceful stillness|n
|
||||||
|
of this sanctuary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.db.atmosphere = {
|
||||||
|
"mood": "peaceful, rejuvenating, alive, welcoming",
|
||||||
|
"lighting": "dappled sunlight, magical plant glow, natural brightness",
|
||||||
|
"sounds": "birdsong, rustling leaves, water trickling, soft breezes",
|
||||||
|
"smells": "flowers, fresh earth, herbs, tree sap, growth",
|
||||||
|
"temperature": "perfect and mild, self-regulating gardens"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.exits = {
|
||||||
|
"north": "Herb Gardens",
|
||||||
|
"east": "Enchanted Grove",
|
||||||
|
"west": "Greenhouse Complex",
|
||||||
|
"south": "Academy Grounds",
|
||||||
|
"northeast": "Sacred Grove Approach"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.objects = [
|
||||||
|
"living vine-covered walls",
|
||||||
|
"natural birch and wisteria archway",
|
||||||
|
"stone benches for resting",
|
||||||
|
"sundials and moondials",
|
||||||
|
"information kiosk with garden maps",
|
||||||
|
"butterflies and hummingbirds",
|
||||||
|
"flowering planters with seasonal blooms",
|
||||||
|
"water fountain with drinking spouts",
|
||||||
|
"gardening tool storage shed",
|
||||||
|
"weather monitoring enchanted devices"
|
||||||
|
]
|
||||||
|
|
||||||
|
class HerbGardens(DefaultRoom):
|
||||||
|
"""
|
||||||
|
The organized herb gardens for medicinal and magical plants.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_object_creation(self):
|
||||||
|
super().at_object_creation()
|
||||||
|
|
||||||
|
self.key = "Herb Gardens"
|
||||||
|
self.aliases = ["herb beds", "medicinal gardens", "apothecary plots"]
|
||||||
|
|
||||||
|
self.db.desc = """
|
||||||
|
|wHerb Gardens|n
|
||||||
|
|
||||||
|
Order and abundance combine in this |yextensive garden|n dedicated
|
||||||
|
to useful plants. The space is divided into |ygeometric beds|n by
|
||||||
|
|ylow hedges of lavender|n, each section carefully labeled with
|
||||||
|
|yenchanted markers|n that display information about the plants
|
||||||
|
growing there.
|
||||||
|
|
||||||
|
|yMedicinal herbs|n occupy the eastern sections—|ycomfrey|n for
|
||||||
|
healing, |yfeverfew|n for headaches, |yechinacea|n for immune
|
||||||
|
support, and dozens of others arranged by their therapeutic
|
||||||
|
properties. The |ywestern plots|n hold |ymagical reagents|n:
|
||||||
|
|ymandrake|n for transformation potions, |ynightshade|n for
|
||||||
|
truth serums, |ymoonpetal|n for divination aids.
|
||||||
|
|
||||||
|
|yGardeners|n in |yearth-colored robes|n move between the beds,
|
||||||
|
tending the plants with expert care. They harvest at optimal
|
||||||
|
times, when the plants' magical or medicinal properties peak.
|
||||||
|
|YGlass cloches|n protect delicate seedlings, while |ymagical
|
||||||
|
irrigation systems|n ensure perfect moisture levels.
|
||||||
|
|
||||||
|
A |ydrying shed|n at the north end processes harvested herbs,
|
||||||
|
while a |ycomposting station|n returns nutrients to the soil.
|
||||||
|
The |xatmosphere is one of productive harmony|n—nature shaped
|
||||||
|
by knowledge and returned to through care.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.db.atmosphere = {
|
||||||
|
"mood": "productive, educational, fragrant, organized",
|
||||||
|
"lighting": "bright sunlight, filtered through occasional trees",
|
||||||
|
"sounds": "bees humming, snipping shears, soft conversations",
|
||||||
|
"smells": "lavender, mint, rosemary, medicinal herbs, earth",
|
||||||
|
"temperature": "warm in sun, cool in shade, perfect for growth"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.exits = {
|
||||||
|
"south": "Gardens Entrance",
|
||||||
|
"north": "Herb Processing Shed",
|
||||||
|
"east": "Poison Garden",
|
||||||
|
"west": "Aromatic Walk"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.objects = [
|
||||||
|
"geometric garden beds with lavender hedges",
|
||||||
|
"enchanted plant information markers",
|
||||||
|
"medicinal herb sections",
|
||||||
|
"magical reagent plots",
|
||||||
|
"gardener's tool stations",
|
||||||
|
"glass cloches for seedlings",
|
||||||
|
"magical irrigation systems",
|
||||||
|
"drying shed for processing",
|
||||||
|
"composting station",
|
||||||
|
"harvesting baskets and supplies"
|
||||||
|
]
|
||||||
|
|
||||||
|
class EnchantedGrove(DefaultRoom):
|
||||||
|
"""
|
||||||
|
A grove where magical plants and fey influences create wonders.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_object_creation(self):
|
||||||
|
super().at_object_creation()
|
||||||
|
|
||||||
|
self.key = "Enchanted Grove"
|
||||||
|
self.aliases = ["fey grove", "magic garden", "wonder woods"]
|
||||||
|
|
||||||
|
self.db.desc = """
|
||||||
|
|wEnchanted Grove|n
|
||||||
|
|
||||||
|
The boundaries between |ynatural and supernatural|n blur in this
|
||||||
|
extraordinary space. Ancient |yoaks|n and |ywillows|n grow in
|
||||||
|
configurations that suggest |yintentional design|n—perhaps by
|
||||||
|
long-forgotten druids or the |yfey themselves|n. Their branches
|
||||||
|
intertwine overhead, forming a |yliving cathedral|n dappled with
|
||||||
|
shifting light.
|
||||||
|
|
||||||
|
|yLuminous flowers|n bloom here that exist nowhere else—|yglowcaps|n
|
||||||
|
that pulse with soft blue radiance, |ysinging lilies|n that harmonize
|
||||||
|
with passing breezes, |ymemory roses|n whose scent recalls cherished
|
||||||
|
moments. |YFaerie rings|n of mushrooms dot clearings, their centers
|
||||||
|
|yglimmering with otherworldly light|n.
|
||||||
|
|
||||||
|
A |ystream|n winds through the grove, its waters |ycrystal clear|n
|
||||||
|
yet somehow reflecting |ystars|n regardless of the time of day.
|
||||||
|
|yStone circles|n and |ywooden groves|n mark places of particular
|
||||||
|
power, where the veil between worlds grows thin. Visitors report
|
||||||
|
|yfleeting glimpses|n of small, luminous beings watching from
|
||||||
|
the corner of vision.
|
||||||
|
|
||||||
|
The |xatmosphere is dreamy and slightly surreal|n. Time moves
|
||||||
|
differently here—minutes feel like hours, or pass in what seems
|
||||||
|
like moments. Those who sleep in the grove often wake with
|
||||||
|
|yprophetic dreams|n or |yartistic inspiration|n.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.db.atmosphere = {
|
||||||
|
"mood": "dreamy, wondrous, slightly uncanny, peaceful",
|
||||||
|
"lighting": "dappled sunlight, bioluminescent plant glow, faerie light",
|
||||||
|
"sounds": "singing flowers, whispering leaves, distant music, trickling water",
|
||||||
|
"smells": "otherworldly flowers, ozone, sweet nectar, wild magic",
|
||||||
|
"temperature": "always comfortable, self-regulating, slightly cool"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.exits = {
|
||||||
|
"west": "Gardens Entrance",
|
||||||
|
"north": "Faerie Ring Center",
|
||||||
|
"east": "Fey Border",
|
||||||
|
"down": "Mushroom Caves"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.objects = [
|
||||||
|
"ancient oak and willow trees",
|
||||||
|
"luminous glowcap mushrooms",
|
||||||
|
"singing lilies",
|
||||||
|
"memory roses",
|
||||||
|
"active faerie rings",
|
||||||
|
"star-reflecting stream",
|
||||||
|
"stone circles of power",
|
||||||
|
"wooden meditation groves",
|
||||||
|
"bioluminescent flower beds",
|
||||||
|
"wishing well with magical properties"
|
||||||
|
]
|
||||||
|
|
||||||
|
class GreenhouseComplex(DefaultRoom):
|
||||||
|
"""
|
||||||
|
The magical greenhouses for plants requiring controlled environments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_object_creation(self):
|
||||||
|
super().at_object_creation()
|
||||||
|
|
||||||
|
self.key = "Greenhouse Complex"
|
||||||
|
self.aliases = ["greenhouses", "conservatory", "climate chambers"]
|
||||||
|
|
||||||
|
self.db.desc = """
|
||||||
|
|wGreenhouse Complex|n
|
||||||
|
|
||||||
|
|yGlass and magic|n combine in this vast conservatory where plants
|
||||||
|
from across the world—and beyond—thrive in carefully controlled
|
||||||
|
conditions. The structure consists of |yseven interconnected domes|n,
|
||||||
|
each maintaining a different |yclimate zone|n through powerful
|
||||||
|
environmental enchantments.
|
||||||
|
|
||||||
|
The |ycentral dome|n houses the |ytropical collection|n—towering
|
||||||
|
|ypalms|n, |ycarnivorous plants|n from distant jungles, and flowers
|
||||||
|
that bloom only in perpetual heat. To the |ynorth|n, the |ydesert
|
||||||
|
dome|n cultivates |ycacti|n and |ysucculents|n that store magical
|
||||||
|
properties along with water. The |yeastern dome|n maintains
|
||||||
|
|yaquatic environments|n for plants that grow only in water.
|
||||||
|
|
||||||
|
|yClimate controls|n—magical devices of brass and crystal—line
|
||||||
|
the walls, their needles and indicators showing temperature,
|
||||||
|
humidity, light levels, and magical saturation. |YAutomated
|
||||||
|
systems|n open and close vents, adjust magical sun-lamps, and
|
||||||
|
trigger irrigation based on each plant's needs.
|
||||||
|
|
||||||
|
The |yatmosphere varies by zone|n—humid and warm in the tropical
|
||||||
|
section, dry and hot in the desert, cool and misty in the
|
||||||
|
aquatic areas. |YBotanists|n in |ywhite coats|n move between
|
||||||
|
domes, clipboard in hand, documenting growth patterns and
|
||||||
|
magical properties.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.db.atmosphere = {
|
||||||
|
"mood": "scientific, nurturing, varied by zone, controlled",
|
||||||
|
"lighting": "magical sun-lamps, natural through glass, zone-specific",
|
||||||
|
"sounds": "humidifiers, fans, water pumps, botanical discussions",
|
||||||
|
"smells": "humid earth, exotic flowers, varied by climate zone",
|
||||||
|
"temperature": "varies by dome: tropical, desert, temperate, aquatic"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.exits = {
|
||||||
|
"east": "Gardens Entrance",
|
||||||
|
"north": "Desert Dome",
|
||||||
|
"northeast": "Tropical Dome",
|
||||||
|
"southeast": "Aquatic Dome",
|
||||||
|
"up": "Rooftop Solar Garden"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.objects = [
|
||||||
|
"seven interconnected glass domes",
|
||||||
|
"climate control magical devices",
|
||||||
|
"tropical plant collection",
|
||||||
|
"desert cacti and succulents",
|
||||||
|
"aquatic plant tanks",
|
||||||
|
"automated irrigation systems",
|
||||||
|
"magical sun-lamp arrays",
|
||||||
|
"botanist workstations with clipboards",
|
||||||
|
"plant specimen preservation cabinets",
|
||||||
|
"seed library and storage vault"
|
||||||
|
]
|
||||||
|
|
||||||
|
class SacredGrove(DefaultRoom):
|
||||||
|
"""
|
||||||
|
The most sacred space in the Gardens, where ancient rituals occur.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_object_creation(self):
|
||||||
|
super().at_object_creation()
|
||||||
|
|
||||||
|
self.key = "Sacred Grove"
|
||||||
|
self.aliases = ["sacred space", "ritual glade", "holy garden"]
|
||||||
|
|
||||||
|
self.db.desc = """
|
||||||
|
|wSacred Grove|n
|
||||||
|
|
||||||
|
Deep within the Gardens lies a |yspace apart|n—a glade where the
|
||||||
|
|ydivine touches the earthly|n. The approach is marked by |yancient
|
||||||
|
standing stones|n carved with symbols predating the academy itself,
|
||||||
|
their surfaces |yworn smooth|n by centuries of reverent touch.
|
||||||
|
|
||||||
|
The grove itself is |ycircular|n, bounded by |yeleven great trees|n
|
||||||
|
of different species—oak, ash, yew, rowan, and others—each
|
||||||
|
representing a different aspect of natural power. Their branches
|
||||||
|
meet overhead, forming a |yliving roof|n that filters sunlight
|
||||||
|
into |ycolumns of golden radiance|n. At the center, a |ynatural
|
||||||
|
altar stone|n rises from the earth, its surface covered in
|
||||||
|
|ymoss and tiny flowers|n.
|
||||||
|
|
||||||
|
The |yatmosphere is heavy with sanctity|n. This is where druids
|
||||||
|
renew their vows, where priests commune with nature deities,
|
||||||
|
where the most potent natural magic is worked. The |yair shimmers|n
|
||||||
|
with accumulated divine energy, and visitors often report feeling
|
||||||
|
|ywatchful presences|n in the rustling leaves.
|
||||||
|
|
||||||
|
|ySeasonal rituals|n mark the turning of the wheel here—|yBeltane
|
||||||
|
fires|n, |yMabon harvests|n, |yYule vigils|n. The grove responds
|
||||||
|
to these observances, trees blooming out of season, flowers
|
||||||
|
opening at midnight, stones humming with harmonic resonance.
|
||||||
|
Even at quiet times, the |xpeace here is profound|n, inviting
|
||||||
|
meditation and spiritual renewal.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.db.atmosphere = {
|
||||||
|
"mood": "sacred, profound, ancient, spiritually charged",
|
||||||
|
"lighting": "golden filtered sunlight, occasional bioluminescence",
|
||||||
|
"sounds": "profound silence, wind in leaves, distant chanting, heartbeats",
|
||||||
|
"smells": "ancient earth, incense, ozone, divine presence",
|
||||||
|
"temperature": "perfect equilibrium, spiritually refreshing"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.exits = {
|
||||||
|
"southwest": "Gardens Entrance",
|
||||||
|
"north": "Druid's Sanctuary",
|
||||||
|
"down": "Root Temple"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.objects = [
|
||||||
|
"ancient standing stones with worn symbols",
|
||||||
|
"eleven sacred trees of power",
|
||||||
|
"natural altar stone with moss",
|
||||||
|
"seasonal ritual remnants and offerings",
|
||||||
|
"prayer ribbons tied to branches",
|
||||||
|
"crystal offerings at tree bases",
|
||||||
|
"ritual circle markings in grass",
|
||||||
|
"holy water font carved from stone",
|
||||||
|
"meditation cushions of natural fiber",
|
||||||
|
"sacred flame that never extinguishes"
|
||||||
|
]
|
||||||
58
world/help_entries.py
Normal file
58
world/help_entries.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
"""
|
||||||
|
File-based help entries. These complements command-based help and help entries
|
||||||
|
added in the database using the `sethelp` command in-game.
|
||||||
|
|
||||||
|
Control where Evennia reads these entries with `settings.FILE_HELP_ENTRY_MODULES`,
|
||||||
|
which is a list of python-paths to modules to read.
|
||||||
|
|
||||||
|
A module like this should hold a global `HELP_ENTRY_DICTS` list, containing
|
||||||
|
dicts that each represent a help entry. If no `HELP_ENTRY_DICTS` variable is
|
||||||
|
given, all top-level variables that are dicts in the module are read as help
|
||||||
|
entries.
|
||||||
|
|
||||||
|
Each dict is on the form
|
||||||
|
::
|
||||||
|
|
||||||
|
{'key': <str>,
|
||||||
|
'text': <str>}`` # the actual help text. Can contain # subtopic sections
|
||||||
|
'category': <str>, # optional, otherwise settings.DEFAULT_HELP_CATEGORY
|
||||||
|
'aliases': <list>, # optional
|
||||||
|
'locks': <str> # optional, 'view' controls seeing in help index, 'read'
|
||||||
|
# if the entry can be read. If 'view' is unset,
|
||||||
|
# 'read' is used for the index. If unset, everyone
|
||||||
|
# can read/view the entry.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
HELP_ENTRY_DICTS = [
|
||||||
|
{
|
||||||
|
"key": "evennia",
|
||||||
|
"aliases": ["ev"],
|
||||||
|
"category": "General",
|
||||||
|
"locks": "read:perm(Developer)",
|
||||||
|
"text": """
|
||||||
|
Evennia is a MU-game server and framework written in Python. You can read more
|
||||||
|
on https://www.evennia.com.
|
||||||
|
|
||||||
|
# subtopics
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
You'll find installation instructions on https://www.evennia.com.
|
||||||
|
|
||||||
|
## Community
|
||||||
|
|
||||||
|
There are many ways to get help and communicate with other devs!
|
||||||
|
|
||||||
|
### Discussions
|
||||||
|
|
||||||
|
The Discussions forum is found at https://github.com/evennia/evennia/discussions.
|
||||||
|
|
||||||
|
### Discord
|
||||||
|
|
||||||
|
There is also a discord channel for chatting - connect using the
|
||||||
|
following link: https://discord.gg/AJJpcRUhtF
|
||||||
|
|
||||||
|
""",
|
||||||
|
},
|
||||||
|
]
|
||||||
90
world/prototypes.py
Normal file
90
world/prototypes.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
"""
|
||||||
|
Prototypes
|
||||||
|
|
||||||
|
A prototype is a simple way to create individualized instances of a
|
||||||
|
given typeclass. It is dictionary with specific key names.
|
||||||
|
|
||||||
|
For example, you might have a Sword typeclass that implements everything a
|
||||||
|
Sword would need to do. The only difference between different individual Swords
|
||||||
|
would be their key, description and some Attributes. The Prototype system
|
||||||
|
allows to create a range of such Swords with only minor variations. Prototypes
|
||||||
|
can also inherit and combine together to form entire hierarchies (such as
|
||||||
|
giving all Sabres and all Broadswords some common properties). Note that bigger
|
||||||
|
variations, such as custom commands or functionality belong in a hierarchy of
|
||||||
|
typeclasses instead.
|
||||||
|
|
||||||
|
A prototype can either be a dictionary placed into a global variable in a
|
||||||
|
python module (a 'module-prototype') or stored in the database as a dict on a
|
||||||
|
special Script (a db-prototype). The former can be created just by adding dicts
|
||||||
|
to modules Evennia looks at for prototypes, the latter is easiest created
|
||||||
|
in-game via the `olc` command/menu.
|
||||||
|
|
||||||
|
Prototypes are read and used to create new objects with the `spawn` command
|
||||||
|
or directly via `evennia.spawn` or the full path `evennia.prototypes.spawner.spawn`.
|
||||||
|
|
||||||
|
A prototype dictionary have the following keywords:
|
||||||
|
|
||||||
|
Possible keywords are:
|
||||||
|
- `prototype_key` - the name of the prototype. This is required for db-prototypes,
|
||||||
|
for module-prototypes, the global variable name of the dict is used instead
|
||||||
|
- `prototype_parent` - string pointing to parent prototype if any. Prototype inherits
|
||||||
|
in a similar way as classes, with children overriding values in their parents.
|
||||||
|
- `key` - string, the main object identifier.
|
||||||
|
- `typeclass` - string, if not set, will use `settings.BASE_OBJECT_TYPECLASS`.
|
||||||
|
- `location` - this should be a valid object or #dbref.
|
||||||
|
- `home` - valid object or #dbref.
|
||||||
|
- `destination` - only valid for exits (object or #dbref).
|
||||||
|
- `permissions` - string or list of permission strings.
|
||||||
|
- `locks` - a lock-string to use for the spawned object.
|
||||||
|
- `aliases` - string or list of strings.
|
||||||
|
- `attrs` - Attributes, expressed as a list of tuples on the form `(attrname, value)`,
|
||||||
|
`(attrname, value, category)`, or `(attrname, value, category, locks)`. If using one
|
||||||
|
of the shorter forms, defaults are used for the rest.
|
||||||
|
- `tags` - Tags, as a list of tuples `(tag,)`, `(tag, category)` or `(tag, category, data)`.
|
||||||
|
- Any other keywords are interpreted as Attributes with no category or lock.
|
||||||
|
These will internally be added to `attrs` (equivalent to `(attrname, value)`.
|
||||||
|
|
||||||
|
See the `spawn` command and `evennia.prototypes.spawner.spawn` for more info.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
## example of module-based prototypes using
|
||||||
|
## the variable name as `prototype_key` and
|
||||||
|
## simple Attributes
|
||||||
|
|
||||||
|
# from random import randint
|
||||||
|
#
|
||||||
|
# GOBLIN = {
|
||||||
|
# "key": "goblin grunt",
|
||||||
|
# "health": lambda: randint(20,30),
|
||||||
|
# "resists": ["cold", "poison"],
|
||||||
|
# "attacks": ["fists"],
|
||||||
|
# "weaknesses": ["fire", "light"],
|
||||||
|
# "tags": = [("greenskin", "monster"), ("humanoid", "monster")]
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# GOBLIN_WIZARD = {
|
||||||
|
# "prototype_parent": "GOBLIN",
|
||||||
|
# "key": "goblin wizard",
|
||||||
|
# "spells": ["fire ball", "lighting bolt"]
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# GOBLIN_ARCHER = {
|
||||||
|
# "prototype_parent": "GOBLIN",
|
||||||
|
# "key": "goblin archer",
|
||||||
|
# "attacks": ["short bow"]
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# This is an example of a prototype without a prototype
|
||||||
|
# (nor key) of its own, so it should normally only be
|
||||||
|
# used as a mix-in, as in the example of the goblin
|
||||||
|
# archwizard below.
|
||||||
|
# ARCHWIZARD_MIXIN = {
|
||||||
|
# "attacks": ["archwizard staff"],
|
||||||
|
# "spells": ["greater fire ball", "greater lighting"]
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# GOBLIN_ARCHWIZARD = {
|
||||||
|
# "key": "goblin archwizard",
|
||||||
|
# "prototype_parent" : ("GOBLIN_WIZARD", "ARCHWIZARD_MIXIN")
|
||||||
|
# }
|
||||||
342
world/workshop_wing.py
Normal file
342
world/workshop_wing.py
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
"""
|
||||||
|
Workshop Wing - Crafting, Smithing, and Magical Creation
|
||||||
|
Evennia MUD World - Room TypeClass Definitions
|
||||||
|
"""
|
||||||
|
|
||||||
|
from evennia import DefaultRoom
|
||||||
|
|
||||||
|
class WorkshopEntrance(DefaultRoom):
|
||||||
|
"""
|
||||||
|
The entrance to the Workshop Wing where practical magic and crafting occur.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_object_creation(self):
|
||||||
|
super().at_object_creation()
|
||||||
|
|
||||||
|
self.key = "Workshop Wing Entrance"
|
||||||
|
self.aliases = ["workshop entry", "crafting wing", "makers hall"]
|
||||||
|
|
||||||
|
self.db.desc = """
|
||||||
|
|wWorkshop Wing Entrance|n
|
||||||
|
|
||||||
|
The transition from polished academy corridors to the |yWorkshop Wing|n
|
||||||
|
is immediately apparent. The air grows |ywarmer|n and carries the
|
||||||
|
mingled scents of |yhot metal, sawdust, alchemical reagents, and
|
||||||
|
ozone|n from magical experiments. The floor changes from marble to
|
||||||
|
|ydurable stone flagging|n, scuffed and stained from years of heavy use.
|
||||||
|
|
||||||
|
The entrance hall is a |yhub of activity|n, with students rushing
|
||||||
|
to and from various workshops, carrying materials, tools, and
|
||||||
|
half-finished projects. |yLarge directional signs|n point the way
|
||||||
|
to specialized areas: |ySmithy|n to the |ynorth|n, |yAlchemy Lab|n to
|
||||||
|
the |yeast|n, |yWoodworking|n to the |ywest|n, and |yArtificing|n below.
|
||||||
|
|
||||||
|
A |ysafety station|n dominates one wall, equipped with emergency
|
||||||
|
showers, healing potion dispensers, and fire suppression crystals.
|
||||||
|
|yWarning signs|n in multiple languages caution about the dangers
|
||||||
|
inherent in practical magic and crafting.
|
||||||
|
|
||||||
|
The ceiling is unusually high, with |yexposed beams|n and |ymagical
|
||||||
|
ventilation shafts|n that whisk away dangerous fumes. |YTool rental|n
|
||||||
|
and |ymaterial storage|n offices line the walls, staffed by
|
||||||
|
experienced artisans who check out equipment to qualified students.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.db.atmosphere = {
|
||||||
|
"mood": "industrious, focused, slightly dangerous, energetic",
|
||||||
|
"lighting": "bright work lights, sparks from forges, magical glows",
|
||||||
|
"sounds": "hammering, sawing, sizzling, shouted warnings, machinery",
|
||||||
|
"smells": "hot metal, sawdust, chemicals, sweat, burning",
|
||||||
|
"temperature": "warm to hot, varying by proximity to forges"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.exits = {
|
||||||
|
"north": "The Great Smithy",
|
||||||
|
"east": "Alchemy Laboratories",
|
||||||
|
"west": "Woodworking Studio",
|
||||||
|
"down": "Artificing Chambers",
|
||||||
|
"south": "Academy Main Corridor"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.objects = [
|
||||||
|
"safety station with emergency equipment",
|
||||||
|
"directional signs to workshop areas",
|
||||||
|
"tool rental office counter",
|
||||||
|
"material storage lockers",
|
||||||
|
"fire suppression crystal clusters",
|
||||||
|
"magical ventilation shafts",
|
||||||
|
"healing potion dispensers",
|
||||||
|
"project submission desk",
|
||||||
|
"safety notice boards",
|
||||||
|
"protective equipment racks"
|
||||||
|
]
|
||||||
|
|
||||||
|
class GreatSmithy(DefaultRoom):
|
||||||
|
"""
|
||||||
|
The main blacksmithing and metalworking workshop.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_object_creation(self):
|
||||||
|
super().at_object_creation()
|
||||||
|
|
||||||
|
self.key = "The Great Smithy"
|
||||||
|
self.aliases = ["smithy", "forge", "metal shop", "blacksmith hall"]
|
||||||
|
|
||||||
|
self.db.desc = """
|
||||||
|
|wThe Great Smithy|n
|
||||||
|
|
||||||
|
|yHeat blasts|n against your face as you enter the largest smithy
|
||||||
|
in the academy. The air shimmers with |ythermal distortion|n rising
|
||||||
|
from a dozen |ymagical forges|n that burn with flames of varying
|
||||||
|
colors—normal orange, enchanted blue, and the rare |ywhite-hot
|
||||||
|
dragon-fire|n used for working the most resilient alloys.
|
||||||
|
|
||||||
|
|yMassive anvils|n of steel, meteoric iron, and enchanted stone
|
||||||
|
ring with the |yconstant percussion|n of hammers. Apprentice smiths
|
||||||
|
work at smaller stations along the walls, while master artisans
|
||||||
|
occupy the central positions, their movements precise and practiced.
|
||||||
|
|YSparks cascade|n like fiery rain, magically contained within
|
||||||
|
safety barriers.
|
||||||
|
|
||||||
|
The walls are lined with |ytool racks|n holding every implement
|
||||||
|
of the metalworker's art—tongs in dozens of sizes, hammers from
|
||||||
|
delicate planishing to heavy sledges, chisels, punches, and
|
||||||
|
specialized magical implements. |YQuenching troughs|n line one
|
||||||
|
wall, filled with water, oil, and exotic magical solutions.
|
||||||
|
|
||||||
|
A |ycompleted works display|n near the entrance showcases exceptional
|
||||||
|
student projects—enchanted blades, intricate armor, and delicate
|
||||||
|
jewelry that demonstrate the heights of metallurgical artistry.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.db.atmosphere = {
|
||||||
|
"mood": "intense, physical, dangerous, creative",
|
||||||
|
"lighting": "fire-glow, orange and blue flames, flying sparks",
|
||||||
|
"sounds": "hammering, sizzling quenches, roaring flames, bellows",
|
||||||
|
"smells": "hot iron, coal smoke, quenching oil, sweat, sulfur",
|
||||||
|
"temperature": "extremely hot, heat shimmers visible"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.exits = {
|
||||||
|
"south": "Workshop Wing Entrance",
|
||||||
|
"north": "Master Smith's Private Forge",
|
||||||
|
"east": "Armor Assembly Bay",
|
||||||
|
"up": "Enchanted Metallurgy Lab"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.objects = [
|
||||||
|
"dozen magical forges with colored flames",
|
||||||
|
"massive anvils of various materials",
|
||||||
|
"complete tool racks with smithing implements",
|
||||||
|
"quenching troughs with multiple solutions",
|
||||||
|
"magical spark containment barriers",
|
||||||
|
"completed works display case",
|
||||||
|
"large bellows operated by apprentices",
|
||||||
|
"metal storage bins sorted by type",
|
||||||
|
"measuring and marking tools",
|
||||||
|
"enchantment application stations"
|
||||||
|
]
|
||||||
|
|
||||||
|
class AlchemyLaboratories(DefaultRoom):
|
||||||
|
"""
|
||||||
|
The primary alchemy and potion-making facility.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_object_creation(self):
|
||||||
|
super().at_object_creation()
|
||||||
|
|
||||||
|
self.key = "Alchemy Laboratories"
|
||||||
|
self.aliases = ["alchemy lab", "potion workshop", "brewing hall"]
|
||||||
|
|
||||||
|
self.db.desc = """
|
||||||
|
|wAlchemy Laboratories|n
|
||||||
|
|
||||||
|
The pungent aroma of |yhundreds of reagents|n greets you in this
|
||||||
|
sprawling laboratory complex. The space is divided into |ystations|n
|
||||||
|
by tall workbenches of |yslate and acid-resistant oak|n, each
|
||||||
|
equipped with a |yfull array of alchemical equipment|n. The lighting
|
||||||
|
is carefully controlled—neither too bright nor too dim—perfect
|
||||||
|
for observing the subtle color changes that indicate reaction progress.
|
||||||
|
|
||||||
|
|yGlassware of every description|n hangs from overhead racks:
|
||||||
|
beakers, flasks, retorts, alembics, and specialized apparatus
|
||||||
|
for distillation, filtration, and crystallization. |YCabinets|n
|
||||||
|
along the walls hold organized reagents, from common herbs to
|
||||||
|
exotic monster parts, each labeled with |yprecautionary runes|n.
|
||||||
|
|
||||||
|
At the center of the room, a |yfume hood|n the size of a small
|
||||||
|
house contains the most dangerous experiments. Its magical
|
||||||
|
extraction system hums constantly, drawing away toxic vapors
|
||||||
|
before they can harm the alchemists. |YEmergency showers|n and
|
||||||
|
|yneutralizing stations|n stand ready at each corner.
|
||||||
|
|
||||||
|
|yStudent workstations|n show various stages of potion brewing—some
|
||||||
|
bubbling merrily, others frozen at precise temperatures, still
|
||||||
|
others glowing with inner light. The |xatmosphere of careful
|
||||||
|
concentration|n is palpable as practitioners measure, mix, and
|
||||||
|
monitor their creations.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.db.atmosphere = {
|
||||||
|
"mood": "focused, meticulous, slightly tense, scientific",
|
||||||
|
"lighting": "controlled magical illumination, colored by reactions",
|
||||||
|
"sounds": "bubbling, hissing, grinding, whispered calculations",
|
||||||
|
"smells": "herbs, chemicals, sulfur, flowers, acrid smoke",
|
||||||
|
"temperature": "controlled, varying by station requirements"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.exits = {
|
||||||
|
"west": "Workshop Wing Entrance",
|
||||||
|
"north": "Advanced Brewing Chambers",
|
||||||
|
"east": "Reagent Storage Vault",
|
||||||
|
"down": "Potion Testing Range"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.objects = [
|
||||||
|
"slate and oak workbenches with equipment",
|
||||||
|
"hanging racks of glassware",
|
||||||
|
"organized reagent cabinets",
|
||||||
|
"massive central fume hood",
|
||||||
|
"magical vapor extraction system",
|
||||||
|
"emergency safety stations",
|
||||||
|
"temperature-controlled brewing vessels",
|
||||||
|
"precise measurement scales",
|
||||||
|
"reagent compendium library",
|
||||||
|
"finished potion display and storage"
|
||||||
|
]
|
||||||
|
|
||||||
|
class WoodworkingStudio(DefaultRoom):
|
||||||
|
"""
|
||||||
|
The woodworking and carpentry workshop.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_object_creation(self):
|
||||||
|
super().at_object_creation()
|
||||||
|
|
||||||
|
self.key = "Woodworking Studio"
|
||||||
|
self.aliases = ["wood shop", "carpentry", "carving hall"]
|
||||||
|
|
||||||
|
self.db.desc = """
|
||||||
|
|wWoodworking Studio|n
|
||||||
|
|
||||||
|
The |yscent of fresh sawdust and wood resin|n fills this well-lit
|
||||||
|
workshop dedicated to the crafting of wooden goods. Unlike the
|
||||||
|
heat of the smithy, the atmosphere here is |ycool and pleasant|n,
|
||||||
|
with excellent ventilation carrying away sawdust and finishing
|
||||||
|
fumes. Natural light floods through |ylarge north-facing windows|n,
|
||||||
|
supplemented by |ymagical illumination|n that reveals wood grain
|
||||||
|
without glare.
|
||||||
|
|
||||||
|
|yWorkbenches|n line the walls, each customized to its regular
|
||||||
|
user's height and preferences. |YHand tools|n hang in precise
|
||||||
|
arrangements—saws, chisels, planes, gouges, and hundreds of
|
||||||
|
other implements. |YPower tools|n enhanced by magical automation
|
||||||
|
occupy the central area: lathes, band saws, and joiners that
|
||||||
|
would take teams of workers to operate conventionally.
|
||||||
|
|
||||||
|
Racks of |yseasoned lumber|n stand organized by species and cut,
|
||||||
|
from common pine to exotic |ymana-wood|n harvested from enchanted
|
||||||
|
forests. A |yfinishing room|n at the back provides space for
|
||||||
|
applying stains, varnishes, and protective enchantments.
|
||||||
|
|
||||||
|
The walls display |yexamples of fine craftsmanship|n—carved panels,
|
||||||
|
intricate furniture, and magical wands in various stages of
|
||||||
|
completion. The |xatmosphere is one of patient precision|n, where
|
||||||
|
rushing leads to ruined work and careful attention yields masterpieces.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.db.atmosphere = {
|
||||||
|
"mood": "patient, focused, creative, grounded",
|
||||||
|
"lighting": "natural north light, soft magical supplement",
|
||||||
|
"sounds": "sawing, planing, gentle hammering, wood whispering",
|
||||||
|
"smells": "sawdust, wood resin, linseed oil, fresh lumber",
|
||||||
|
"temperature": "cool and comfortable, well-ventilated"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.exits = {
|
||||||
|
"east": "Workshop Wing Entrance",
|
||||||
|
"north": "Wand Carving Studio",
|
||||||
|
"west": "Lumber Storage Yard",
|
||||||
|
"up": "Furniture Design Gallery"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.objects = [
|
||||||
|
"customized workbenches for each user",
|
||||||
|
"organized hand tool collections",
|
||||||
|
"magically enhanced power tools",
|
||||||
|
"seasoned lumber racks by species",
|
||||||
|
"finishing room with ventilation",
|
||||||
|
"examples of fine craftsmanship",
|
||||||
|
"wand blanks and carving tools",
|
||||||
|
"wood species reference library",
|
||||||
|
"sharpening station for edge tools",
|
||||||
|
"dust collection magical system"
|
||||||
|
]
|
||||||
|
|
||||||
|
class ArtificingChambers(DefaultRoom):
|
||||||
|
"""
|
||||||
|
The underground chambers for magical device creation and enchanting.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_object_creation(self):
|
||||||
|
super().at_object_creation()
|
||||||
|
|
||||||
|
self.key = "Artificing Chambers"
|
||||||
|
self.aliases = ["artificing", "enchantment lab", "device workshop"]
|
||||||
|
|
||||||
|
self.db.desc = """
|
||||||
|
|wArtificing Chambers|n
|
||||||
|
|
||||||
|
Descending into the |yArtificing Chambers|n feels like entering
|
||||||
|
another world. The stone walls here are |yancient|n, predating
|
||||||
|
much of the academy above, carved by unknown hands and later
|
||||||
|
reinforced with |ymagical wards|n. The air tastes of |ypower|n—
|
||||||
|
the accumulated residue of centuries of enchantment work.
|
||||||
|
|
||||||
|
The main chamber is |ycircular|n, with |yseven workstations|n
|
||||||
|
arranged around a |ycentral power nexus|n—a glowing crystal
|
||||||
|
formation that provides stable magical energy for charging
|
||||||
|
artifacts. Each station is |yspecially shielded|n to prevent
|
||||||
|
interference between simultaneous enchantment projects.
|
||||||
|
|
||||||
|
|yGlass cases|n along the walls contain |ydangerous magical
|
||||||
|
components|n—souls bound in gems, elemental essences, captured
|
||||||
|
starlight, and other materials that require careful handling.
|
||||||
|
|YRunes|n carved into the floor and ceiling provide containment
|
||||||
|
and focus for the energies manipulated here.
|
||||||
|
|
||||||
|
The |xatmosphere is heavy with potential|n. This is where
|
||||||
|
ordinary objects become magical artifacts, where the marriage
|
||||||
|
of craft and sorcery produces items of legend. The work requires
|
||||||
|
equal parts technical skill, magical aptitude, and creative
|
||||||
|
vision. |YMaster artificers|n move between stations, offering
|
||||||
|
guidance to students attempting their first permanent enchantments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.db.atmosphere = {
|
||||||
|
"mood": "mysterious, powerful, focused, reverent",
|
||||||
|
"lighting": "glowing runes, crystal nexus, magical item auras",
|
||||||
|
"sounds": "humming power, whispered incantations, soft chimes",
|
||||||
|
"smells": "ozone, old stone, magical residues, rare incense",
|
||||||
|
"temperature": "cool and stable, climate controlled for artifacts"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.exits = {
|
||||||
|
"up": "Workshop Wing Entrance",
|
||||||
|
"north": "Enchanted Vault",
|
||||||
|
"east": "Rune Inscription Studio",
|
||||||
|
"down": "Deep Containment"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.objects = [
|
||||||
|
"seven shielded enchantment workstations",
|
||||||
|
"central power nexus crystal formation",
|
||||||
|
"glass cases with magical components",
|
||||||
|
"floor and ceiling containment runes",
|
||||||
|
"artifact charging stations",
|
||||||
|
"magical measuring instruments",
|
||||||
|
"enchantment formula library",
|
||||||
|
"emergency mana dampeners",
|
||||||
|
"artifact stabilization fields",
|
||||||
|
"master artificer consultation desk"
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user