commit iniziale.

This commit is contained in:
Francesco Cappelli 2022-01-10 14:42:13 +01:00
commit 043cdfc230
63 changed files with 5219 additions and 0 deletions

52
.gitignore vendored Normal file
View file

@ -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

40
README.md Normal file
View file

@ -0,0 +1,40 @@
# Welcome to Evennia!
This is your game directory, set up to let you start with
your new game right away. An overview of this directory is found here:
https://github.com/evennia/evennia/wiki/Directory-Overview#the-game-directory
You can delete this readme file when you've read it and you can
re-arrange things in this game-directory to suit your own sense of
organisation (the only exception is the directory structure of the
`server/` directory, which Evennia expects). If you change the structure
you must however also edit/add to your settings file to tell Evennia
where to look for things.
Your game's main configuration file is found in
`server/conf/settings.py` (but you don't need to change it to get
started). If you just created this directory (which means you'll already
have a `virtualenv` running if you followed the default instructions),
`cd` to this directory then initialize a new database using
evennia migrate
To start the server, stand in this directory and run
evennia start
This will start the server, logging output to the console. Make
sure to create a superuser when asked. By default you can now connect
to your new game using a MUD client on `localhost`, port `4000`. You can
also log into the web client by pointing a browser to
`http://localhost:4001`.
# Getting started
From here on you might want to look at one of the beginner tutorials:
http://github.com/evennia/evennia/wiki/Tutorials.
Evennia's documentation is here:
https://github.com/evennia/evennia/wiki.
Enjoy!

6
__init__.py Normal file
View file

@ -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.
"""

14
commands/README.md Normal file
View file

@ -0,0 +1,14 @@
# commands/
This folder holds modules for implementing one's own commands and
command sets. All the modules' classes are essentially empty and just
imports the default implementations from Evennia; so adding anything
to them will start overloading the defaults.
You can change the organisation of this directory as you see fit, just
remember that if you change any of the default command set classes'
locations, you need to add the appropriate paths to
`server/conf/settings.py` so that Evennia knows where to find them.
Also remember that if you create new sub directories you must put
(optionally empty) `__init__.py` files in there so that Python can
find your modules.

0
commands/__init__.py Normal file
View file

285
commands/builder.py Normal file
View file

@ -0,0 +1,285 @@
from evennia import default_cmds, create_object, search_tag
from evennia.utils import inherits_from
from evennia.utils.eveditor import EvEditor
from commands.command import Command
from typeclasses.exits import BaseDoor
from typeclasses.rooms import Room, Zone
def _descdoor_load(caller):
return caller.db.evmenu_target.db.desc or ""
def _descdoor_save(caller, buf):
"""
Save line buffer to the desc prop. This should
return True if successful and also report its status to the user.
"""
caller.db.evmenu_target.setdesc(buf)
caller.msg("Saved.")
return True
def _descdoor_quit(caller):
caller.attributes.remove("evmenu_target")
caller.msg("Exited editor.")
class CmdDescDoor(Command):
"""
describe a BaseDoor in the current room.
Usage:
descdoor <obj> = <description>
Switches:
edit - Open up a line editor for more advanced editing.
Sets the "desc" attribute on an object. If an object is not given,
describe the current room.
"""
key = "descdoor"
aliases = ["descd"]
switch_options = ("edit",)
locks = "cmd:perm(descdoor) or perm(Builder)"
help_category = "Building"
def edit_handler(self):
if self.rhs:
self.msg("|rYou may specify a value, or use the edit switch, " "but not both.|n")
return
if self.args:
obj = self.caller.search(self.args)
else:
obj = self.caller.location or self.msg("|rYou can't describe oblivion.|n")
if not obj:
return
if not (obj.access(self.caller, "control") or obj.access(self.caller, "edit")):
self.caller.msg("You don't have permission to edit the description of %s." % obj.key)
self.caller.db.evmenu_target = obj
# launch the editor
EvEditor(
self.caller,
loadfunc=_descdoor_load,
savefunc=_descdoor_save,
quitfunc=_descdoor_quit,
key="desc",
persistent=True,
)
return
def func(self):
"""Define command"""
caller = self.caller
if not self.args or ("=" in self.args and "edit" in self.switches):
caller.msg("Usage: descdoor <obj> = <description>")
return
if "edit" in self.switches:
self.edit_handler()
return
# We have an =
obj = caller.search(self.lhs)
if not obj:
return
desc = self.rhs or ""
if inherits_from(obj, BaseDoor):
if obj.access(self.caller, "control") or obj.access(self.caller, "edit"):
obj.setdesc(desc)
caller.msg("The description was set on %s." % obj.get_display_name(caller))
else:
caller.msg("You don't have permission to edit the description of %s." % obj.key)
else:
self.msg("|rYou can't describe oblivion.|n")
class CmdOpen(default_cmds.CmdOpen):
__doc__ = default_cmds.CmdOpen.__doc__
# overloading parts of the default CmdOpen command to support doors.
def create_exit(self, exit_name, location, destination, exit_aliases=None, typeclass=None):
"""
Simple wrapper for the default CmdOpen.create_exit
"""
# create a new exit as normal
new_exit = super().create_exit(
exit_name, location, destination, exit_aliases=exit_aliases, typeclass=typeclass
)
if hasattr(self, "return_exit_already_created"):
# we don't create a return exit if it was already created (because
# we created a door)
del self.return_exit_already_created
return new_exit
if inherits_from(new_exit, BaseDoor):
# a door - create its counterpart and make sure to turn off the default
# return-exit creation of CmdOpen
self.caller.msg(
"Note: A door-type exit was created - ignored eventual custom return-exit type."
)
self.return_exit_already_created = True
back_exit = self.create_exit(
exit_name, destination, location, exit_aliases=exit_aliases, typeclass=typeclass
)
new_exit.db.return_exit = back_exit
back_exit.db.return_exit = new_exit
return new_exit
class CmdUpdateLightState(Command):
"""
update room light state.
Usage:
update_light [targetroom]
"""
key = "update_light"
aliases = ["up_l"]
locks = "cmd:perm(update_light) or perm(Builder)"
help_category = "Building"
arg_regex = r"(?:^(?:\s+|\/).*$)|^$"
def func(self):
caller = self.caller
if not self.args:
target = caller.location
if not target:
caller.msg("You have no location to update!")
return
else:
target = caller.search(self.args)
if not (target.attributes.has("is_lit") and hasattr(target, "check_light_state")):
caller.msg(
"You cannot update lights on {}!".format(target.key))
return
target.check_light_state()
caller.msg("Performed update on {}!".format(target.key))
class CmdZone(Command):
"""
creates, deletes or lists zones
Usage:
zone[/list||/del||/addroom] [zonename] [= room]
Creates a new zone.
"""
key = "zone"
locks = "cmd:perm(zone) or perm(Builders)"
help_category = "Building"
def func(self):
"""
Creates the zone.
"""
caller = self.caller
if "list" in self.switches:
string = "";
zones = search_tag(key="zone", category="general")
for zone in zones:
string += "|c{}|n ({})\n".format(zone.name, zone.dbref)
rooms = search_tag(key=zone.name, category="zoneId")
for room in rooms:
string += "- {} ({})\n".format(room.name, room.dbref)
caller.msg("Zones found: \n" + string)
return
if not self.args:
string = "Usage: zone[/list||/del||/addroom] [zonename] [= room]"
caller.msg(string)
return
if "del" in self.switches:
self.delete_zone(self.args)
elif "addroom" in self.switches:
self.add_room_to_zone(self.lhs, self.rhs)
else:
zone = create_object(Zone, key=self.args)
caller.msg("Created zone |w{}|n.".format(zone.name))
def delete_zone(self, zone_string):
caller = self.caller
zone = caller.search(zone_string, global_search=True, exact=True)
if not zone:
return
if not inherits_from(zone, Zone):
caller.msg("{} is not a valid zone.",format(zone.name))
return
key = zone.name
zone.delete()
caller.msg("Zone {} deleted.".format(key))
def add_room_to_zone(self, zone_string, room_string):
caller = self.caller
zone = caller.search(zone_string, global_search=True, exact=True)
if not zone:
return
if not inherits_from(zone, Zone):
caller.msg("{} is not a valid zone.",format(zone.name))
return
room = caller.search(room_string, global_search=True)
if not room:
return
if not inherits_from(room, Room):
caller.msg("{} is not a valid room.",format(room.name))
return
zone.add_room(room)
caller.msg("{} added to zone {}.".format(room.name, zone.name))
class CmdAddToZone(Command):
"""
add a room to a zone
Usage:
@addtozone obj = zone
Adds a room to an existing zone.
"""
key = "@addtozone"
locks = "cmd:perm(zone) or perm(Builders)"
help_category = "Building"
def func(self):
"""
Adds a room to an existing zone.
"""
caller = self.caller
if self.rhs:
# We have an =
zone = caller.search(self.rhs, global_search=True, exact=True)
if not zone:
self.msg("Zone %s doesn't exist." % self.rhs)
return
if not utils.inherits_from(zone, Zone):
self.msg("{r%s is not a valid zone.{n" % zone.name)
return
room = caller.search(self.lhs)
if not room:
self.msg("{rRoom %s doesn't exist.{n" % self.lhs)
return
if not utils.inherits_from(room, BaseRoom):
self.msg("{r%s is not a valid room.{n" % room.name)
return
room.add_to_zone(zone.name)
self.msg("Room %s (%s) added to zone %s (%s)." % (room.name, room.dbref, zone.name, zone.dbref))
else:
self.msg("{rUsage: @addtozone obj = zone{n")
return

776
commands/command.py Normal file
View file

@ -0,0 +1,776 @@
"""
Commands
Commands describe the input the account can do to the game.
"""
import re
import random
from evennia import utils, default_cmds, create_script, logger
from evennia.utils import evtable
from evennia.contrib.health_bar import display_meter
from evennia.utils import inherits_from
from evennia.utils.utils import list_to_string
from evennia.utils.eveditor import EvEditor
from utils.building import create_room, create_exit
from utils.utils import has_tag, fmt_light, fmt_dark, toggle_effect, has_effect, indefinite_article
from typeclasses.exits import BaseDoor
from typeclasses.rooms import IndoorRoom
from typeclasses.objects import Item, EquippableItem
from typeclasses.scripts import Script, CmdActionScript
from world import spells
CMD_SEARCH_TIME = 5
class Command(default_cmds.MuxCommand):
"""
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.
"""
def at_post_cmd(self):
caller = self.caller
prompt = "|_|/°|w%s|n°: " % (caller.location)
caller.msg(prompt=prompt)
# overloading the look command with our custom MuxCommand with at_post_cmd hook
class CmdLook(default_cmds.CmdLook, Command):
pass
class CmdDrop(default_cmds.CmdDrop, Command):
pass
class CmdGet(Command):
"""
pick up something
Usage:
get <obj> [from <container>]
Picks up an object from your location and puts it in
your inventory.
"""
key = "get"
aliases = "grab"
locks = "cmd:all()"
arg_regex = r"\s|$"
def parse(self):
"""
Handle parsing of:
<object> [FROM <container>]
"""
self.args = args = self.args.strip().lower()
if "from" in args:
obj, *rest = args.split(" from ", 1)
container = rest[0] if rest else ""
else:
obj = self.args
container = ""
self.obj_name = obj.strip()
self.container_name = container.strip()
def func(self):
"""implements the command."""
caller = self.caller
if not self.args or not self.obj_name:
caller.msg("Get what?")
return
if inherits_from(caller.location, IndoorRoom) and not caller.location.db.is_lit:
caller.msg("Its too dark to get anything.")
return
if self.container_name:
container = caller.search(self.container_name, location=caller.location)
obj = caller.search(self.obj_name, location=container)
else:
obj = caller.search(self.obj_name, location=caller.location)
if not obj:
return
if caller == obj:
caller.msg("You can't get yourself.")
return
if not obj.access(caller, "get"):
if obj.db.get_err_msg:
caller.msg(obj.db.get_err_msg)
else:
caller.msg("You can't get that.")
return
# calling at_before_get hook method
if not obj.at_before_get(caller):
return
success = obj.move_to(caller, quiet=True)
if not success:
caller.msg("This can't be picked up.")
else:
caller.msg("You pick up %s." % obj.name)
caller.location.msg_contents(
"%s picks up %s." % (caller.name, obj.name), exclude=caller
)
# calling at_get hook method
obj.at_get(caller)
class CmdCharacter(Command):
"""
Show character sheet.
Usage:
character
Displays a list of your current ability values.
"""
key = "character"
aliases = ["char"]
lock = "cmd:all()"
help_category = "General"
def func(self):
self.caller.msg(
'\u250B|w' + " {0:27}".format(self.caller.name,) + '|n\u250B|/')
self.caller.msg('-HEALTH' + '-' * 23)
meter = display_meter(self.caller.get_health(), 100,
empty_color="222", align="center", fill_color=["R"])
self.caller.msg(meter)
self.caller.msg('-MANA' + '-' * 25)
meter = display_meter(self.caller.get_mana(), 100,
empty_color="222", align="center", fill_color=["B"])
self.caller.msg(meter + '|/')
str, agi, intel = self.caller.get_abilities()
table = evtable.EvTable(border="tablecols")
table.add_column("STRENGTH", "AGILITY",
"INTELLECT", header="Attributes")
table.add_column("|g%03d|n/100" % str, "|g%03d|n/100" %
agi, "|g%03d|n/100" % intel, header="")
self.caller.msg(table)
class CmdLight(Command):
"""
A command to light objects that support it.
Usage:
light <someone>
Light an object in your inventory.
"""
key = "light"
aliases = []
lock = "cmd:all()"
help_category = "General"
arg_regex = r"(?:^(?:\s+|\/).*$)|^$"
def func(self):
caller = self.caller
if not self.args:
self.msg("Light what?")
return
else:
target = caller.search(self.args)
if not target:
self.msg("Light what?")
return
if not target.access(caller, 'light'):
self.msg("You cannot do that.")
return
if not has_effect(target, "emit_light"):
toggle_effect(target, "emit_light")
self.msg("You light {}.".format(target.name))
else:
toggle_effect(target, "emit_light")
self.msg("You put off {}.".format(target.name))
if caller.location and inherits_from(caller.location, IndoorRoom):
caller.location.check_light_state()
class CmdSearch(Command):
"""
"""
key = "search"
aliases = ["ss"]
locks = "cmd:all()"
help_category = "General"
arg_regex = r"(?:^(?:\s+|\/).*$)|^$"
def func(self):
caller = self.caller
if has_effect(caller, "is_busy"):
caller.msg("You are already busy {}.".format(caller.current_action.busy_msg()))
return
if not self.args:
target = caller.location
if not target:
caller.msg("You have no location to search!")
return
else:
target = caller.search(self.args)
if not target:
caller.msg("You cannot search {}!".format(self.args))
return
if target.access(caller, "search"):
toggle_effect(caller, "is_busy")
caller.msg("You search {}.".format(target.get_display_name(caller)))
action_script = create_script("commands.command.CmdSearchComplete", obj=caller, interval=CMD_SEARCH_TIME, attributes=[("target", self.args)])
caller.db.current_action = action_script
else:
caller.msg("You cannot search {}!".format(target.get_display_name(caller)))
class CmdSearchComplete(CmdActionScript):
def at_script_creation(self):
super().at_script_creation()
self.key = "cmd_search_complete"
self.desc = ""
self.db.target = ""
def at_repeat(self):
caller = self.obj
target_string = self.db.target
if has_effect(caller, "is_busy"):
toggle_effect(caller, "is_busy")
if not target_string:
target = caller.location
if not target:
caller.msg("You have no location to search!")
return
else:
target = caller.search(target_string)
if not target:
caller.msg("You cannot search {} anymore!".format(target_string))
return
if target.access(caller, "search"):
items = []
for con in target.contents:
if inherits_from(con, Item) and con.access(caller, "get"):
items.append(con)
if items:
found_item_idx = random.randrange(0, len(items))
found_item = items[found_item_idx]
found_item_name = found_item.get_numbered_name(1, caller)[0]
caller.msg("You find {}.".format(found_item_name))
if not found_item.access(caller, "get"):
if found_item.db.get_err_msg:
caller.msg(found_item.db.get_err_msg)
else:
caller.msg("You can't get that.")
return
# calling at_before_get hook method
if not found_item.at_before_get(caller):
return
success = found_item.move_to(caller, quiet=True)
if not success:
caller.msg("This can't be picked up.")
else:
caller.msg("You pick up %s." % found_item_name)
caller.location.msg_contents(
"%s picks up %s." % (caller.name, found_item_name), exclude=caller
)
# calling at_get hook method
found_item.at_get(caller)
else:
caller.msg("There is nothing to be found here.")
else:
caller.msg("You cannot search {} anymore!".format(target.get_display_name(caller)))
def busy_msg(self):
return "searching {}".format(self.db.target)
class CmdPut(Command):
"""
Put an item inside a container
Usage:
put <item> in <container>
"""
key = "put"
locks = "cmd:all()"
help_category = "General"
def parse(self):
"""
Handle parsing of:
<item> in <container>
"""
self.args = args = self.args.strip().lower()
item_name, container_name = "", ""
if " in " in args:
item_name, *rest = args.split(" in ", 1)
container_name = rest[0] if rest else ""
self.item_name = item_name.strip()
self.container_name = container_name.strip()
def func(self):
caller = self.caller
if not self.args or not self.item_name or not self.container_name:
caller.msg("Usage: put <item> in <container>")
return
item = caller.search(self.item_name, typeclass="typeclasses.objects.Item")
if not item:
return
container = caller.search(self.container_name, typeclass="typeclasses.objects.ContainerFeature")
if not container:
return
if not item.access(caller, "get"):
caller.msg("You cannot do that with {}.".format(item.name))
return
if not container.access(caller, "put"):
caller.msg("You cannot access {}.".format(container.name))
return
if has_tag(item, "equipped", "general"):
caller.msg("{} is equipped. Remove it first.".format(item.name))
return
item.move_to(container, use_destination=False)
caller.msg("You put {} inside {}.".format(item.name, container.name))
class CmdOpenCloseDoor(Command):
"""
Open and close a door
Usage:
open <door>
close <door>
"""
key = "op"
aliases = ["close"]
locks = "cmd:all()"
help_category = "General"
def func(self):
if not self.args:
self.caller.msg("Usage: open||close <door>")
return
door = self.caller.search(self.args)
if not door:
return
if not inherits_from(door, BaseDoor):
self.caller.msg("This is not a door.")
return
if self.cmdstring == "op":
if door.locks.check(self.caller, "traverse"):
self.caller.msg("%s is already open." % door.key)
else:
door.setlock("traverse:true()")
self.caller.msg("You open %s." % door.key)
else: # close
if not door.locks.check(self.caller, "traverse"):
self.caller.msg("%s is already closed." % door.key)
else:
door.setlock("traverse:false()")
self.caller.msg("You close %s." % door.key)
class CmdEquip(Command):
key = "equip"
aliases = ["eq", "remove"]
lock = "cmd:all()"
help_category = "General"
arg_regex = r"\s.+|$"
def func(self):
caller = self.caller
if not self.args:
caller.msg("You cannot do that.")
return
item = caller.search(self.args, location=caller, nofound_string="")
if not item:
caller.msg("You don't have any {}".format(self.args))
return
if inherits_from(item, EquippableItem) and item.access(caller, "equip"):
if self.cmdstring == "remove":
if has_tag(item, "equipped", "general"):
if item.at_unequip(caller, ""):
self.remove(caller, item)
else:
return
else:
caller.msg("{} is not equipped.".format(item.name))
return
else:
if has_tag(item, "equipped", "general"):
caller.msg("{} is already equipped.".format(item.name))
return
slots = [slot for slot in caller.db.equipment.keys() if item.db.slot in slot]
if not slots:
caller.msg("You don't have {} {}".format(indefinite_article(item.db.slot), item.db.slot))
return
selected_slot = slots[0]
# find an empty slot
empty_slots = [slot for slot in slots if not caller.db.equipment[slot]]
if empty_slots:
selected_slot = empty_slots[0]
# remove equipment if slot is already in use
if caller.db.equipment[selected_slot]:
old_equipment = caller.search(caller.db.equipment[selected_slot], location=caller, quiet=True)
if old_equipment:
if old_equipment[0].at_unequip(caller, selected_slot):
self.remove(caller, old_equipment[0])
else:
return
if item.at_equip(caller, selected_slot):
caller.db.equipment[selected_slot] = item.dbref
item.tags.add("equipped", category="general")
caller.msg("You equip |w{}|n on |w{}|n".format(item.name, selected_slot))
caller.location.msg_contents("{} equips |w{}|n on {}".format(caller.name, item.name, selected_slot), exclude=caller)
else:
caller.msg("You cannot equip {}".format(item.name))
def remove(self, caller, item):
selected_slot = [slot for slot in caller.db.equipment.keys() if caller.db.equipment[slot] == item.dbref][0]
caller.msg("You remove {} from {}".format(item, selected_slot))
caller.db.equipment[selected_slot] = None
item.tags.remove("equipped", category="general")
class CmdInventory(Command):
"""
view inventory
Usage:
inventory
inv
Shows your inventory.
"""
key = "inventory"
aliases = ["inv", "i"]
locks = "cmd:all()"
arg_regex = r"$"
def func(self):
"""check inventory"""
items = self.caller.contents
if not items:
string = "You are not carrying anything."
else:
from evennia.utils.ansi import raw as raw_ansi
table = self.styled_table()
for item in items:
effect_string = ""
if has_tag(item, 'equipped', 'general'):
effect_string += "|w⚒|n"
else:
effect_string += "|=a⚒|n"
if has_effect(item, 'emit_light'):
effect_string += "|y☀|n"
else:
effect_string += "|=a☀|n"
table.add_row(
f"|w{item.name}|n",
effect_string,
"{}|n".format(utils.crop(raw_ansi(item.db.desc), width=50) or ""),
)
string = f"|wYou are carrying:\n{table}"
self.caller.msg(string)
class CmdCast(Command):
"""
"""
key = "cast"
aliases = ["cs"]
lock = "cmd:false()"
help_category = "General"
arg_regex = r"\s.+|$"
def parse(self):
"""
Handle parsing of:
::
<spell> [at <target>]
"""
self.args = args = self.args.strip().lower()
spell_name, target_name = "", ""
if " at " in args:
spell_name, *rest = args.split(" at ", 1)
target_name = rest[0] if rest else ""
else:
spell_name = args
self.spell_name = spell_name.strip()
self.target_name = target_name.strip()
def func(self):
caller = self.caller
if not self.args or not self.spell_name:
caller.msg("Usage: cast <spell> [at <target>]")
return
spell_id = self.spell_name.replace(' ', '_')
if self.spell_name not in caller.db.spells:
caller.msg("You cannot cast {}.".format(self.spell_name))
return
if hasattr(spells, "spell_" + spell_id):
spell = getattr(spells, "spell_" + spell_id)
else:
logger.log_err("Cannot find spell {}.".format("spell_" + spell_id))
return
spell(caller, self.target_name)
class CmdTestPy(Command):
key = "testpy"
aliases = ["testpy"]
lock = "cmd:false()"
help_category = "General"
arg_regex = r"\s.+|$"
def func(self):
caller = self.caller
pattern = re.compile(r'''((?:[^ "']|"[^"]*"|'[^']*')+)''')
self.args = pattern.split(self.lhs)[1::2]
# room = create_room(self.args[0].strip(" \"'"), int(self.args[1].strip(" \"'")), int(self.args[2].strip(" \"'")), self.args[3].strip(" \"'"))
# caller.msg(room)
exit = create_exit("exit_empty", caller.location, "north")
caller.msg(exit)
# caller.msg(dkmud_oob=({"testarg": "valuetestarg"}))
# if not self.args:
# target = caller.location
# if not target:
# caller.msg("You have no location to test!")
# return
# else:
# target = caller.search(self.args)
# if not target:
# return
# -------------------------------------------------------------
#
# 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

133
commands/default_cmdsets.py Normal file
View file

@ -0,0 +1,133 @@
"""
Command sets
All commands in the game must be grouped in a cmdset. A given command
can be part of any number of cmdsets and cmdsets can be added/removed
and merged onto entities at runtime.
To create new commands to populate the cmdset, see
`commands/command.py`.
This module wraps the default command sets of Evennia; overloads them
to add/remove commands from the default lineup. You can create your
own cmdsets by inheriting from them or directly from `evennia.CmdSet`.
"""
from evennia import default_cmds
from commands.command import CmdCharacter
from commands.command import CmdLook
from commands.command import CmdGet
from commands.command import CmdDrop
from commands.command import CmdLight
from commands.command import CmdTestPy
from commands.command import CmdSearch
from commands.command import CmdEquip
from commands.command import CmdInventory
from commands.command import CmdCast
from commands.command import CmdPut
from commands.command import CmdOpenCloseDoor
from commands.builder import CmdUpdateLightState
from commands.builder import CmdOpen
from commands.builder import CmdDescDoor
from commands.builder import CmdZone
from utils.crafting import CmdCraft
class CharacterCmdSet(default_cmds.CharacterCmdSet):
"""
The `CharacterCmdSet` contains general in-game commands like `look`,
`get`, etc available on in-game Character objects. It is merged with
the `AccountCmdSet` when an Account puppets a Character.
"""
key = "DefaultCharacter"
def at_cmdset_creation(self):
"""
Populates the cmdset
"""
super().at_cmdset_creation()
#
# any commands you add below will overload the default ones.
#
self.add(CmdCharacter())
self.add(CmdLook())
self.add(CmdGet())
self.add(CmdDrop())
self.add(CmdTestPy())
self.add(CmdLight())
self.add(CmdSearch())
self.add(CmdEquip())
self.add(CmdInventory())
self.add(CmdCast())
self.add(CmdPut())
self.add(CmdOpenCloseDoor())
self.add(CmdUpdateLightState())
self.add(CmdOpen())
self.add(CmdDescDoor())
self.add(CmdZone())
self.add(CmdCraft())
class AccountCmdSet(default_cmds.AccountCmdSet):
"""
This is the cmdset available to the Account at all times. It is
combined with the `CharacterCmdSet` when the Account puppets a
Character. It holds game-account-specific commands, channel
commands, etc.
"""
key = "DefaultAccount"
def at_cmdset_creation(self):
"""
Populates the cmdset
"""
super().at_cmdset_creation()
#
# any commands you add below will overload the default ones.
#
class UnloggedinCmdSet(default_cmds.UnloggedinCmdSet):
"""
Command set available to the Session before being logged in. This
holds commands like creating a new account, logging in, etc.
"""
key = "DefaultUnloggedin"
def at_cmdset_creation(self):
"""
Populates the cmdset
"""
super().at_cmdset_creation()
#
# any commands you add below will overload the default ones.
#
class SessionCmdSet(default_cmds.SessionCmdSet):
"""
This cmdset is made available on Session level once logged in. It
is empty by default.
"""
key = "DefaultSession"
def at_cmdset_creation(self):
"""
This is the only method defined in a cmdset, called during
its creation. It should populate the set with command instances.
As and example we just add the empty base `Command` object.
It prints some info.
"""
super().at_cmdset_creation()
#
# any commands you add below will overload the default ones.
#

38
server/README.md Normal file
View file

@ -0,0 +1,38 @@
# server/
This directory holds files used by and configuring the Evennia server
itself.
Out of all the subdirectories in the game directory, Evennia does
expect this directory to exist, so you should normally not delete,
rename or change its folder structure.
When running you will find four new files appear in this directory:
- `server.pid` and `portal.pid`: These hold the process IDs of the
Portal and Server, so that they can be managed by the launcher. If
Evennia is shut down uncleanly (e.g. by a crash or via a kill
signal), these files might erroneously remain behind. If so Evennia
will tell you they are "stale" and they can be deleted manually.
- `server.restart` and `portal.restart`: These hold flags to tell the
server processes if it should die or start again. You never need to
modify those files.
- `evennia.db3`: This will only appear if you are using the default
SQLite3 database; it a binary file that holds the entire game
database; deleting this file will effectively reset the game for
you and you can start fresh with `evennia migrate` (useful during
development).
## server/conf/
This subdirectory holds the configuration modules for the server. With
them you can change how Evennia operates and also plug in your own
functionality to replace the default. You usually need to restart the
server to apply changes done here. The most important file is the file
`settings.py` which is the main configuration file of Evennia.
## server/logs/
This subdirectory holds various log files created by the running
Evennia server. It is also the default location for storing any custom
log files you might want to output using Evennia's logging mechanisms.

1
server/__init__.py Normal file
View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

1
server/conf/__init__.py Normal file
View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View file

@ -0,0 +1,19 @@
"""
At_initial_setup module template
Custom at_initial_setup method. This allows you to hook special
modifications to the initial server startup process. Note that this
will only be run once - when the server starts up for the very first
time! It is called last in the startup process and can thus be used to
overload things that happened before it.
The module must contain a global function at_initial_setup(). This
will be called without arguments. Note that tracebacks in this module
will be QUIETLY ignored, so make sure to check it well to make sure it
does what you expect it to.
"""
def at_initial_setup():
pass

54
server/conf/at_search.py Normal file
View file

@ -0,0 +1,54 @@
"""
Search and multimatch handling
This module allows for overloading two functions used by Evennia's
search functionality:
at_search_result:
This is called whenever a result is returned from an object
search (a common operation in commands). It should (together
with at_multimatch_input below) define some way to present and
differentiate between multiple matches (by default these are
presented as 1-ball, 2-ball etc)
at_multimatch_input:
This is called with a search term and should be able to
identify if the user wants to separate a multimatch-result
(such as that from a previous search). By default, this
function understands input on the form 1-ball, 2-ball etc as
indicating that the 1st or 2nd match for "ball" should be
used.
This module is not called by default, to use it, add the following
line to your settings file:
SEARCH_AT_RESULT = "server.conf.at_search.at_search_result"
"""
def at_search_result(matches, caller, query="", quiet=False, **kwargs):
"""
This is a generic hook for handling all processing of a search
result, including error reporting.
Args:
matches (list): This is a list of 0, 1 or more typeclass instances,
the matched result of the search. If 0, a nomatch error should
be echoed, and if >1, multimatch errors should be given. Only
if a single match should the result pass through.
caller (Object): The object performing the search and/or which should
receive error messages.
query (str, optional): The search query used to produce `matches`.
quiet (bool, optional): If `True`, no messages will be echoed to caller
on errors.
Keyword Args:
nofound_string (str): Replacement string to echo on a notfound error.
multimatch_string (str): Replacement string to echo on a multimatch error.
Returns:
processed_result (Object or None): This is always a single result
or `None`. If `None`, any error reporting/handling should
already have happened.
"""

View file

@ -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

55
server/conf/cmdparser.py Normal file
View file

@ -0,0 +1,55 @@
"""
Changing the default command parser
The cmdparser is responsible for parsing the raw text inserted by the
user, identifying which command/commands match and return one or more
matching command objects. It is called by Evennia's cmdhandler and
must accept input and return results on the same form. The default
handler is very generic so you usually don't need to overload this
unless you have very exotic parsing needs; advanced parsing is best
done at the Command.parse level.
The default cmdparser understands the following command combinations
(where [] marks optional parts.)
[cmdname[ cmdname2 cmdname3 ...] [the rest]
A command may consist of any number of space-separated words of any
length, and contain any character. It may also be empty.
The parser makes use of the cmdset to find command candidates. The
parser return a list of matches. Each match is a tuple with its first
three elements being the parsed cmdname (lower case), the remaining
arguments, and the matched cmdobject from the cmdset.
This module is not accessed by default. To tell Evennia to use it
instead of the default command parser, add the following line to
your settings file:
COMMAND_PARSER = "server.conf.cmdparser.cmdparser"
"""
def cmdparser(raw_string, cmdset, caller, match_index=None):
"""
This function is called by the cmdhandler once it has
gathered and merged all valid cmdsets valid for this particular parsing.
raw_string - the unparsed text entered by the caller.
cmdset - the merged, currently valid cmdset
caller - the caller triggering this parsing
match_index - an optional integer index to pick a given match in a
list of same-named command matches.
Returns:
list of tuples: [(cmdname, args, cmdobj, cmdlen, mratio), ...]
where cmdname is the matching command name and args is
everything not included in the cmdname. Cmdobj is the actual
command instance taken from the cmdset, cmdlen is the length
of the command name and the mratio is some quality value to
(possibly) separate multiple matches.
"""
# Your implementation here

View file

@ -0,0 +1,38 @@
# -*- 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
Enter |whelp|n for more info. |wlook|n will re-show this screen.
|b==============================================================|n""".format(
settings.SERVERNAME, utils.get_evennia_version("short")
)

View file

@ -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()

52
server/conf/inputfuncs.py Normal file
View file

@ -0,0 +1,52 @@
"""
Input functions
Input functions are always called from the client (they handle server
input, hence the name).
This module is loaded by being included in the
`settings.INPUT_FUNC_MODULES` tuple.
All *global functions* included in this module are considered
input-handler functions and can be called by the client to handle
input.
An input function must have the following call signature:
cmdname(session, *args, **kwargs)
Where session will be the active session and *args, **kwargs are extra
incoming arguments and keyword properties.
A special command is the "default" command, which is will be called
when no other cmdname matches. It also receives the non-found cmdname
as argument.
default(session, cmdname, *args, **kwargs)
"""
# def oob_echo(session, *args, **kwargs):
# """
# Example echo function. Echoes args, kwargs sent to it.
#
# Args:
# session (Session): The Session to receive the echo.
# args (list of str): Echo text.
# kwargs (dict of str, optional): Keyed echo text
#
# """
# session.msg(oob=("echo", args, kwargs))
#
#
# def default(session, cmdname, *args, **kwargs):
# """
# Handles commands without a matching inputhandler func.
#
# Args:
# session (Session): The active Session.
# cmdname (str): The (unmatched) command name
# args, kwargs (any): Arguments to function.
#
# """
# pass

30
server/conf/lockfuncs.py Normal file
View file

@ -0,0 +1,30 @@
"""
Lockfuncs
Lock functions are functions available when defining lock strings,
which in turn limits access to various game systems.
All functions defined globally in this module are assumed to be
available for use in lockstrings to determine access. See the
Evennia documentation for more info on locks.
A lock function is always called with two arguments, accessing_obj and
accessed_obj, followed by any number of arguments. All possible
arguments should be handled with *args, **kwargs. The lock function
should handle all eventual tracebacks by logging the error and
returning False.
Lock functions in this module extend (and will overload same-named)
lock functions from evennia.locks.lockfuncs.
"""
# def myfalse(accessing_obj, accessed_obj, *args, **kwargs):
# """
# called in lockstring with myfalse().
# A simple logger that always returns false. Prints to stdout
# for simplicity, should use utils.logger for real operation.
# """
# print "%s tried to access %s. Access denied." % (accessing_obj, accessed_obj)
# return False

105
server/conf/mssp.py Normal file
View file

@ -0,0 +1,105 @@
"""
MSSP (Mud Server Status Protocol) meta information
Modify this file to specify what MUD listing sites will report about your game.
All fields are static. The number of currently active players and your game's
current uptime will be added automatically by Evennia.
You don't have to fill in everything (and most fields are not shown/used by all
crawlers anyway); leave the default if so needed. You need to reload the server
before the updated information is made available to crawlers (reloading does
not affect uptime).
After changing the values in this file, you must register your game with the
MUD website list you want to track you. The listing crawler will then regularly
connect to your server to get the latest info. No further configuration is
needed on the Evennia side.
"""
MSSPTable = {
# Required fields
"NAME": "Mygame", # usually the same as SERVERNAME
# Generic
"CRAWL DELAY": "-1", # limit how often crawler may update the listing. -1 for no limit
"HOSTNAME": "", # telnet hostname
"PORT": ["4000"], # telnet port - most important port should be *last* in list!
"CODEBASE": "Evennia",
"CONTACT": "", # email for contacting the mud
"CREATED": "", # year MUD was created
"ICON": "", # url to icon 32x32 or larger; <32kb.
"IP": "", # current or new IP address
"LANGUAGE": "", # name of language used, e.g. English
"LOCATION": "", # full English name of server country
"MINIMUM AGE": "0", # set to 0 if not applicable
"WEBSITE": "", # http:// address to your game website
# Categorisation
"FAMILY": "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", # Allowed: Alpha, Closed Beta, Open Beta, Live
"GAMESYSTEM": "Custom", # D&D, d20 System, World of Darkness, etc. Use Custom if homebrew
# Subgenre: LASG, Medieval Fantasy, World War II, Frankenstein,
# Cyberpunk, Dragonlance, etc. Or None if not applicable.
"SUBGENRE": "None",
# World
"AREAS": "0",
"HELPFILES": "0",
"MOBILES": "0",
"OBJECTS": "0",
"ROOMS": "0", # use 0 if room-less
"CLASSES": "0", # use 0 if class-less
"LEVELS": "0", # use 0 if level-less
"RACES": "0", # use 0 if race-less
"SKILLS": "0", # use 0 if skill-less
# Protocols set to 1 or 0; should usually not be changed)
"ANSI": "1",
"GMCP": "1",
"MSDP": "1",
"MXP": "1",
"SSL": "1",
"UTF-8": "1",
"MCCP": "1",
"XTERM 256 COLORS": "1",
"XTERM TRUE COLORS": "0",
"ATCP": "0",
"MCP": "0",
"MSP": "0",
"VT100": "0",
"PUEBLO": "0",
"ZMP": "0",
# Commercial set to 1 or 0)
"PAY TO PLAY": "0",
"PAY FOR PERKS": "0",
# Hiring set to 1 or 0)
"HIRING BUILDERS": "0",
"HIRING CODERS": "0",
# Extended variables
# World
"DBSIZE": "0",
"EXITS": "0",
"EXTRA DESCRIPTIONS": "0",
"MUDPROGS": "0",
"MUDTRIGS": "0",
"RESETS": "0",
# Game (set to 1 or 0, or one of the given alternatives)
"ADULT MATERIAL": "0",
"MULTICLASSING": "0",
"NEWBIE FRIENDLY": "0",
"PLAYER CITIES": "0",
"PLAYER CLANS": "0",
"PLAYER CRAFTING": "0",
"PLAYER GUILDS": "0",
"EQUIPMENT SYSTEM": "None", # "None", "Level", "Skill", "Both"
"MULTIPLAYING": "None", # "None", "Restricted", "Full"
"PLAYERKILLING": "None", # "None", "Restricted", "Full"
"QUEST SYSTEM": "None", # "None", "Immortal Run", "Automated", "Integrated"
"ROLEPLAYING": "None", # "None", "Accepted", "Encouraged", "Enforced"
"TRAINING SYSTEM": "None", # "None", "Level", "Skill", "Both"
# World originality: "All Stock", "Mostly Stock", "Mostly Original", "All Original"
"WORLD ORIGINALITY": "All Original",
}

View file

@ -0,0 +1,24 @@
"""
Start plugin services
This plugin module can define user-created services for the Portal to
start.
This module must handle all imports and setups required to start
twisted services (see examples in evennia.server.portal.portal). It
must also contain a function start_plugin_services(application).
Evennia will call this function with the main Portal application (so
your services can be added to it). The function should not return
anything. Plugin services are started last in the Portal startup
process.
"""
def start_plugin_services(portal):
"""
This hook is called by Evennia, last in the Portal startup process.
portal - a reference to the main portal application.
"""
pass

View file

@ -0,0 +1,24 @@
"""
Server plugin services
This plugin module can define user-created services for the Server to
start.
This module must handle all imports and setups required to start a
twisted service (see examples in evennia.server.server). It must also
contain a function start_plugin_services(application). Evennia will
call this function with the main Server application (so your services
can be added to it). The function should not return anything. Plugin
services are started last in the Server startup process.
"""
def start_plugin_services(server):
"""
This hook is called by Evennia, last in the Server startup process.
server - a reference to the main server application.
"""
pass

View file

@ -0,0 +1,37 @@
"""
ServerSession
The serversession is the Server-side in-memory representation of a
user connecting to the game. Evennia manages one Session per
connection to the game. So a user logged into the game with multiple
clients (if Evennia is configured to allow that) will have multiple
sessions tied to one Account object. All communication between Evennia
and the real-world user goes through the Session(s) associated with that user.
It should be noted that modifying the Session object is not usually
necessary except for the most custom and exotic designs - and even
then it might be enough to just add custom session-level commands to
the SessionCmdSet instead.
This module is not normally called. To tell Evennia to use the class
in this module instead of the default one, add the following to your
settings file:
SERVER_SESSION_CLASS = "server.conf.serversession.ServerSession"
"""
from evennia.server.serversession import ServerSession as BaseServerSession
class ServerSession(BaseServerSession):
"""
This class represents a player's session and is a template for
individual protocols to communicate with Evennia.
Each account gets one or more sessions assigned to them whenever they connect
to the game server. All communication between game and account goes
through their session(s).
"""
pass

50
server/conf/settings.py Normal file
View file

@ -0,0 +1,50 @@
r"""
Evennia settings file.
The available options are found in the default settings file found
here:
/home/cek/workspace/DKmud/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 = "dkmud"
GLOBAL_SCRIPTS = {
"__ai_manager__": {'typeclass': 'typeclasses.scripts.AiManagerScript',
'repeats': 0, 'interval': 1}
}
CRAFT_RECIPE_MODULES = ['world.recipes_base']
######################################################################
# 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.")

View file

@ -0,0 +1,41 @@
"""
Web plugin hooks.
"""
def at_webserver_root_creation(web_root):
"""
This is called as the web server has finished building its default
path tree. At this point, the media/ and static/ URIs have already
been added to the web root.
Args:
web_root (twisted.web.resource.Resource): The root
resource of the URI tree. Use .putChild() to
add new subdomains to the tree.
Returns:
web_root (twisted.web.resource.Resource): The potentially
modified root structure.
Example:
from twisted.web import static
my_page = static.File("web/mypage/")
my_page.indexNames = ["index.html"]
web_root.putChild("mypage", my_page)
"""
return web_root
def at_webproxy_root_creation(web_root):
"""
This function can modify the portal proxy service.
Args:
web_root (evennia.server.webserver.Website): The Evennia
Website application. Use .putChild() to add new
subdomains that are Portal-accessible over TCP;
primarily for new protocol development, but suitable
for other shenanigans.
"""
return web_root

15
server/logs/README.md Normal file
View file

@ -0,0 +1,15 @@
This directory contains Evennia's log files. The existence of this README.md file is also necessary
to correctly include the log directory in git (since log files are ignored by git and you can't
commit an empty directory).
- `server.log` - log file from the game Server.
- `portal.log` - log file from Portal proxy (internet facing)
Usually these logs are viewed together with `evennia -l`. They are also rotated every week so as not
to be too big. Older log names will have a name appended by `_month_date`.
- `lockwarnings.log` - warnings from the lock system.
- `http_requests.log` - this will generally be empty unless turning on debugging inside the server.
- `channel_<channelname>.log` - these are channel logs for the in-game channels They are also used
by the `/history` flag in-game to get the latest message history.

15
typeclasses/README.md Normal file
View file

@ -0,0 +1,15 @@
# 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 subdirectories you create.

0
typeclasses/__init__.py Normal file
View file

104
typeclasses/accounts.py Normal file
View file

@ -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

62
typeclasses/channels.py Normal file
View file

@ -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

80
typeclasses/characters.py Normal file
View file

@ -0,0 +1,80 @@
"""
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
from evennia.utils import inherits_from
from typeclasses import rooms
from typeclasses.exits import Exit
from utils.utils import has_tag, has_effect, has_effect_in
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.
"""
def at_object_creation(self):
self.db.desc = "A human being."
self.db.health = 1
self.db.mana = 1
self.db.strength = 1
self.db.agility = 1
self.db.intellect = 1
self.db.equipment = {
'head': None,
'torso': None,
'legs': None,
'right hand': None,
'left hand': None,
'foot': None
}
self.db.spells = []
self.db.current_action = None
def get_health(self):
return self.db.health
def get_mana(self):
return self.db.mana
def get_abilities(self):
return self.db.strength, self.db.agility, self.db.intellect
def at_look(self, target, **kwargs):
description = super().at_look(target, **kwargs)
# You can't see things in room if it's dark.
if inherits_from(self.location, rooms.IndoorRoom):
if not self.is_superuser and not self.location.db.is_lit and \
not (inherits_from(target, rooms.Room) or inherits_from(target, Exit)):
description = "Could not find '{}'.".format(target.name)
return description

81
typeclasses/effects.py Normal file
View file

@ -0,0 +1,81 @@
from evennia.utils import inherits_from, logger
from typeclasses.characters import Character
from typeclasses.scripts import Script
from typeclasses.rooms import IndoorRoom
from utils.utils import has_tag, toggle_effect, has_effect
class EffectMagicalLight(Script):
"""
"""
def at_script_creation(self):
self.key = "effect_magic_light_script"
self.desc = "not now"
self.start_delay = True
self.interval = 20
self.persistent = True # will survive reload
self.repeats = 1
def at_start(self):
if self.obj:
if not has_effect(self.obj, "emit_magic_light"):
toggle_effect(self.obj, "emit_magic_light")
if self.obj.location:
if inherits_from(self.obj.location, IndoorRoom):
self.obj.location.msg_contents("{} starts emitting a soft and steady light.".format(self.obj.name))
self.obj.location.check_light_state()
# check if effect target is in actor contents
if self.obj.location.location and inherits_from(self.obj.location.location, IndoorRoom):
if inherits_from(self.obj.location, Character):
self.obj.location.msg("{} starts emitting a soft and steady light.".format(self.obj.name))
self.obj.location.location.check_light_state()
def at_stop(self):
if self.obj:
if has_effect(self.obj, "emit_magic_light"):
toggle_effect(self.obj, "emit_magic_light")
if self.obj.location:
if inherits_from(self.obj.location, IndoorRoom):
self.obj.location.msg_contents("{} stops emitting light.".format(self.obj.name))
self.obj.location.check_light_state()
# check if effect target is in actor contents
if self.obj.location.location and inherits_from(self.obj.location.location, IndoorRoom):
if inherits_from(self.obj.location, Character):
self.obj.location.msg("{} stops emitting light.".format(self.obj.name))
self.obj.location.location.check_light_state()
def at_repeat(self):
self.at_stop()
class EffectCharm(Script):
"""
"""
def at_script_creation(self):
self.key = "effect_charm_script"
self.desc = "not now"
self.start_delay = True
self.interval = 20
self.persistent = True # will survive reload
self.repeats = 1
def at_start(self):
if self.obj:
if not has_effect(self.obj, "charm"):
toggle_effect(self.obj, "charm")
self.obj.db.real_owner = self.obj.db.owner
self.obj.db.owner = self.db.source
def at_stop(self):
if self.obj:
if has_effect(self.obj, "charm"):
toggle_effect(self.obj, "charm")
self.obj.db.owner = self.obj.db.real_owner
del self.obj.db.real_owner
def at_repeat(self):
self.at_stop()

171
typeclasses/exits.py Normal file
View file

@ -0,0 +1,171 @@
"""
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.
"""
import random
from evennia import DefaultExit
from utils.utils import has_effect
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 at_traverse(self, traversing_object, target_location, **kwargs):
if has_effect(traversing_object, "is_busy"):
traversing_object.msg("You are already busy {}.".format(traversing_object.db.current_action.busy_msg()))
else:
super().at_traverse(traversing_object, target_location, **kwargs)
def delete(self):
if self.location and self.location.db.zone:
self.location.db.zone.remove_room_exit(self)
return super().delete()
"""
BaseDoor
Contribution - Griatch 2016
A simple two-way exit that represents a door that can be opened and
closed. Can easily be expanded from to make it lockable, destroyable
etc.
To try it out, `@dig` a new room and then use the (overloaded) `@open`
command to open a new doorway to it like this:
@open doorway:contrib.simpledoor.SimpleDoor = otherroom
You can then use `open doorway' and `close doorway` to change the open
state. If you are not superuser (`@quell` yourself) you'll find you
cannot pass through either side of the door once it's closed from the
other side.
"""
class BaseDoor(Exit):
"""
A two-way exit "door" with some methods for affecting both "sides"
of the door at the same time. For example, set a lock on either of the two
sides using `exitname.setlock("traverse:false())`
"""
DARK_MESSAGES = (
"It is pitch black. You are likely to be eaten by a grue.",
"It's pitch black. You fumble around but cannot find anything.",
"You don't see a thing. You feel around, managing to bump your fingers hard against something. Ouch!",
"You don't see a thing! Blindly grasping the air around you, you find nothing.",
"It's totally dark here. You almost stumble over some un-evenness in the ground.",
"You are completely blind. For a moment you think you hear someone breathing nearby ... "
"\n ... surely you must be mistaken.",
"Blind, you think you find some sort of object on the ground, but it turns out to be just a stone.",
"Blind, you bump into a wall. The wall seems to be covered with some sort of vegetation,"
" but its too damp to burn.",
"You can't see anything, but the air is damp. It feels like you are far underground.",
)
def at_object_creation(self):
"""
Called the very first time the door is created.
"""
self.db.dark_desc = ""
self.db.return_exit = None
def setlock(self, lockstring):
"""
Sets identical locks on both sides of the door.
Args:
lockstring (str): A lockstring, like `"traverse:true()"`.
"""
self.locks.add(lockstring)
self.db.return_exit.locks.add(lockstring)
def setdesc(self, description):
"""
Sets identical descs on both sides of the door.
Args:
setdesc (str): A description.
"""
self.db.desc = description
self.db.return_exit.db.desc = description
def delete(self):
"""
Deletes both sides of the door.
"""
# we have to be careful to avoid a delete-loop.
if self.db.return_exit:
super().delete()
super().delete()
return True
def at_failed_traverse(self, traversing_object):
"""
Called when door traverse: lock fails.
Args:
traversing_object (Typeclassed entity): The object
attempting the traversal.
"""
traversing_object.msg("%s is closed." % self.key)
def return_appearance(self, looker, **kwargs):
"""
This formats a description. It is the hook a 'look' command
should call.
Args:
looker (Object): Object doing the looking.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
if not looker:
return ""
string = "{}\n".format(self.get_display_name(looker))
string += "-" * 100
string += "|/"
if not self.db.is_lit and not looker.is_superuser:
desc = self.db.dark_desc if self.db.dark_desc else random.choice(self.DARK_MESSAGES)
else:
desc = self.db.desc
if desc:
string += "{}".format(desc)
return string

View file

@ -0,0 +1,41 @@
import random
from evennia import gametime
from typeclasses.objects import Object
class Action(Object):
def at_object_creation(self):
super().at_object_creation()
self.db.action_time = 0
self.db.action_completion_time = 0
def prepare(self, actor):
#set duration of action
self.db.action_completion_time = gametime.gametime() + self.db.action_time
def update(self, actor):
pass
def complete(self, actor):
pass
def completion_time(self):
return self.db.action_completion_time
class ActionIdle(Action):
def at_object_creation(self):
super().at_object_creation()
self.db.action_time = 10
def update(self, actor):
pass
def complete(self, actor):
roll = random.randrange(100)
if roll < 10:
actor.emote()
#TEST
actor.db.energy = 0 if actor.db.energy == 0 else actor.db.energy - 1

47
typeclasses/mobs.py Normal file
View file

@ -0,0 +1,47 @@
import random
from evennia import create_object
from typeclasses.objects import Object
from typeclasses.scripts import Script
from typeclasses.mob_actions import ActionIdle
class Mob(Object):
def at_object_creation(self):
super().at_object_creation()
self.tags.add("ai_mob", category="general")
self.db.owner = None
self.db.health = 1
self.db.mana = 1
self.db.strength = 1
self.db.agility = 1
self.db.intellect = 1
# needs
self.db.energy = 100
def at_object_delete(self):
if self.db.action:
self.db.action.delete()
return True
def at_init(self):
self.db.action = None
def tick(self):
pass
def think(self):
if not self.db.action:
self.db.action = create_object(ActionIdle, key="action_idle")
self.db.action.prepare(self)
def emote(self):
if self.location:
self.location.msg_contents("{} is thinking something.".format(self.get_display_name(self.location)), exclude=self, from_obj=self)

264
typeclasses/objects.py Normal file
View file

@ -0,0 +1,264 @@
"""
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 collections import defaultdict
from evennia import DefaultObject
from evennia.utils import logger, evtable, inherits_from
from typeclasses.rooms import IndoorRoom
from utils.utils import has_effect_in, has_tag
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
"""
pass
class Feature(Object):
def at_object_creation(self):
self.locks.add("get:false(); puppet:false()")
# add attribute to store object description when viewed from a location
self.db.feature_desc = "You see |w{}|n.".format(self.get_numbered_name(1, None)[0])
def return_appearance(self, looker, **kwargs):
if not looker:
return ""
# get description, build string
string = "{}\n".format(self.get_display_name(looker))
string += "-" * 100
string += "|/"
desc = self.db.desc
if desc:
string += "{}".format(desc)
return string
class ContainerFeature(Feature):
def at_object_creation(self):
super().at_object_creation()
self.locks.add("put:all()")
def return_appearance(self, looker, **kwargs):
string = super().return_appearance(looker)
# get and identify all objects
visible = (con for con in self.contents if con != looker and con.access(looker, "view"))
exits, users, things = [], [], defaultdict(list)
for con in visible:
key = con.get_display_name(looker)
if con.destination or con.has_account:
logger.log_warn("{} is an exit or a character inside container {}.".format(con.dbref, self.dbref))
else:
# things can be pluralized
things[key].append(con)
if things:
table = evtable.EvTable()
for key, itemlist in sorted(things.items()):
table.add_row("|w{}|n".format(key), "|c{}|n".format(len(itemlist)))
string += "|/Contains:|/" + str(table)
else:
string += "|/The {} is empty.".format(self.name)
return string
def at_object_receive(self, obj, source_location):
"""
Called when an object enters the container.
"""
if inherits_from(self.location, "typeclasses.rooms.IndoorRoom"):
# if we are storing a light emitting object in a container
# we also check the room light state.
if has_effect_in(obj, ['emit_magic_light', 'emit_light']):
self.location.check_light_state()
class Item(Object):
def at_object_creation(self):
pass
def at_before_get(self, caller):
if not self.access(caller, 'view') and not caller.is_superuser:
caller.msg("Could not find '{}'".format(self.name))
return False
return True
class EquippableItem(Item):
def at_object_creation(self):
self.locks.add("equip:all()")
self.db.slot = 'hand'
def at_before_drop(self, dropper, **kwargs):
result = super().at_before_drop(dropper, **kwargs)
if result:
if has_tag(self, "equipped", "general"):
dropper.msg("You cannot drop an equipped item.")
result = False
return result
def at_equip(self, caller, where, **kwargs):
return True
def at_unequip(self, caller, where, **kwargs):
return True

287
typeclasses/rooms.py Normal file
View file

@ -0,0 +1,287 @@
"""
Room
Rooms are simple containers that has no location of their own.
"""
from collections import defaultdict
import random
from evennia import logger, search_tag
from evennia import DefaultRoom
from evennia.utils.utils import list_to_string
from evennia.utils import inherits_from
from utils import spath
from utils.utils import has_tag, fmt_light, fmt_dark, has_effect
from typeclasses.characters import Character
MAP_SIZE = 128
class Room(DefaultRoom):
"""
Rooms are like any Object, except their location is None
(which is default). They also use basetype_setup() to
add locks so they cannot be puppeted or picked up.
(to change that, use at_object_creation instead)
See examples/object.py for a list of
properties and methods available on all Objects.
"""
def at_object_creation(self):
self.locks.add("light:false()")
self.db.x = 0
self.db.y = 0
self.db.zone = None
class IndoorRoom(Room):
DARK_MESSAGES = (
"It is pitch black. You are likely to be eaten by a grue.",
"It's pitch black. You fumble around but cannot find anything.",
"You don't see a thing. You feel around, managing to bump your fingers hard against something. Ouch!",
"You don't see a thing! Blindly grasping the air around you, you find nothing.",
"It's totally dark here. You almost stumble over some un-evenness in the ground.",
"You are completely blind. For a moment you think you hear someone breathing nearby ... "
"\n ... surely you must be mistaken.",
"Blind, you think you find some sort of object on the ground, but it turns out to be just a stone.",
"Blind, you bump into a wall. The wall seems to be covered with some sort of vegetation,"
" but its too damp to burn.",
"You can't see anything, but the air is damp. It feels like you are far underground.",
)
def at_object_creation(self):
super().at_object_creation()
self.locks.add("search:all()")
self.db.is_lit = False
self.db.dark_desc = ""
def at_init(self):
"""
Called when room is first recached (such as after a reload)
"""
self.check_light_state()
def return_appearance(self, looker, **kwargs):
"""
This formats a description. It is the hook a 'look' command
should call.
Args:
looker (Object): Object doing the looking.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
if not looker:
return ""
# get and identify all objects
visible = (con for con in self.contents if con != looker and con.access(looker, "view"))
features, exits, users, things = [], [], [], defaultdict(list)
for con in visible:
key = con.get_display_name(looker)
if con.destination:
exits.append(key)
elif con.has_account:
users.append("|c%s|n" % key)
elif con.db.feature_desc:
features.append(con.db.feature_desc)
else:
# things can be pluralized
things[key].append(con)
# get description, build string
string = "{}\n".format(self.get_display_name(looker))
string += "-" * 100
string += "|/"
if not self.db.is_lit and not looker.is_superuser:
desc = self.db.dark_desc if self.db.dark_desc else random.choice(self.DARK_MESSAGES)
else:
desc = self.db.desc
if desc:
string += "{}".format(desc)
if features and (self.db.is_lit or looker.is_superuser):
for feature in features:
string += "|/{}".format(feature)
if things and (self.db.is_lit or looker.is_superuser):
# handle pluralization of things (never pluralize users)
thing_strings = []
for key, itemlist in sorted(things.items()):
nitem = len(itemlist)
if nitem == 1:
key, _ = itemlist[0].get_numbered_name(
nitem, looker, key=key)
else:
key = [item.get_numbered_name(nitem, looker, key=key)[
1] for item in itemlist][0]
thing_strings.append(key)
string += "|/In this place you see "
if len(thing_strings) == 1:
string += "|w{}|n.".format(thing_strings[0])
else:
for idx, thing in enumerate(thing_strings):
if idx != len(thing_strings) - 1:
string += "|w{}|n, ".format(thing)
else:
string += "and |w{}|n.".format(thing)
if exits:
string += "|/"
string += "-" * 100
string += "\n|wExits:|n " + list_to_string(exits)
if users:
if self.db.is_lit or looker.is_superuser:
string += "\n|wYou see:|n " + list_to_string(users)
else:
string += "\n|wYou sense you are not alone...|n"
return string
def check_light_state(self, exclude=None):
changed = False
# there is an object emitting light?
if any(self._carries_light(obj) for obj in self.contents if obj != exclude):
if not self.db.is_lit:
changed = True
self.msg_contents(fmt_light("The room lights up."))
self.db.is_lit = True
# show objects in room but not chars or exits
# for obj in self.contents:
# if not obj.has_account and not obj.destination:
# obj.locks.add('view:all()')
else:
if self.db.is_lit:
changed = True
self.msg_contents(fmt_dark("Darkness falls."), exclude)
# no one is carrying light - darken the room
self.db.is_lit = False
# hidden objects in room but not chars or exits
# for obj in self.contents:
# if not obj.has_account and not obj.destination:
# obj.locks.add('view:false()')
return changed
def _carries_light(self, obj):
"""
Checks if the given object carries anything that gives light.
Note that we do NOT look for a specific LightSource typeclass,
but for the Attribute is_giving_light - this makes it easy to
later add other types of light-giving items. We also accept
if there is a light-giving object in the room overall (like if
a splinter was dropped in the room)
"""
return (
has_effect(obj, 'emit_light') or has_effect(obj, 'emit_magic_light')
or any(o for o in obj.contents
if (has_effect(o, 'emit_light') or has_effect(o, 'emit_magic_light'))
and not inherits_from(obj, "typeclasses.objects.ContainerFeature"))
)
def at_object_receive(self, obj, source_location):
"""
Called when an object enters the room.
"""
self.check_light_state()
def at_object_leave(self, obj, target_location):
"""
In case people leave with the light.
This also works if they are teleported away.
"""
# do not test for an object going in character's inventory
if inherits_from(target_location, Character):
# obj.locks.add('view:all()')
return
# since this hook is called while the object is still in the room,
# we exclude it from the light check, to ignore any light sources
# it may be carrying.
self.check_light_state(exclude=obj)
def get_display_name(self, looker, **kwargs):
display_name = super().get_display_name(looker, **kwargs)
if self.db.is_lit:
display_name = fmt_light(display_name)
else:
display_name = fmt_dark(display_name)
return display_name
class Zone(DefaultRoom):
"""
Zones are containers for rooms and, for now, is used to
provide path-finding capabilities to mob.
"""
def at_object_creation(self):
super().at_object_creation()
self.tags.add("zone", category="general")
self.locks.add(";".join(["get:false()", "puppet:false()", "view:perm(zone) or perm(Builder)"]))
self.at_init()
def at_init(self):
super().at_init()
# when reloaded recalculate path-finding data
self.create_paths()
def create_paths(self):
self.ndb.sp_graph = spath.Graph()
self.ndb.map = [[{"room_id": -1} for i in range(MAP_SIZE)] for j in range(MAP_SIZE)]
rooms = search_tag(key=self.name, category="zoneId")
for room in rooms:
self.add_room(room)
def add_room(self, room):
# add to map
if 0 <= room.db.x < MAP_SIZE and 0 <= room.db.y < MAP_SIZE and self.ndb.map[room.db.x][room.db.y]["room_id"] == -1:
self.ndb.map[room.db.x][room.db.y]["room_id"] = room.dbref
else:
logger.log_err("Cannot add room {} to {} at position {}:{}.".format(room.dbref, self.dbref, room.db.x, room.db.y))
raise Exception("Cannot add room {} to {} at position {}:{}.".format(room.dbref, self.dbref, room.db.x, room.db.y))
# avoid inserting room into graph if is already inserted
if not self.ndb.sp_graph.is_vertex(room):
self.ndb.sp_graph.add_vertex(room)
room.tags.add(self.name, category="zoneId")
room.db.zone = self
self.update_room_exits(room)
def update_room_exits(self, room):
if self.ndb.sp_graph.is_vertex(room):
for ex in room.exits: # iterate exits in the room
if not self.ndb.sp_graph.is_edge(room, ex.destination):
self.ndb.sp_graph.add_edge(room, ex.destination, 1, ex.name)
def remove_room_exit(self, exit_obj):
if self.ndb.sp_graph.is_vertex(exit_obj.location) and self.ndb.sp_graph.is_edge(exit_obj.location, exit_obj.destination):
self.ndb.sp_graph.del_edge(exit_obj.location, exit_obj.destination)
def delete(self):
rooms = search_tag(key=self.name, category="zoneId")
for room in rooms:
room.tags.remove(self.name, category="zoneId")
return super().delete()
def shortest_path(self, start, end):
return spath.shortestPath(self.ndb.sp_graph, start, end)

164
typeclasses/scripts.py Normal file
View file

@ -0,0 +1,164 @@
"""
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.
"""
import random
from evennia import DefaultScript, gametime, search_tag, logger
from evennia.utils import inherits_from
from utils.utils import has_tag, toggle_effect, has_effect
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
class CmdActionScript(Script):
"""
"""
def at_script_creation(self):
super().at_script_creation()
self.start_delay = True
self.persistent = True # will survive reload
self.repeats = 1
def busy_msg(self):
return "doing something else"
class AiManagerScript(Script):
"""
"""
def at_script_creation(self):
super().at_script_creation()
self.key = "ai_manager_script"
self.desc = "Does things."
def at_start(self):
logger.log_info("[AiManagerScript] starting...")
bots = search_tag(key="ai_mob", category="general")
logger.log_info("[AiManagerScript] found %d ai aware mobs." % len(bots))
logger.log_info("[AiManagerScript] started.")
def at_stop(self):
logger.log_info("[AiManagerScript] stopped.")
def at_repeat(self):
current_time = gametime.gametime()
bots = search_tag(key="ai_mob", category="general")
for bot in bots:
bot.think()
if bot.db.action != None:
bot.db.action.update(bot)
if bot.db.action.completion_time() <= current_time:
bot.db.action.complete(bot)
bot.db.action.delete()
class Weather(Script):
"""
A timer script that displays weather info. Meant to
be attached to a room.
"""
def at_script_creation(self):
self.key = "weather_script"
self.desc = "Gives random weather messages."
self.interval = 60 * 1 # every 5 minutes
self.persistent = True # will survive reload
def at_repeat(self):
"called every self.interval seconds."
rand = random.random()
if rand < 0.5:
weather = "A faint breeze is felt."
elif rand < 0.7:
weather = "Clouds sweep across the sky."
else:
weather = "There is a light drizzle of rain."
# send this message to everyone inside the object this
# script is attached to (likely a room)
self.obj.msg_contents(weather)

0
utils/__init__.py Normal file
View file

63
utils/building.py Normal file
View file

@ -0,0 +1,63 @@
from evennia.contrib.ingame_python import typeclasses
from evennia.prototypes import spawner
from evennia.utils import inherits_from
from evennia.utils.search import search_object
def create_room(room_prototype, x, y, zone_id):
zones = search_object(zone_id, typeclass="typeclasses.rooms.Zone", exact=True)
if not zones:
raise Exception("create_room: cannot find zone {}".format(zone_id))
zone = zones[0]
room, *rest = spawner.spawn(room_prototype)
room.db.x = x
room.db.y = y
zone.add_room(room)
return room
def create_exit(exit_prototype, location, direction):
x = location.db.x
y = location.db.y
if direction == "north":
x -= 1
if direction == "south":
x += 1
if direction == "west":
y -= 1
if direction == "east":
y += 1
destination_id = location.db.zone.ndb.map[x][y]["room_id"]
if destination_id == -1:
return False
destinations = search_object(destination_id, exact=True)
if not destinations:
raise Exception("create_exit: cannot find room {}".format(destination_id))
destination = destinations[0]
# check if exists a room in the selected direction
exits = search_object(direction, candidates=location.exits)
if exits:
exit_obj = exits[0]
exit_obj.delete()
exit_obj, *rest = spawner.spawn(exit_prototype)
exit_obj.location = location
exit_obj.destination = destination
exit_obj.aliases.add(direction)
if inherits_from(exit_obj, "typeclasses.exits.BaseDoor"):
# a door - create its counterpart
return_exit, *rest = spawner.spawn(exit_prototype)
return_exit.location = destination
return_exit.destination = location
return_exit.aliases.add(direction)
exit_obj.db.return_exit = return_exit
return_exit.db.return_exit = exit_obj
return exit_obj

1109
utils/crafting.py Normal file

File diff suppressed because it is too large Load diff

71
utils/priodict.py Normal file
View file

@ -0,0 +1,71 @@
# Priority dictionary using binary heaps
# David Eppstein, UC Irvine, 8 Mar 2002
# Implements a data structure that acts almost like a dictionary, with two modifications:
# (1) D.smallest() returns the value x minimizing D[x]. For this to work correctly,
# all values D[x] stored in the dictionary must be comparable.
# (2) iterating "for x in D" finds and removes the items from D in sorted order.
# Each item is not removed until the next item is requested, so D[x] will still
# return a useful value until the next iteration of the for-loop.
# Each operation takes logarithmic amortized time.
from __future__ import generators
class priorityDictionary(dict):
def __init__(self):
'''Initialize priorityDictionary by creating binary heap of pairs (value,key).
Note that changing or removing a dict entry will not remove the old pair from the heap
until it is found by smallest() or until the heap is rebuilt.'''
self.__heap = []
dict.__init__(self)
def smallest(self):
'''Find smallest item after removing deleted items from front of heap.'''
if len(self) == 0:
raise IndexError("smallest of empty priorityDictionary")
heap = self.__heap
while heap[0][1] not in self or self[heap[0][1]] != heap[0][0]:
lastItem = heap.pop()
insertionPoint = 0
while 1:
smallChild = 2*insertionPoint+1
if smallChild+1 < len(heap) and heap[smallChild] > heap[smallChild+1] :
smallChild += 1
if smallChild >= len(heap) or lastItem <= heap[smallChild]:
heap[insertionPoint] = lastItem
break
heap[insertionPoint] = heap[smallChild]
insertionPoint = smallChild
return heap[0][1]
def __iter__(self):
'''Create destructive sorted iterator of priorityDictionary.'''
def iterfn():
while len(self) > 0:
x = self.smallest()
yield x
del self[x]
return iterfn()
def __setitem__(self,key,val):
'''Change value stored in dictionary and add corresponding pair to heap.
Rebuilds the heap if the number of deleted items gets large, to avoid memory leakage.'''
dict.__setitem__(self,key,val)
heap = self.__heap
if len(heap) > 2 * len(self):
self.__heap = [(v,k) for k,v in self.iteritems()]
self.__heap.sort() # builtin sort probably faster than O(n)-time heapify
else:
newPair = (val,key)
insertionPoint = len(heap)
heap.append(None)
while insertionPoint > 0 and newPair < heap[(insertionPoint-1)//2]:
heap[insertionPoint] = heap[(insertionPoint-1)//2]
insertionPoint = (insertionPoint-1)//2
heap[insertionPoint] = newPair
def setdefault(self,key,val):
'''Reimplement setdefault to pass through our customized __setitem__.'''
if key not in self:
self[key] = val
return self[key]

120
utils/spath.py Normal file
View file

@ -0,0 +1,120 @@
# Dijkstra's algorithm for shortest paths
# David Eppstein, UC Irvine, 4 April 2002
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/117228
from utils.priodict import priorityDictionary
def Dijkstra(G,start,end=None):
"""
Find shortest paths from the start vertex to all vertices nearer than or equal to the end.
The input graph G is assumed to have the following representation:
A vertex can be any object that can be used as an index into a dictionary.
G is a dictionary, indexed by vertices. For any vertex v, G[v] is itself a dictionary,
indexed by the neighbors of v. For any edge v->w, G[v][w] is the length of the edge.
This is related to the representation in <http://www.python.org/doc/essays/graphs.html>
where Guido van Rossum suggests representing graphs as dictionaries mapping vertices
to lists of outgoing edges, however dictionaries of edges have many advantages over lists:
they can store extra information (here, the lengths), they support fast existence tests,
and they allow easy modification of the graph structure by edge insertion and removal.
Such modifications are not needed here but are important in many other graph algorithms.
Since dictionaries obey iterator protocol, a graph represented as described here could
be handed without modification to an algorithm expecting Guido's graph representation.
Of course, G and G[v] need not be actual Python dict objects, they can be any other
type of object that obeys dict protocol, for instance one could use a wrapper in which vertices
are URLs of web pages and a call to G[v] loads the web page and finds its outgoing links.
The output is a pair (D,P) where D[v] is the distance from start to v and P[v] is the
predecessor of v along the shortest path from s to v.
Dijkstra's algorithm is only guaranteed to work correctly when all edge lengths are positive.
This code does not verify this property for all edges (only the edges examined until the end
vertex is reached), but will correctly compute shortest paths even for some graphs with negative
edges, and will raise an exception if it discovers that a negative edge has caused it to make a mistake.
"""
D = {} # dictionary of final distances
P = {} # dictionary of predecessors
Q = priorityDictionary() # estimated distances of non-final vertices
Q[start] = 0
for v in Q:
D[v] = Q[v]
if v == end: break
for w in G[v]:
vwLength = D[v] + G[v][w]
if w in D:
if vwLength < D[w]:
raise ValueError("Dijkstra: found better path to already-final vertex")
elif w not in Q or vwLength < Q[w]:
Q[w] = vwLength
P[w] = v
return (D,P)
def shortestPath(G,start,end):
"""
Find a single shortest path from the given start vertex to the given end vertex.
The input has the same conventions as Dijkstra().
The output is a list of the vertices in order along the shortest path.
"""
D,P = Dijkstra(G,start,end)
Path = []
while 1:
Path.append(end)
if end == start: break
end = P[end]
Path.reverse()
return Path
class Graph:
def __init__(self):
self.graph = {}
def add_vertex(self, vertex):
self.graph[vertex] = {}
def del_vertex(self, vertex):
del self.graph[vertex]
def is_vertex(self, vertex):
if vertex in self.graph:
return True
else:
return False
def add_edge(self, vertex_start, vertex_end, weight, data):
self.graph[vertex_start][vertex_end] = weight
def del_edge(self, vertex_start, vertex_end):
del self.graph[vertex_start][vertex_end]
def is_edge(self, vertex_start, vertex_end):
if vertex_start in self.graph and vertex_end in self.graph[vertex_start]:
return True
else:
return False
def get_graph(self):
return self.graph
def __getitem__(self, key):
return self.graph.get(key, {})
def __len__(self):
return len(self.graph)
# example, CLR p.528
# G = {'s': {'u':10, 'x':5},
# 'u': {'v':1, 'x':2},
# 'v': {'y':4},
# 'x':{'u':3,'v':9,'y':2},
# 'y':{'s':7,'v':6}}
#
# print Dijkstra(G,'s')
# print shortestPath(G,'s','v')

34
utils/utils.py Normal file
View file

@ -0,0 +1,34 @@
from evennia.prototypes import spawner
def has_tag(obj, key, category):
return obj.tags.get(key=key, category=category) != None;
def fmt_light(message):
return "|r\u2600|y {} |r\u2600|n".format(message)
def fmt_dark(message):
return "|w\u2600|=h {} |w\u2600|n".format(message)
def toggle_effect(obj, effect):
if has_tag(obj, effect, "effect"):
obj.tags.remove(effect, category="effect")
else:
obj.tags.add(effect, category="effect")
def has_effect(obj, effect):
return has_tag(obj, effect, "effect")
def has_effect_in(obj, effects):
return any(True for effect in effects if has_tag(obj, effect, "effect"))
def indefinite_article(name):
"""Return the right english indefinite article for given name."""
the_vowels = ["a","e","i","o","u"]
value = ""
if name[0].lower() in the_vowels:
value = "an"
else:
value = "a"
return value

0
web/__init__.py Normal file
View file

View file

@ -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/`

View file

@ -0,0 +1,3 @@
You can replace the CSS files for Evennia's webclient here.
You can find the original files in `evennia/web/static/webclient/css/`

View file

@ -0,0 +1,3 @@
You can replace the javascript files for Evennia's webclient page here.
You can find the original files in `evennia/web/static/webclient/js/`

View file

@ -0,0 +1,3 @@
You can replace the CSS files for Evennia's homepage here.
You can find the original files in `evennia/web/static/website/css/`

View file

@ -0,0 +1,3 @@
You can replace the image files for Evennia's home page here.
You can find the original files in `evennia/web/static/website/images/`

View file

@ -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.

View file

@ -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/`

View file

@ -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/`

View file

@ -0,0 +1,3 @@
Flatpages require a default.html template, which can be overwritten by placing it in this folder.
You can find the original files in `evennia/web/website/templates/website/flatpages/`

View file

@ -0,0 +1,3 @@
The templates involving login/logout can be overwritten here.
You can find the original files in `evennia/web/website/templates/website/registration/`

18
web/urls.py Normal file
View file

@ -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

10
world/README.md Normal file
View file

@ -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
world/__init__.py Normal file
View file

26
world/batch_cmds.ev Normal file
View file

@ -0,0 +1,26 @@
#
# A batch-command file is a way to build a game world
# in a programmatic way, by placing a sequence of
# build commands after one another. This allows for
# using a real text editor to edit e.g. descriptions
# rather than entering text on the command line.
#
# A batch-command file is loaded with @batchprocess in-game:
#
# @batchprocess[/interactive] tutorial_examples.batch_cmds
#
# A # as the first symbol on a line begins a comment and
# marks the end of a previous command definition. This is important,
# - every command must be separated by at least one line of comment.
#
# All supplied commands are given as normal, on their own line
# and accept arguments in any format up until the first next
# comment line begins. Extra whitespace is removed; an empty
# line in a command definition translates into a newline.
#
# See `evennia/contrib/tutorial_examples/batch_cmds.ev` for
# an example of a batch-command code. See also the batch-code
# system for loading python-code this way.
#

115
world/batches/init.ev Normal file
View file

@ -0,0 +1,115 @@
# We start from limbo. Remember that every command in the batchfile
# -must- be separated by at least one comment-line.
@tel #2
#
@dig/tel ruined room;start_00:typeclasses.rooms.IndoorRoom
#
@desc here =
This room, once royally adorned, now lies in ruins.
A violent battle must have been fought in this place,
mixed with the broken wood of the furniture stand out broken weapons
and bodies devoured by the passage of time.
The long oak table that once occupied the center of the room
it is overturned against the wall to create a makeshift barricade.
#
@create/drop skeleton of a soldier in armor;skeleton;soldier:typeclasses.objects.Feature
#
@desc skeleton =
The skeleton of a soldier, still locked in their armor now
rusty. They lie leaning against the barricade where he died, their bony hand
clutched to the handle of a broken spear.
#
@set skeleton/feature_desc = A |wskeleton|n in a broken armor is collapsed on the floor behind the table.
#
@lock skeleton = search:all()
#
@dig/tel long hall;hall;start_01:typeclasses.rooms.IndoorRoom
#
@desc start_01 =
A long hall paved with large hewn stones, thick oak beams
still hold up the ceiling frescoed with gilded symbols. Dust corpuscles swirl
in the light, disturbed by your passage.
#Una lunga sala mattonata da grosse pietre squadrate, le spesse travi di quercia
#ancora reggono il soffitto affrescato di simboli dorati. Corpuscoli di polvere vorticano
#nella stanza illuminata, disturbati dal vostro passaggio.
#
@open sculpted archway;archway;start_door_00:typeclasses.exits.BaseDoor = start_00
#
@descdoor start_door_00 =
A beautifully sculpted arched entrance. Two figures are carved into the
stone on either side of the door, on the right Its, the muse of Deception, on
right Izzac, the muse of Authority.
#Un'entrata ad arco meravigliosamente scolpita. Due figure sono intagliate nella
#pietra ai lati della porta, alla destra Its, la musa della manipolazione, alla
#destra Izzac, la musa dell'autorità.
#
@create/drop pile of stones;pile;rubble_01:typeclasses.objects.Feature
#
@set rubble_01/feature_desc =
A |wpile of stones|n and a collapsed beam from the ceiling make it difficult to cross
this area.
#Un cumulo di pietre e travi crollate dal soffitto rendono difficoltoso attraversare
#questa zona.
#
@desc rubble_01 =
A large root system pierced the ceiling of this room, shattering one
of the load-bearing boards. Some of the covering stones now lie damaged on the ground,
filling the floor with debris.
#Un grosso sistema di radici ha perforato il soffitto di questa sala, spezzando una
#delle assi portanti. Una parte delle pietre di copertura sono rovinate al suolo,
#riempiendo il pavimento di detriti.
#
@dig/tel old guardhouse;guardhouse;start_02:typeclasses.rooms.IndoorRoom
#
@desc start_02 =
An old guardhouse devastated by the fighting that took place in these halls.
The only part that has been spared is the ceiling, completely covered with
peeling frescoes depicting scenes of martial life.
#Una vecchia guardiola devastata dal combattimento avvenuto in queste sale.
#L'unica parte che è stata risparmiata è il soffitto, completamente ricoperto da
#affreschi scrostati rappresentati scene di vita marziale.
#
@open open doorway;doorway;start_door_01:typeclasses.exits.BaseDoor = start_01
#
@descdoor start_door_01 =
A large doorway, with no door. The rune '|y◧|n' is engraved on the granite jamb.
#
@dig/tel empty corridor;corridor;start_03:typeclasses.rooms.IndoorRoom
#
@desc start_03 =
The sides of the corridor are lined with stone archways, each adorned by a
stone statue. All the statues have been broken behind recognition.
#
@open small doorway;start_door_03:typeclasses.exits.BaseDoor = start_01
#
@descdoor start_door_03 =
A small doorway, with no door. The rune '|y◓|n' is engraved on the granite jamb.
#
@dig/tel ruined temple;temple;start_04:typeclasses.rooms.IndoorRoom
#
@desc start_04 =
This building seems to have survived the ravages of time better than
most of the others. Its arched roof and wide spaces suggests that
this is a temple or church of some kind.
#
@open large reinforced door;reinforced door;door;start_door_02:typeclasses.exits.BaseDoor = start_03
#
@descdoor start_door_02 =
A big oak door, reinforced with iron bars across its frame.
It bears marks and burns all over its surface but hasn't been breached during the
siege.
#
zone tutorial_zone
#
zone/addroom tutorial_zone = start_door_00
#
zone/addroom tutorial_zone = start_door_01
#
zone/addroom tutorial_zone = start_door_02
#
zone/addroom tutorial_zone = start_door_03
#
zone/addroom tutorial_zone = start_door_04
#
@tel start_00
#

219
world/prototypes.py Normal file
View file

@ -0,0 +1,219 @@
"""
Prototypes
A prototype is a simple way to create individualized instances of a
given typeclass. It is dictionary with specific key names.
For example, you might have a Sword typeclass that implements everything a
Sword would need to do. The only difference between different individual Swords
would be their key, description and some Attributes. The Prototype system
allows to create a range of such Swords with only minor variations. Prototypes
can also inherit and combine together to form entire hierarchies (such as
giving all Sabres and all Broadswords some common properties). Note that bigger
variations, such as custom commands or functionality belong in a hierarchy of
typeclasses instead.
A prototype can either be a dictionary placed into a global variable in a
python module (a 'module-prototype') or stored in the database as a dict on a
special Script (a db-prototype). The former can be created just by adding dicts
to modules Evennia looks at for prototypes, the latter is easiest created
in-game via the `olc` command/menu.
Prototypes are read and used to create new objects with the `spawn` command
or directly via `evennia.spawn` or the full path `evennia.prototypes.spawner.spawn`.
A prototype dictionary have the following keywords:
Possible keywords are:
- `prototype_key` - the name of the prototype. This is required for db-prototypes,
for module-prototypes, the global variable name of the dict is used instead
- `prototype_parent` - string pointing to parent prototype if any. Prototype inherits
in a similar way as classes, with children overriding values in their partents.
- `key` - string, the main object identifier.
- `typeclass` - string, if not set, will use `settings.BASE_OBJECT_TYPECLASS`.
- `location` - this should be a valid object or #dbref.
- `home` - valid object or #dbref.
- `destination` - only valid for exits (object or #dbref).
- `permissions` - string or list of permission strings.
- `locks` - a lock-string to use for the spawned object.
- `aliases` - string or list of strings.
- `attrs` - Attributes, expressed as a list of tuples on the form `(attrname, value)`,
`(attrname, value, category)`, or `(attrname, value, category, locks)`. If using one
of the shorter forms, defaults are used for the rest.
- `tags` - Tags, as a list of tuples `(tag,)`, `(tag, category)` or `(tag, category, data)`.
- Any other keywords are interpreted as Attributes with no category or lock.
These will internally be added to `attrs` (eqivalent to `(attrname, value)`.
See the `spawn` command and `evennia.prototypes.spawner.spawn` for more info.
"""
ROOM_EMPTY = {
"prototype_key": "room_empty",
"key": "empty room",
"desc": "An empty room.",
"typeclass": "typeclasses.rooms.IndoorRoom"
}
EXIT_EMPTY = {
"prototype_key": "exit_empty",
"key": "corridor",
"desc": "An empty corridor.",
"typeclass": "typeclasses.exits.BaseDoor"
}
BROKEN_CROWN = {
"prototype_key": "broken_crown",
"key": "broken crown",
"desc": "An old iron crown, dented and covered in rust.",
"typeclass": "typeclasses.objects.EquippableItem",
"slot": 'head'
}
MULTICOLORED_ROBE = {
"prototype_key": "multicolored robe",
"key": "multicolored robe",
"desc": "A long robe, made of many different colored cloth patches.",
"typeclass": "typeclasses.objects.EquippableItem",
"slot": 'torso'
}
PLAIN_TROUSERS = {
"prototype_key": "plain trousers",
"key": "plain trousers",
"desc": "Simple but robust cloth trousers.",
"typeclass": "typeclasses.objects.EquippableItem",
"slot": 'legs'
}
LEATHER_BOOTS = {
"prototype_key": "leather boots",
"key": "leather boots",
"desc": "A worn pair of leather boots.",
"typeclass": "typeclasses.objects.EquippableItem",
"slot": 'foot'
}
FEATURE_CONTAINER = {
"prototype_key": "feature_container",
"key": "chest",
"desc": "A chest.",
"feature_desc": "A |wchest|n lies on the floor.",
"typeclass": "typeclasses.objects.ContainerFeature"
}
FEATURE_SKELETON = {
"prototype_key": "feature_skeleton",
"key": "rugged skeleton",
"desc": "An old humanoid skeleton, eroded by the passage of time.",
"feature_desc": "A rugged humanoid |wskeleton|n lies on the floor, theirs bony hand still clutching a broken spear. What remains of theirs armor and clothings is too battered to let you recognize their origins.",
"typeclass": "typeclasses.objects.Feature"
}
STONE = {
"prototype_key": "stone",
"key": "stone",
"desc": "An unremarkable stone made of granite.",
"aliases": ["granite stone"],
"typeclass": "typeclasses.objects.Item"
}
BIG_STONE = {
"prototype_key": "big stone",
"key": "big stone",
"desc": "An unremarkable stone made of granite. It seems very heavy.",
"aliases": ["big granite stone"],
"get_err_msg": "You are not strong enough to lift this stone.",
"locks": "get:attr_gt(strength, 50)",
"typeclass": "typeclasses.objects.Item"
}
LANTERN = {
"prototype_key": "lantern",
"key": "old lantern",
"desc": "An old lantern, still filled with oil.",
"aliases": ["lantern"],
"attrs": [("is_lit", True, None, None)],
"tags": [("emit_light", "effect", None)],
"locks": "light:all()",
"typeclass": "typeclasses.objects.Item"
}
BLADE_TOOL = {
"prototype_key": "blade tool",
"key": "steel blade",
"desc": "A steel blade, with an oak handle wrapped in cloth.",
"aliases": ["blade"],
"tags": [("blade", "crafting_tool", None)],
"typeclass": "typeclasses.objects.EquippableItem",
"slot": 'foot'
}
WOOD_MATERIAL = {
"prototype_key": "wood_material",
"key": "piece of wood",
"desc": "An unremarkable piece of wood.",
"aliases": ["wood"],
"tags": [("wood", "crafting_material", None)],
"typeclass": "typeclasses.objects.Item"
}
BLOOD_MATERIAL = {
"prototype_key": "blood_material",
"key": "vial of blood",
"desc": "A vial of blood. Fresh.",
"aliases": ["blood, vial"],
"tags": [("blood", "crafting_material", None)],
"typeclass": "typeclasses.objects.Item"
}
SUMMONING_CIRCLE = {
"prototype_key": "summoning_circle",
"key": "summoning circle",
"aliases": ["circle"],
"desc": "A circular pattern of mystical runes drawn with blood.",
"feature_desc": "An arcane |wcircle of summoning|n is draw with blood on the floor.",
"typeclass": "typeclasses.objects.Feature"
}
## example of module-based prototypes using
## the variable name as `prototype_key` and
## simple Attributes
# from random import randint
#
# GOBLIN = {
# "key": "goblin grunt",
# "health": lambda: randint(20,30),
# "resists": ["cold", "poison"],
# "attacks": ["fists"],
# "weaknesses": ["fire", "light"],
# "tags": = [("greenskin", "monster"), ("humanoid", "monster")]
# }
#
# GOBLIN_WIZARD = {
# "prototype_parent": "GOBLIN",
# "key": "goblin wizard",
# "spells": ["fire ball", "lighting bolt"]
# }
#
# GOBLIN_ARCHER = {
# "prototype_parent": "GOBLIN",
# "key": "goblin archer",
# "attacks": ["short bow"]
# }
#
# This is an example of a prototype without a prototype
# (nor key) of its own, so it should normally only be
# used as a mix-in, as in the example of the goblin
# archwizard below.
# ARCHWIZARD_MIXIN = {
# "attacks": ["archwizard staff"],
# "spells": ["greater fire ball", "greater lighting"]
# }
#
# GOBLIN_ARCHWIZARD = {
# "key": "goblin archwizard",
# "prototype_parent" : ("GOBLIN_WIZARD", "ARCHWIZARD_MIXIN")
# }

23
world/recipes_base.py Normal file
View file

@ -0,0 +1,23 @@
from utils.crafting import CraftingRecipe
class WoodenPuppetRecipe(CraftingRecipe):
"""A puppet"""
name = "wooden puppet" # name to refer to this recipe as
tool_tags = ["blade"]
consumable_tags = ["wood"]
output_prototypes = [
{"key": "carved wooden doll",
"typeclass": "typeclasses.objects.Item",
"desc": "A small carved doll"}
]
class SummoningCircleRecipe(CraftingRecipe):
"""A summoning circle"""
name = "summoning circle" # name to refer to this recipe as
tool_tags = []
consumable_tags = ["blood"]
output_prototypes = [
"summoning_circle"
]

46
world/spells.py Normal file
View file

@ -0,0 +1,46 @@
from evennia import utils, create_script, logger
from evennia.utils import inherits_from
from typeclasses import effects
from typeclasses.mobs import Mob
from utils.utils import has_effect
def spell_light(caller, target, **kwargs):
if not target:
caller.msg("You need something to place your light on.")
return
target_obj = caller.search(target, location=[caller, caller.location])
if not target_obj:
return
if has_effect(target_obj, "emit_magic_light"):
caller.msg("{} already has a magical light on itself.".format(target_obj.name))
return
light_script = create_script(effects.EffectMagicalLight, obj=target_obj)
caller.msg("You cast |wlight|n on {}.".format(target_obj.name))
def spell_charm(caller, target, **kwargs):
if not target:
caller.msg("You need someone to place your charm on.")
return
target_obj = caller.search(target, location=[caller.location])
if not target_obj:
return
if not inherits_from(target_obj, Mob):
caller.msg("You cannot charm {}".format(target_obj.name))
return
if has_effect(target_obj, "charm"):
caller.msg("{} is already charmed.".format(target_obj.name))
return
charm_script = create_script(effects.EffectCharm, obj=target_obj, attributes=[("source", caller.dbref)])
caller.msg("You cast |wcharm|n on {}.".format(target_obj.name))