commit
c1ced4ff33
63 changed files with 2438 additions and 0 deletions
@ -0,0 +1 @@ |
|||||||
|
docker run -it --rm -p 4000:4000 -p 4001:4001 -p 4002:4002 --rm -v $PWD:/usr/src/game --user $UID:$GID evennia/evennia |
@ -0,0 +1,52 @@ |
|||||||
|
*.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 |
||||||
|
*.pid |
||||||
|
*.restart |
||||||
|
*.db3 |
||||||
|
|
||||||
|
# Installation-specific. |
||||||
|
# For group efforts, comment out some or all of these. |
||||||
|
server/conf/secret_settings.py |
||||||
|
server/logs/*.log.* |
||||||
|
web/static/* |
||||||
|
web/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 |
@ -0,0 +1,8 @@ |
|||||||
|
[MESSAGE CONTROL] |
||||||
|
|
||||||
|
disable=C0103, E0401 |
||||||
|
|
||||||
|
[VARIABLES] |
||||||
|
|
||||||
|
additional-builtins=caller |
||||||
|
|
@ -0,0 +1,2 @@ |
|||||||
|
Exit inside item, can set to any room while inside it then teleport to that room |
||||||
|
|
@ -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! |
@ -0,0 +1,6 @@ |
|||||||
|
""" |
||||||
|
This sub-package holds the template for creating a new game folder. |
||||||
|
The new game folder (when running evennia --init) is a copy of this |
||||||
|
folder. |
||||||
|
|
||||||
|
""" |
@ -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,0 +1,188 @@ |
|||||||
|
""" |
||||||
|
Commands |
||||||
|
|
||||||
|
Commands describe the input the account can do to the game. |
||||||
|
|
||||||
|
""" |
||||||
|
|
||||||
|
from evennia import Command as BaseCommand |
||||||
|
|
||||||
|
# from evennia import default_cmds |
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand): |
||||||
|
""" |
||||||
|
Inherit from this if you want to create your own command styles |
||||||
|
from scratch. Note that Evennia's default commands inherits from |
||||||
|
MuxCommand instead. |
||||||
|
|
||||||
|
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. |
||||||
|
|
||||||
|
Each Command 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 |
@ -0,0 +1,97 @@ |
|||||||
|
""" |
||||||
|
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.override_cmds import CmdSetOverride |
||||||
|
|
||||||
|
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() |
||||||
|
self.add(CmdSetOverride()) |
||||||
|
# |
||||||
|
# any commands you add below will overload the default ones. |
||||||
|
# |
||||||
|
|
||||||
|
|
||||||
|
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. |
||||||
|
# |
@ -0,0 +1,36 @@ |
|||||||
|
''' |
||||||
|
Command and command set for using a Keypad |
||||||
|
''' |
||||||
|
|
||||||
|
from evennia import Command, CmdSet |
||||||
|
|
||||||
|
class CmdUseKeypad(Command): |
||||||
|
''' |
||||||
|
Use a keypad |
||||||
|
|
||||||
|
Usage: |
||||||
|
use [the] keypad |
||||||
|
''' |
||||||
|
|
||||||
|
key = 'use keypad' |
||||||
|
aliases = ['use the keypad', 'unlock'] |
||||||
|
|
||||||
|
def func(self): |
||||||
|
''' |
||||||
|
Implement the command |
||||||
|
''' |
||||||
|
# Have to yield here since Evennia only allows yield in command func |
||||||
|
keycode = yield('Enter keycode: ') |
||||||
|
self.obj.attempt_unlock(self.caller, keycode) |
||||||
|
|
||||||
|
class CmdSetKeypad(CmdSet): |
||||||
|
''' |
||||||
|
Command set for using a keypad |
||||||
|
''' |
||||||
|
key = 'cmdset_keypad' |
||||||
|
|
||||||
|
def at_cmdset_creation(self): |
||||||
|
'Attach commands to the command set' |
||||||
|
self.add(CmdUseKeypad()) |
||||||
|
|
||||||
|
|
@ -0,0 +1,150 @@ |
|||||||
|
''' |
||||||
|
Commands and command set related to Mechs |
||||||
|
''' |
||||||
|
|
||||||
|
from evennia import Command, CmdSet |
||||||
|
from evennia.utils.utils import list_to_string |
||||||
|
|
||||||
|
# TODO: Pullout disconnected characters |
||||||
|
class CmdPilot(Command): |
||||||
|
''' |
||||||
|
Pilot a thing |
||||||
|
|
||||||
|
Usage: |
||||||
|
pilot [target] |
||||||
|
''' |
||||||
|
|
||||||
|
key = 'pilot' |
||||||
|
aliases = ['enter'] |
||||||
|
locks = 'cmd:not cmdinside()' |
||||||
|
|
||||||
|
def func(self): |
||||||
|
''' |
||||||
|
Implement the command |
||||||
|
''' |
||||||
|
caller = self.caller |
||||||
|
location = caller.location |
||||||
|
|
||||||
|
if not self.args: |
||||||
|
message = "You must give a target" |
||||||
|
caller.msg(message) |
||||||
|
return |
||||||
|
|
||||||
|
target = caller.search(self.args.strip()) |
||||||
|
if not target: |
||||||
|
return |
||||||
|
|
||||||
|
drivers = list(filter(lambda x: x.is_connected, self.obj.contents)) |
||||||
|
|
||||||
|
if drivers: |
||||||
|
caller.msg(f"This mech is currently piloted by {drivers[0]}, you cannot enter") |
||||||
|
return |
||||||
|
|
||||||
|
caller_msg = "You climb into the %s" % target.name |
||||||
|
location_msg = "%s climbs into the %s" % (caller.name, target.name) |
||||||
|
caller.msg(caller_msg) |
||||||
|
location.msg_contents(location_msg, exclude=[caller]) |
||||||
|
self.caller.move_to(self.obj, quiet=True) |
||||||
|
|
||||||
|
class CmdLeave(Command): |
||||||
|
''' |
||||||
|
Leave a thing |
||||||
|
|
||||||
|
Usage: |
||||||
|
leave |
||||||
|
''' |
||||||
|
|
||||||
|
key = 'leave' |
||||||
|
aliases = ['exit'] |
||||||
|
locks = 'cmd:cmdinside()' |
||||||
|
|
||||||
|
def func(self): |
||||||
|
''' |
||||||
|
Implement the command |
||||||
|
''' |
||||||
|
caller = self.caller |
||||||
|
|
||||||
|
caller_msg = "You climb out of the %s" % self.obj.name |
||||||
|
location_msg = "%s climbs out of the %s" % (caller.name, self.obj.name) |
||||||
|
caller.msg(caller_msg) |
||||||
|
self.obj.location.msg_contents(location_msg, exclude=[caller, self.obj]) |
||||||
|
self.caller.move_to(self.obj.location, quiet=True) |
||||||
|
|
||||||
|
class CmdHUD(Command): |
||||||
|
''' |
||||||
|
Look outside the mech |
||||||
|
|
||||||
|
Usage: |
||||||
|
hud |
||||||
|
''' |
||||||
|
|
||||||
|
key = 'HUD' |
||||||
|
aliases = ['hud'] |
||||||
|
locks = 'cmd:cmdinside()' |
||||||
|
|
||||||
|
def func(self): |
||||||
|
'Implement the command' |
||||||
|
caller = self.caller |
||||||
|
outside = self.obj.location |
||||||
|
|
||||||
|
caller.msg(outside.return_appearance(self.caller, exclude=[self.obj])) |
||||||
|
|
||||||
|
class CmdDrive(Command): |
||||||
|
''' |
||||||
|
Drive the mech |
||||||
|
|
||||||
|
Usage: |
||||||
|
drive to [location] |
||||||
|
''' |
||||||
|
|
||||||
|
key = 'drive' |
||||||
|
locks = 'cmd:cmdinside()' |
||||||
|
|
||||||
|
def parse(self): |
||||||
|
if self.args: |
||||||
|
parsed_args = [x for x in self.args.split() if x not in ('to', 'the', 'into')] |
||||||
|
self.args = ' '.join(parsed_args) |
||||||
|
|
||||||
|
def func(self): |
||||||
|
'Implement the command' |
||||||
|
if not self.args: |
||||||
|
self.caller.msg('You must provide a location to drive to') |
||||||
|
return |
||||||
|
|
||||||
|
search_results = self.obj.search(self.args, quiet=True) |
||||||
|
if not search_results: |
||||||
|
self.caller.msg(f"The mech doesn't understand how to drive to '{self.args}'") |
||||||
|
return |
||||||
|
|
||||||
|
if len(search_results) > 1: |
||||||
|
self.caller.msg('More than one result found, please be more specific') |
||||||
|
self.caller.msg(f"Results: {list_to_string(list(search_results))}") |
||||||
|
return |
||||||
|
|
||||||
|
result = search_results[0] |
||||||
|
if result == self.caller.location: |
||||||
|
self.caller.msg('You cannot drive into yourself') |
||||||
|
return |
||||||
|
|
||||||
|
# Only exits should have a '.destination' |
||||||
|
if not result.destination: |
||||||
|
self.caller.msg(f"You cannot drive into {self.args}") |
||||||
|
return |
||||||
|
|
||||||
|
self.caller.msg(f"Driving to {result.destination}") |
||||||
|
self.obj.move_to(result) |
||||||
|
self.caller.execute_cmd('HUD') |
||||||
|
|
||||||
|
class CmdSetMech(CmdSet): |
||||||
|
''' |
||||||
|
Commands for mechs |
||||||
|
''' |
||||||
|
|
||||||
|
def at_cmdset_creation(self): |
||||||
|
'Attach commands to the command set' |
||||||
|
self.add(CmdDrive()) |
||||||
|
self.add(CmdHUD()) |
||||||
|
self.add(CmdLeave()) |
||||||
|
self.add(CmdPilot()) |
||||||
|
|
||||||
|
|
@ -0,0 +1,41 @@ |
|||||||
|
''' |
||||||
|
Modified default commands |
||||||
|
''' |
||||||
|
|
||||||
|
from evennia import Command, CmdSet |
||||||
|
from evennia.commands.default import ( |
||||||
|
general |
||||||
|
) |
||||||
|
|
||||||
|
# TODO: Needs tests |
||||||
|
class CmdOverrideLook(general.CmdLook): |
||||||
|
''' |
||||||
|
Look command but parses out certain words |
||||||
|
''' |
||||||
|
|
||||||
|
def parse(self): |
||||||
|
if self.args: |
||||||
|
parsed_args = [x for x in self.args.split() if x not in ('at', 'the')] |
||||||
|
self.args = ' '.join(parsed_args) |
||||||
|
|
||||||
|
def func(self): |
||||||
|
''' |
||||||
|
Call original but now with new parsed args |
||||||
|
''' |
||||||
|
# I don't like this here...but I can't do it on the Mech CmdSet because it isn't on the |
||||||
|
# character |
||||||
|
if self.args and self.caller.location.typename == 'Mech': |
||||||
|
results = self.caller.location.search(self.args) |
||||||
|
if results: |
||||||
|
self.caller.msg(f"You can't see that very well from inside a mech") |
||||||
|
return |
||||||
|
super().func() |
||||||
|
|
||||||
|
class CmdSetOverride(CmdSet): |
||||||
|
''' |
||||||
|
Container for overridden commands |
||||||
|
''' |
||||||
|
|
||||||
|
def at_cmdset_creation(self): |
||||||
|
'Attach commands to the command set' |
||||||
|
self.add(CmdOverrideLook()) |
@ -0,0 +1,25 @@ |
|||||||
|
''' |
||||||
|
Tests for CmdPilot |
||||||
|
''' |
||||||
|
|
||||||
|
from evennia.commands.default.tests import CommandTest |
||||||
|
from evennia import create_object |
||||||
|
from mock import Mock |
||||||
|
from typeclasses.exits import KeypadDoor |
||||||
|
from commands.keypad import * |
||||||
|
from evennia.commands.default.cmdset_character import CharacterCmdSet |
||||||
|
import re |
||||||
|
|
||||||
|
class TestCmdUseKeypad(CommandTest): |
||||||
|
'Tests for CmdPilot' |
||||||
|
|
||||||
|
def setUp(self): |
||||||
|
'Setup environment for testing mech commands' |
||||||
|
super().setUp() |
||||||
|
self.keypad_door = create_object(KeypadDoor, key='door', location=self.room1) |
||||||
|
|
||||||
|
def test_func_without_arg(self): |
||||||
|
'Tests func method' |
||||||
|
|
||||||
|
self.call(CmdUseKeypad(), '', 'Please enter keycode: ') |
||||||
|
|
@ -0,0 +1,104 @@ |
|||||||
|
''' |
||||||
|
Tests for CmdPilot |
||||||
|
''' |
||||||
|
|
||||||
|
from evennia.commands.default.tests import CommandTest |
||||||
|
from evennia import create_object |
||||||
|
from mock import Mock |
||||||
|
from typeclasses import mech |
||||||
|
from commands.mech_cmds import * |
||||||
|
from evennia.commands.default.help import CmdHelp |
||||||
|
from evennia.commands.default.cmdset_character import CharacterCmdSet |
||||||
|
import re |
||||||
|
|
||||||
|
# TODO: Make [0][2]['text'][0] not a thing... |
||||||
|
# TODO: Test lockfunc cmdinside (self.call can't move location) |
||||||
|
|
||||||
|
class MechCmdHelper(CommandTest): |
||||||
|
'Helper class for mech command tests' |
||||||
|
|
||||||
|
def setUp(self): |
||||||
|
'Setup environment for testing mech commands' |
||||||
|
super().setUp() |
||||||
|
self.mech = create_object(mech.Mech, key='mech', |
||||||
|
location=self.room1, aliases=['mech']) |
||||||
|
|
||||||
|
# Need to remove permissions to test locks |
||||||
|
self.char1.account.permissions.remove('Developer') |
||||||
|
|
||||||
|
# Place Char 1 inside mech |
||||||
|
self.char1.move_to(self.mech) |
||||||
|
|
||||||
|
class TestCmdPilot(MechCmdHelper): |
||||||
|
'Tests for CmdPilot' |
||||||
|
|
||||||
|
def setUp(self): |
||||||
|
'Setup tests for pilot command' |
||||||
|
super().setUp() |
||||||
|
|
||||||
|
# Ensure characters start outside of the mech |
||||||
|
self.char1.move_to(self.mech.location) |
||||||
|
self.char2.move_to(self.mech.location) |
||||||
|
|
||||||
|
def test_func_without_arg(self): |
||||||
|
'Tests func method' |
||||||
|
|
||||||
|
self.call(CmdPilot(), '', 'You must give a target') |
||||||
|
|
||||||
|
def test_func_with_arg(self): |
||||||
|
# Store messages received by char2 |
||||||
|
self.char1.msg = Mock() |
||||||
|
self.char2.msg = Mock() |
||||||
|
|
||||||
|
self.assertNotIn(self.char1, self.mech.contents) |
||||||
|
self.char1.execute_cmd('pilot mech') |
||||||
|
self.assertIn(self.char1, self.mech.contents) |
||||||
|
|
||||||
|
# Check messages sent |
||||||
|
self.assertEqual(self.char1.msg.mock_calls[0][1][0], 'You climb into the mech') |
||||||
|
self.assertEqual(self.char2.msg.mock_calls[0][2]['text'][0], 'Char climbs into the mech') |
||||||
|
|
||||||
|
def test_func_occupied(self): |
||||||
|
'Test trying to pilot an occupied mech' |
||||||
|
# It's easier to use char2 to test because `char2.is_connected` returns true |
||||||
|
self.char1.move_to(self.mech) |
||||||
|
|
||||||
|
self.char2.msg = Mock() |
||||||
|
self.char2.execute_cmd('pilot mech') |
||||||
|
self.assertEqual(self.char2.msg.mock_calls[0][1][0], 'This mech is currently piloted by Char, you cannot enter') |
||||||
|
self.assertNotIn(self.char2, self.mech.contents) |
||||||
|
|
||||||
|
class TestCmdLeave(MechCmdHelper): |
||||||
|
'Tests for CmdLeave' |
||||||
|
|
||||||
|
def test_func(self): |
||||||
|
'Tests func method' |
||||||
|
|
||||||
|
# Store messages received by chararacters |
||||||
|
self.char1.msg = Mock() |
||||||
|
self.char2.msg = Mock() |
||||||
|
|
||||||
|
self.char1.execute_cmd('leave mech') |
||||||
|
|
||||||
|
self.assertEqual(self.char1.msg.mock_calls[0][1][0], 'You climb out of the mech') |
||||||
|
self.assertEqual(self.char2.msg.mock_calls[0][2]['text'][0], 'Char climbs out of the mech') |
||||||
|
|
||||||
|
class TestCmdHUD(MechCmdHelper): |
||||||
|
'Tests for CmdHud' |
||||||
|
|
||||||
|
def test_func(self): |
||||||
|
'Tests func method' |
||||||
|
|
||||||
|
self.char1.msg = Mock() |
||||||
|
self.char1.execute_cmd('hud') |
||||||
|
|
||||||
|
output = self.char1.msg.mock_calls[0][1][0] |
||||||
|
self.assertEqual(output, self.room1.return_appearance(self.char1, exclude=[self.mech])) |
||||||
|
|
||||||
|
class TestCmdDrive(MechCmdHelper): |
||||||
|
'Tests for CmdDrive' |
||||||
|
|
||||||
|
def test_func(self): |
||||||
|
'Tests func method' |
||||||
|
self.char1.execute_cmd('drive to out') |
||||||
|
self.assertEqual(self.mech.location, self.room2) |
@ -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. |
@ -0,0 +1 @@ |
|||||||
|
# -*- coding: utf-8 -*- |
@ -0,0 +1 @@ |
|||||||
|
# -*- coding: utf-8 -*- |
@ -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 |
@ -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. |
||||||
|
|
||||||
|
Kwargs: |
||||||
|
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. |
||||||
|
|
||||||
|
""" |
@ -0,0 +1,63 @@ |
|||||||
|
""" |
||||||
|
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_start() |
||||||
|
at_server_stop() |
||||||
|
at_server_reload_start() |
||||||
|
at_server_reload_stop() |
||||||
|
at_server_cold_start() |
||||||
|
at_server_cold_stop() |
||||||
|
|
||||||
|
""" |
||||||
|
|
||||||
|
|
||||||
|
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 |
@ -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 |
@ -0,0 +1,39 @@ |
|||||||
|
# -*- 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") |
||||||
|
) |
@ -0,0 +1,51 @@ |
|||||||
|
""" |
||||||
|
Inlinefunc |
||||||
|
|
||||||
|
Inline functions allow for direct conversion of text users mark in a |
||||||
|
special way. Inlinefuncs are deactivated by default. To activate, add |
||||||
|
|
||||||
|
INLINEFUNC_ENABLED = True |
||||||
|
|
||||||
|
to your settings file. The default inlinefuncs are found in |
||||||
|
evennia.utils.inlinefunc. |
||||||
|
|
||||||
|
In text, usage is straightforward: |
||||||
|
|
||||||
|
$funcname([arg1,[arg2,...]]) |
||||||
|
|
||||||
|
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(text, *args, **kwargs) |
||||||
|
|
||||||
|
where `text` is always the part between {funcname(args) and |
||||||
|
{/funcname and the *args are taken from the appropriate part of the |
||||||
|
call. If no {/funcname is given, `text` will be the empty string. |
||||||
|
|
||||||
|
It is important that the inline function properly clean the |
||||||
|
incoming `args`, checking their type and replacing them with sane |
||||||
|
defaults if needed. If impossible to resolve, the unmodified text |
||||||
|
should be returned. The inlinefunc should never cause a traceback. |
||||||
|
|
||||||
|
While the inline function should accept **kwargs, the keyword is |
||||||
|
never accepted as a valid call - this is only intended to be used |
||||||
|
internally by Evennia, notably to send the `session` keyword to |
||||||
|
the function; this is the session of the object viewing the string |
||||||
|
and can be used to customize it to each session. |
||||||
|
|
||||||
|
""" |
||||||
|
|
||||||
|
# def capitalize(text, *args, **kwargs): |
||||||
|
# "Silly capitalize example. Used as {capitalize() ... {/capitalize" |
||||||
|
# session = kwargs.get("session") |
||||||
|
# return text.capitalize() |
@ -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 |
@ -0,0 +1,47 @@ |
|||||||
|
""" |
||||||
|
|
||||||
|
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 |
||||||
|
|
||||||
|
def cmdinside(accessing_obj, accessed_obj, *args, **kwargs): |
||||||
|
''' |
||||||
|
Usage: cmdinside() |
||||||
|
|
||||||
|
Used to lock commands and only allows access if the command |
||||||
|
is defined on an object which accessing_obj is inside of. |
||||||
|
''' |
||||||
|
return accessed_obj.obj == accessing_obj.location |
||||||
|
|
||||||
|
def is_a(accessing_obj, accessed_obj, expected_type=None): |
||||||
|
''' |
||||||
|
Usage: is_a(typename='Example') |
||||||
|
|
||||||
|
Used to determine if a certain object matches expected object |
||||||
|
''' |
||||||
|
return type(accessing_obj) == expected_type or accessing_obj.typename == expected_type |
@ -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": "Evennia", |
||||||
|
# Generic |
||||||
|
"CRAWL DELAY": "-1", # limit how often crawler updates the listing. -1 for no limit |
||||||
|
"HOSTNAME": "", # current or new hostname |
||||||
|
"PORT": ["4000"], # 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": "www.evennia.com", |
||||||
|
# Categorisation |
||||||
|
"FAMILY": "Custom", # evennia goes under 'Custom' |
||||||
|
"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", # 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 available. |
||||||
|
"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) |
||||||
|
"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", |
||||||
|
} |
@ -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 |
@ -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 |
@ -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 |
@ -0,0 +1,44 @@ |
|||||||
|
r""" |
||||||
|
Evennia settings file. |
||||||
|
|
||||||
|
The available options are found in the default settings file found |
||||||
|
here: |
||||||
|
|
||||||
|
/usr/src/evennia/evennia/settings_default.py |
||||||
|
|
||||||
|
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 = "taoshengshi" |
||||||
|
|
||||||
|
|
||||||
|
###################################################################### |
||||||
|
# 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.") |
@ -0,0 +1,28 @@ |
|||||||
|
""" |
||||||
|
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 |
@ -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. |
@ -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,0 +1,104 @@ |
|||||||
|
""" |
||||||
|
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 import DefaultAccount, DefaultGuest |
||||||
|
|
||||||
|
|
||||||
|
class Account(DefaultAccount): |
||||||
|
""" |
||||||
|
This class describes the actual OOC account (i.e. the user connecting |
||||||
|
to the MUD). It does NOT have visual appearance in the game world (that |
||||||
|
is handled by the character which is connected to this). Comm channels |
||||||
|
are attended/joined using this object. |
||||||
|
|
||||||
|
It can be useful e.g. for storing configuration options for your game, but |
||||||
|
should generally not hold any character-related info (that's best handled |
||||||
|
on the character level). |
||||||
|
|
||||||
|
Can be set using BASE_ACCOUNT_TYPECLASS. |
||||||
|
|
||||||
|
|
||||||
|
* available properties |
||||||
|
|
||||||
|
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. |
||||||
|
sessions (list of Sessions) - sessions connected to this account |
||||||
|
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(). |
||||||
|
|
||||||
|
* Helper methods |
||||||
|
|
||||||
|
msg(text=None, **kwargs) |
||||||
|
execute_cmd(raw_string, session=None) |
||||||
|
search(ostring, global_search=False, attribute_name=None, use_nicks=False, location=None, ignore_errors=False, account=False) |
||||||
|
is_typeclass(typeclass, exact=False) |
||||||
|
swap_typeclass(new_typeclass, clean_attributes=False, no_default=True) |
||||||
|
access(accessing_obj, access_type='read', default=False) |
||||||
|
check_permstring(permstring) |
||||||
|
|
||||||
|
* Hook methods (when re-implementation, remember methods need to have self as first arg) |
||||||
|
|
||||||
|
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_cmdset_get(**kwargs) |
||||||
|
at_first_login() |
||||||
|
at_post_login(session=None) |
||||||
|
at_disconnect() |
||||||
|
at_message_receive() |
||||||
|
at_message_send() |
||||||
|
at_server_reload() |
||||||
|
at_server_shutdown() |
||||||
|
|
||||||
|
""" |
||||||
|
|
||||||
|
pass |
||||||
|
|
||||||
|
|
||||||
|
class Guest(DefaultGuest): |
||||||
|
""" |
||||||
|
This class is used for guest logins. Unlike Accounts, Guests and their |
||||||
|
characters are deleted after disconnection. |
||||||
|
""" |
||||||
|
|
||||||
|
pass |
@ -0,0 +1,62 @@ |
|||||||
|
""" |
||||||
|
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 import DefaultChannel |
||||||
|
|
||||||
|
|
||||||
|
class Channel(DefaultChannel): |
||||||
|
""" |
||||||
|
Working methods: |
||||||
|
at_channel_creation() - called once, when the channel is created |
||||||
|
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) |
||||||
|
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) |
||||||
|
|
||||||
|
Useful hooks: |
||||||
|
channel_prefix(msg, emit=False) - 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. |
||||||
|
|
||||||
|
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 |
||||||
|
pre_send_message(msg) - runs just before a message is sent to channel |
||||||
|
post_send_message(msg) - called just after message was sent to channel |
||||||
|
|
||||||
|
""" |
||||||
|
|
||||||
|
pass |
@ -0,0 +1,34 @@ |
|||||||
|
""" |
||||||
|
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 import DefaultCharacter |
||||||
|
|
||||||
|
|
||||||
|
class Character(DefaultCharacter): |
||||||
|
""" |
||||||
|
The Character defaults to reimplementing some of base Object's hook methods with the |
||||||
|
following functionality: |
||||||
|
|
||||||
|
at_basetype_setup - always assigns the DefaultCmdSet to this object type |
||||||
|
(important!)sets locks so character cannot be picked up |
||||||
|
and its commands only be called by itself, not anyone else. |
||||||
|
(to change things, use at_object_creation() instead). |
||||||
|
at_after_move(source_location) - Launches the "look" command after every move. |
||||||
|
at_post_unpuppet(account) - when Account disconnects from the Character, we |
||||||
|
store the current location in the pre_logout_location Attribute and |
||||||
|
move it to a None-location so the "unpuppeted" character |
||||||
|
object does not need to stay on grid. Echoes "Account has disconnected" |
||||||
|
to the room. |
||||||
|
at_pre_puppet - Just before Account re-connects, retrieves the character's |
||||||
|
pre_logout_location Attribute and move it back on the grid. |
||||||
|
at_post_puppet - Echoes "AccountName has entered the game" to the room. |
||||||
|
|
||||||
|
""" |
||||||
|
|
||||||
|
# TODO: Override return_appearance |
@ -0,0 +1,64 @@ |
|||||||
|
""" |
||||||
|
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 import DefaultExit |
||||||
|
from commands.keypad import CmdSetKeypad |
||||||
|
from evennia.utils import delay |
||||||
|
|
||||||
|
class Exit(DefaultExit): |
||||||
|
""" |
||||||
|
Exits are connectors between rooms. Exits are normal Objects except |
||||||
|
they defines the `destination` property. It also does work in the |
||||||
|
following methods: |
||||||
|
|
||||||
|
basetype_setup() - sets default exit locks (to change, use `at_object_creation` instead). |
||||||
|
at_cmdset_get(**kwargs) - this is called when the cmdset is accessed and should |
||||||
|
rebuild the Exit cmdset along with a command matching the name |
||||||
|
of the Exit object. Conventionally, a kwarg `force_init` |
||||||
|
should force a rebuild of the cmdset, this is triggered |
||||||
|
by the `@alias` command when aliases are changed. |
||||||
|
at_failed_traverse() - gives a default error message ("You cannot |
||||||
|
go there") if exit traversal fails and an |
||||||
|
attribute `err_traverse` is not defined. |
||||||
|
|
||||||
|
Relevant hooks to overload (compared to other types of Objects): |
||||||
|
at_traverse(traveller, target_loc) - called to do the actual traversal and calling of the other hooks. |
||||||
|
If overloading this, consider using super() to use the default |
||||||
|
movement implementation (and hook-calling). |
||||||
|
at_after_traverse(traveller, source_loc) - called by at_traverse just after traversing. |
||||||
|
at_failed_traverse(traveller) - called by at_traverse if traversal failed for some reason. Will |
||||||
|
not be called if the attribute `err_traverse` is |
||||||
|
defined, in which case that will simply be echoed. |
||||||
|
""" |
||||||
|
|
||||||
|
def return_appearance(self, looker, **kwargs): |
||||||
|
'Modified version of what users normally see when using look' |
||||||
|
if not looker: |
||||||
|
return '' |
||||||
|
|
||||||
|
return self.db.desc |
||||||
|
|
||||||
|
class KeypadDoor(Exit): |
||||||
|
def at_object_creation(self): |
||||||
|
self.cmdset.add(CmdSetKeypad, permanent=True) |
||||||
|
self.db.keycode = '0000' |
||||||
|
self.db.desc = 'You see a locked door, |xunlock|n it with the keypad' |
||||||
|
|
||||||
|
def at_traverse(self, traveller, target_loc): |
||||||
|
if self.db.locked: |
||||||
|
traveller.msg('The door is locked, |xuse the keypad|n to unlock it') |
||||||
|
return |
||||||
|
self.db.locked = True |
||||||
|
traveller.msg('You hear the door lock itself again') |
||||||
|
super().at_traverse(traveller, target_loc) |
||||||
|
|
||||||
|
def attempt_unlock(self, traveller, keycode): |
||||||
|
if keycode == self.db.keycode: |
||||||
|
traveller.msg("The keypad flashes green, the door is unlocked") |
||||||
|
self.db.locked = False |
||||||
|
else: |
||||||
|
traveller.msg(f"The keypad flashes red, {keycode} is not the correct keycode") |
@ -0,0 +1,50 @@ |
|||||||
|
''' |
||||||
|
Mech typeclass |
||||||
|
''' |
||||||
|
|
||||||
|
from typeclasses.objects import Object |
||||||
|
from commands.mech_cmds import CmdSetMech |
||||||
|
|
||||||
|
DESC_INSIDE = ''' |
||||||
|
You see the insides of a large mech. |
||||||
|
|
||||||
|
Glancing at the controls you see you can do the following: |
||||||
|
look through the |xHUD|n |
||||||
|
|xdrive|n to a destination |
||||||
|
|xexit|n the mech |
||||||
|
'''.strip() |
||||||
|
|
||||||
|
class Mech(Object): |
||||||
|
''' |
||||||
|
Mech typeclass |
||||||
|
''' |
||||||
|
def at_object_creation(self): |
||||||
|
'Called once when object is created' |
||||||
|
self.locks.add('get:none()') # Restrict ability to be put in inventory |
||||||
|
self.db.get_err_msg = "You can't possibly lift this" |
||||||
|
|
||||||
|
self.db.desc_inside = DESC_INSIDE |
||||||
|
self.cmdset.add_default(CmdSetMech, permanent=True) |
||||||
|
|
||||||
|
def msg_contents(self, text=None, exclude=[], from_obj=None, mapping=None, **kwargs): |
||||||
|
'Intercept in order to relay messages from pilot to the room' |
||||||
|
super().msg_contents(text=text, exclude=exclude, from_obj=from_obj, mapping=mapping) |
||||||
|
|
||||||
|
# Relay messages from pilot to room |
||||||
|
if exclude: |
||||||
|
# Evennia will pass a tuple to this method...I convert it because I hate tuples |
||||||
|
# TODO: Embrace the tuple and fix my implementation |
||||||
|
exclude = list(exclude).append(self) |
||||||
|
self.location.msg_contents(text=text, exclude=exclude, from_obj=from_obj, mapping=mapping) |
||||||
|
|
||||||
|
def at_msg_receive(self, text=None, exclude=None, from_obj=None, **kwargs): |
||||||
|
'Relay messages to pilots' |
||||||
|
# Prevent loop when pilot uses say |
||||||
|
self.msg_contents(text=text, exclude=exclude, from_obj=from_obj) |
||||||
|
|
||||||
|
def return_appearance(self, looker=None): |
||||||
|
'What a user sees when using look' |
||||||
|
if looker in self.contents: |
||||||
|
# return self.db.desc_inside |
||||||
|
return self.db.desc_inside |
||||||
|
return 'You see a large mech. It looks like it has missiles and stuff.' |
@ -0,0 +1,176 @@ |
|||||||
|
""" |
||||||
|
Object |
||||||
|
|
||||||
|
The Object is the "naked" base class for things in the game world. |
||||||
|
|
||||||
|
Note that the default Character, Room and Exit does not inherit from |
||||||
|
this Object, but from their respective default implementations in the |
||||||
|
evennia library. If you want to use this class as a parent to change |
||||||
|
the other types, you can do so by adding this as a multiple |
||||||
|
inheritance. |
||||||
|
|
||||||
|
""" |
||||||
|
from evennia import DefaultObject |
||||||
|
|
||||||
|
|
||||||
|
class Object(DefaultObject): |
||||||
|
""" |
||||||
|
This is the root typeclass object, implementing an in-game Evennia |
||||||
|
game object, such as having a location, being able to be |
||||||
|
manipulated or looked at, etc. If you create a new typeclass, it |
||||||
|
must always inherit from this object (or any of the other objects |
||||||
|
in this file, since they all actually inherit from BaseObject, as |
||||||
|
seen in src.object.objects). |
||||||
|
|
||||||
|
The BaseObject class implements several hooks tying into the game |
||||||
|
engine. By re-implementing these hooks you can control the |
||||||
|
system. You should never need to re-implement special Python |
||||||
|
methods, such as __init__ and especially never __getattribute__ and |
||||||
|
__setattr__ since these are used heavily by the typeclass system |
||||||
|
of Evennia and messing with them might well break things for you. |
||||||
|
|
||||||
|
|
||||||
|
* 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 of Objects, read-only) - returns all objects inside this |
||||||
|
object (including exits) |
||||||
|
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 |
||||||
|
|
||||||
|
* 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) |
||||||
|
|
||||||
|
search(ostring, global_search=False, attribute_name=None, |
||||||
|
use_nicks=False, location=None, ignore_errors=False, account=False) |
||||||
|
execute_cmd(raw_string) |
||||||
|
msg(text=None, **kwargs) |
||||||
|
msg_contents(message, exclude=None, from_obj=None, **kwargs) |
||||||
|
move_to(destination, quiet=False, emit_to_obj=None, use_destination=True) |
||||||
|
copy(new_key=None) |
||||||
|
delete() |
||||||
|
is_typeclass(typeclass, exact=False) |
||||||
|
swap_typeclass(new_typeclass, clean_attributes=False, no_default=True) |
||||||
|
access(accessing_obj, access_type='read', default=False) |
||||||
|
check_permstring(permstring) |
||||||
|
|
||||||
|
* 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_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_before_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_after_move(source_location) - always called after a move has |
||||||
|
been successfully performed. |
||||||
|
at_object_leave(obj, target_location) - called when an object leaves |
||||||
|
this object in any fashion |
||||||
|
at_object_receive(obj, source_location) - called when this object receives |
||||||
|
another object |
||||||
|
|
||||||
|
at_traverse(traversing_object, source_loc) - (exit-objects only) |
||||||
|
handles all moving across the exit, including |
||||||
|
calling the other exit hooks. Use super() to retain |
||||||
|
the default functionality. |
||||||
|
at_after_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_get(getter) - called after object has been picked up. |
||||||
|
Does not stop pickup. |
||||||
|
at_drop(dropper) - called when this object has been dropped. |
||||||
|
at_say(speaker, message) - by default, called if an object inside this |
||||||
|
object speaks |
||||||
|
|
||||||
|
""" |
||||||
|
|
||||||
|
def return_appearance(self, looker, **kwargs): |
||||||
|
'Modified version of what users normally see when using look' |
||||||
|
if not looker: |
||||||
|
return '' |
||||||
|
|
||||||
|
return self.db.desc |
||||||
|
|
||||||
|
class ImmovableObject(Object): |
||||||
|
'Objects that are not "gettable" and do not show when using look' |
||||||
|
|
||||||
|
def at_object_creation(self): |
||||||
|
self.locks.add('get:none()') |
||||||
|
self.db.get_err_msg = "You can't possibly lift this" |
||||||
|
self.db.immovable = True |
@ -0,0 +1,52 @@ |
|||||||
|
""" |
||||||
|
Room |
||||||
|
|
||||||
|
Rooooooms |
||||||
|
""" |
||||||
|
|
||||||
|
from evennia import DefaultRoom |
||||||
|
from evennia import DefaultObject |
||||||
|
from evennia.locks.lockhandler import check_lockstring |
||||||
|
from evennia.utils.utils import list_to_string |
||||||
|
|
||||||
|
class Room(DefaultRoom): |
||||||
|
def check_perm(self, caller, permission): |
||||||
|
return check_lockstring(caller, f"dummy:perm({permission})") |
||||||
|
|
||||||
|
def return_appearance(self, looker, exclude=(), **kwargs): |
||||||
|
""" |
||||||
|
This is called whenever someone runs the 'look' command. |
||||||
|
|
||||||
|
Args: |
||||||
|
looker (Object): Object doing the looking. |
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users |
||||||
|
overriding the call (unused by default). |
||||||
|
""" |
||||||
|
output = f"{self.db.desc}" |
||||||
|
|
||||||
|
users, other_objs = [], [] |
||||||
|
contents = self.contents |
||||||
|
if exclude: |
||||||
|
contents = list(set(contents) - set(exclude)) |
||||||
|
for obj in contents: |
||||||
|
if obj.id == looker.id: |
||||||
|
continue |
||||||
|
obj_name = obj.get_numbered_name(1, looker)[0] |
||||||
|
if obj.is_connected: |
||||||
|
users.append(obj.get_display_name(looker)) |
||||||
|
if not obj.destination and not obj.has_account and not obj.db.immovable: |
||||||
|
other_objs.append(obj_name) |
||||||
|
if users: |
||||||
|
output += f"\nThese people are here: {list_to_string(users)}" |
||||||
|
if other_objs: |
||||||
|
output += f"\nYou also see: {list_to_string(other_objs)}" |
||||||
|
|
||||||
|
admin_only_objs = [] |
||||||
|
if self.check_perm(looker, 'Admin'): |
||||||
|
for obj in contents: |
||||||
|
if obj.id != looker.id: |
||||||
|
admin_only_objs.append(obj.get_display_name(looker)) |
||||||
|
if admin_only_objs: |
||||||
|
output += f"\n\n|xAdmin only: {list_to_string(admin_only_objs)}" |
||||||
|
|
||||||
|
return output |
@ -0,0 +1,92 @@ |
|||||||
|
""" |
||||||
|
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 import DefaultScript |
||||||
|
|
||||||
|
|
||||||
|
class Script(DefaultScript): |
||||||
|
""" |
||||||
|
A script type is customized by redefining some or all of its hook |
||||||
|
methods and variables. |
||||||
|
|
||||||
|
* available properties |
||||||
|
|
||||||
|
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 |
||||||
|
|
||||||
|
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_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_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. |
||||||
|
|
||||||
|
""" |
||||||
|
|
||||||
|
pass |
@ -0,0 +1,34 @@ |
|||||||
|
''' |
||||||
|
Tests for Mech |
||||||
|
''' |
||||||
|
|
||||||
|
from evennia.commands.default.tests import CommandTest |
||||||
|
from evennia.commands.default import general |
||||||
|
from evennia import create_object |
||||||
|
|
||||||
|
from typeclasses import rooms, mech |
||||||
|
|
||||||
|
class TestMech(CommandTest): |
||||||
|
''' |
||||||
|
Tests for Mech |
||||||
|
''' |
||||||
|
def setUp(self): |
||||||
|
'Create a test mech' |
||||||
|
super().setUp() |
||||||
|
self.mech = create_object(mech.Mech, key='a giant mech', |
||||||
|
location=self.room1, aliases=['mech']) |
||||||
|
|
||||||
|
def test_return_appearance(self): |
||||||
|
'Tests for look behavior' |
||||||
|
self.call(general.CmdLook(), 'mech', "You see a large mech. It looks like it has missiles and stuff.") |
||||||
|
self.char1.move_to(self.mech) |
||||||
|
self.call(general.CmdLook(), 'mech', "You see the insides of a large mech.") |
||||||
|
self.char1.move_to(self.room1) |
||||||
|
|
||||||
|
def test_at_object_creation(self): |
||||||
|
'Tests for at_object_creation method' |
||||||
|
|
||||||
|
# Need to remove permissions to test locks |
||||||
|
self.char1.account.permissions.remove('Developer') |
||||||
|
|
||||||
|
self.call(general.CmdGet(), 'mech', "You can't possibly lift this") |
@ -0,0 +1,42 @@ |
|||||||
|
''' |
||||||
|
Tests for Room |
||||||
|
''' |
||||||
|
|
||||||
|
from evennia.utils.test_resources import EvenniaTest |
||||||
|
from evennia import create_object |
||||||
|
|
||||||
|
from typeclasses import rooms, objects |
||||||
|
import re |
||||||
|
|
||||||
|
class TestRoom(EvenniaTest): |
||||||
|
'Tests for Room' |
||||||
|
def setUp(self): |
||||||
|
''' |
||||||
|
Create Test Room, place objects in it, and move Char 1 to it |
||||||
|
''' |
||||||
|
super().setUp() |
||||||
|
self.test_room = create_object(rooms.Room, key='Test Room') |
||||||
|
|
||||||
|
self.giant_stone = create_object(objects.ImmovableObject, key='a giant stone', |
||||||
|
location=self.test_room, aliases=['stone']) |
||||||
|
self.giant_stone.locks.add('get:none()') |
||||||
|
self.giant_stone.db.get_err_msg = "You can't possibly lift this" |
||||||
|
|
||||||
|
self.bobble = create_object(objects.Object, key='a bobble', |
||||||
|
location=self.test_room, aliases=['bobble']) |
||||||
|
self.bobble.db.desc = "A bobble that you can carry" |
||||||
|
|
||||||
|
self.char1.location = self.test_room |
||||||
|
|
||||||
|
self.char2.name = 'John' |
||||||
|
self.char2.location = self.test_room |
||||||
|
self.char2.account.is_connected = True |
||||||
|
|
||||||
|
def test_return_appearance_when_admin(self): |
||||||
|
'Should return output containing "Admin only:"' |
||||||
|
self.assertIn('Admin only:', self.test_room.return_appearance(self.char1)) |
||||||
|
|
||||||
|
def test_return_appearance_when_not_admin(self): |
||||||
|
'Admin only output is not present when user is not an admin' |
||||||
|
self.char1.account.permissions.remove('Developer') |
||||||
|
self.assertIsNone(re.search(r'Admin only', self.test_room.return_appearance(self.char1))) |
@ -0,0 +1,13 @@ |
|||||||
|
If you want to override one of the static files (such as a CSS or JS file) used by Evennia or a Django app installed in your Evennia project, |
||||||
|
copy it into this directory's corresponding subdirectory, and it will be placed in the static folder when you run: |
||||||
|
|
||||||
|
python manage.py collectstatic |
||||||
|
|
||||||
|
...or when you reload the server via the command line. |
||||||
|
|
||||||
|
Do note you may have to reproduce any preceeding directory structures for the file to end up in the right place. |
||||||
|
|
||||||
|
Also note that you may need to clear out existing static files for your new ones to be gathered in some cases. Deleting files in static/ |
||||||
|
will force them to be recollected. |
||||||
|
|
||||||
|
To see what files can be overridden, find where your evennia package is installed, and look in `evennia/web/static/` |
@ -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/` |
@ -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/` |
@ -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/` |
@ -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/` |
@ -0,0 +1,4 @@ |
|||||||
|
Place your own version of templates into this file to override the default ones. |
||||||
|
For instance, if there's a template at: `evennia/web/website/templates/website/index.html` |
||||||
|
and you want to replace it, create the file `template_overrides/website/index.html` |
||||||
|
and it will be loaded instead. |
@ -0,0 +1,3 @@ |
|||||||
|
Replace Evennia's webclient django templates with your own here. |
||||||
|
|
||||||
|
You can find the original files in `evennia/web/webclient/templates/webclient/` |
@ -0,0 +1,7 @@ |
|||||||
|
You can replace the django templates (html files) for the website |
||||||
|
here. It uses the default "prosimii" theme. If you want to maintain |
||||||
|
multiple themes rather than just change the default one in-place, |
||||||
|
make new folders under `template_overrides/` and change |
||||||
|
`settings.ACTIVE_THEME` to point to the folder name to use. |
||||||
|
|
||||||
|
You can find the original files under `evennia/web/website/templates/website/` |
@ -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/` |
@ -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/` |
@ -0,0 +1,18 @@ |
|||||||
|
""" |
||||||
|
Url definition file to redistribute incoming URL requests to django |
||||||
|
views. Search the Django documentation for "URL dispatcher" for more |
||||||
|
help. |
||||||
|
|
||||||
|
""" |
||||||
|
from django.conf.urls import url, include |
||||||
|
|
||||||
|
# default evennia patterns |
||||||
|
from evennia.web.urls import urlpatterns |
||||||
|
|
||||||
|
# eventual custom patterns |
||||||
|
custom_patterns = [ |
||||||
|
# url(r'/desired/url/', view, name='example'), |
||||||
|
] |
||||||
|
|
||||||
|
# this is required by Django. |
||||||
|
urlpatterns = custom_patterns + urlpatterns |
@ -0,0 +1,8 @@ |
|||||||
|
[MESSAGE CONTROL] |
||||||
|
|
||||||
|
disable=C0103, E0401 |
||||||
|
|
||||||
|
[VARIABLES] |
||||||
|
|
||||||
|
additional-builtins=caller |
||||||
|
|
@ -0,0 +1,10 @@ |
|||||||
|
# world/ |
||||||
|
|
||||||
|
This folder is meant as a miscellanous 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. |
@ -0,0 +1,73 @@ |
|||||||
|
""" |
||||||
|
Prototypes |
||||||
|
|
||||||
|
A prototype is a simple way to create individualized instances of a |
||||||
|
given `Typeclass`. 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. |
||||||
|
|
||||||
|
Example prototypes are read by the `@spawn` command but is also easily |
||||||
|
available to use from code via `evennia.spawn` or `evennia.utils.spawner`. |
||||||
|
Each prototype should be a dictionary. Use the same name as the |
||||||
|
variable to refer to other prototypes. |
||||||
|
|
||||||
|
Possible keywords are: |
||||||
|
prototype_parent - string pointing to parent prototype of this structure. |
||||||
|
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. |
||||||
|
aliases - string or list of strings. |
||||||
|
|
||||||
|
ndb_<name> - value of a nattribute (the "ndb_" part is ignored). |
||||||
|
any other keywords are interpreted as Attributes and their values. |
||||||
|
|
||||||
|
See the `@spawn` command and `evennia.utils.spawner` for more info. |
||||||
|
|
||||||
|
""" |
||||||
|
|
||||||
|
# from random import randint |
||||||
|
# |
||||||
|
# GOBLIN = { |
||||||
|
# "key": "goblin grunt", |
||||||
|
# "health": lambda: randint(20,30), |
||||||
|
# "resists": ["cold", "poison"], |
||||||
|
# "attacks": ["fists"], |
||||||
|
# "weaknesses": ["fire", "light"] |
||||||
|
# } |
||||||
|
# |
||||||
|
# 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") |
||||||
|
# } |
@ -0,0 +1,33 @@ |
|||||||
|
'taoshengshi' |
||||||
|
|
||||||
|
#HEADER |
||||||
|
|
||||||
|
from evennia import create_object, search_object |
||||||
|
from typeclasses import exits, objects, rooms |
||||||
|
from typeclasses.mech import Mech |
||||||
|
|
||||||
|
def move_or_create(typeclass, key, aliases=[], location=None, destination=None): |
||||||
|
objects = search_object(key) |
||||||
|
# TODO: This only errors if typeclass is different...it should error if 2 objects have the same key or aliases |
||||||
|
matching_objects = list(filter(lambda x: type(x) == typeclass, objects)) |
||||||
|
if len(matching_objects) > 1: |
||||||
|
caller.msg(f"ERROR: More than one object found for '{key}'") |
||||||
|
raise |
||||||
|
if not matching_objects: |
||||||
|
caller.msg(f"Creating '{key}'") |
||||||
|
return create_object(typeclass, key=key, aliases=aliases, location=location, destination=destination) |
||||||
|
current_object = matching_objects[0] |
||||||
|
if location != current_object.location: |
||||||
|
caller.msg(f"Moving '{key}' to '{destination}'") |
||||||
|
current_object.move_to(destination) |
||||||
|
return current_object |
||||||
|
|
||||||
|
caller.msg(f'Leaving {key} alone') |
||||||
|
return current_object |
||||||
|
|
||||||
|
# Rooms (defined here to be available to all files) |
||||||
|
limbo = search_object('Limbo')[0] |
||||||
|
cave = move_or_create(rooms.Room, key='cave') |
||||||
|
lobby = move_or_create(rooms.Room, key='lobby') |
||||||
|
|
||||||
|
#INSERT taoshengshi.cave |
@ -0,0 +1,64 @@ |
|||||||
|
#CODE |
||||||
|
'Cave' |
||||||
|
|
||||||
|
entrance_to_cave = move_or_create(exits.Exit, key='taoshengshi', |
||||||
|
aliases=['tao'], |
||||||
|
location=limbo, |
||||||
|
destination=cave) |
||||||
|
|
||||||
|
cave.db.desc = ''' |
||||||
|
You are inside an expansive underground cave. The air is moist and smells of |
||||||
|
earth, minerals, and age. |
||||||
|
|
||||||
|
Ahead is a |xfacility|n embedded into the natural rock wall. You can make out a |
||||||
|
large metal door which appears to be a |xgarage|n of some sort, but is much too |
||||||
|
large for any normal sized vehicle. To the right of the garage is a door that |
||||||
|
serves as the |xentrance|n to the facility. |
||||||
|
|
||||||
|
To your left and right are several rows of parking spots terminating in rock |
||||||
|
walls on either side. |
||||||
|
|
||||||
|
Behind you is an excavated |xtunnel|n that you cannot see the end of. |
||||||
|
''' |
||||||
|
|
||||||
|
|
||||||
|
facility = move_or_create(objects.ImmovableObject, key='facility', |
||||||
|
aliases=['facility', 'e'], |
||||||
|
location=cave) |
||||||
|
|
||||||
|
facility.db.desc = ''' |
||||||
|
Best you can tell is this facility is used for the manufacture of some sort of |
||||||
|
large mechanical objects. Perhaps construction or farming equipment? |
||||||
|
''' |
||||||
|
|
||||||
|
garage = move_or_create(objects.ImmovableObject, key='garage', |
||||||
|
aliases=['garage', 'g'], |
||||||
|
location=cave) |
||||||
|
garage.db.desc = ''' |
||||||
|
The garage door is made of corrugated metal and is large enough to hold even |
||||||
|
the largest machinery. You can't see how it is opened and suspect it must be |
||||||
|
opened from the inside. It is much too large to open by hand. |
||||||
|
''' |
||||||
|
|
||||||
|
tunnel = move_or_create(exits.Exit, key='excavated tunnel', |
||||||
|
aliases=['excavated', 'e', 'tunnel', 't'], |
||||||
|
location=cave, |
||||||
|
destination=limbo) |
||||||
|
|
||||||
|
tunnel.db.desc = ''' |
||||||
|
This tunnel looks like it stretches on forever, no light can be seen at the end. |
||||||
|
'''.strip() |
||||||
|
|
||||||
|
tunnel.db.err_traverse = ''' |
||||||
|
You walk for what seems like miles with no discernible change in scenery. |
||||||
|
|
||||||
|
Eventually you turn back. You'll need some sort of vehicle to leave this place. |
||||||
|
'''.strip() |
||||||
|
tunnel.locks.add("traverse:is_a('Mech')") |
||||||
|
|
||||||
|
facility_door = move_or_create(exits.KeypadDoor, key='facility door', |
||||||
|
aliases=['entrance', 'door'], |
||||||
|
location=cave, |
||||||
|
destination=lobby) |
||||||
|
|
||||||
|
mech = move_or_create(Mech, key='giant mech', location=cave, aliases=['mech']) |
Loading…
Reference in new issue