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