commit iniziale.
This commit is contained in:
commit
043cdfc230
63 changed files with 5219 additions and 0 deletions
52
.gitignore
vendored
Normal file
52
.gitignore
vendored
Normal 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
40
README.md
Normal file
|
@ -0,0 +1,40 @@
|
|||
# Welcome to Evennia!
|
||||
|
||||
This is your game directory, set up to let you start with
|
||||
your new game right away. An overview of this directory is found here:
|
||||
https://github.com/evennia/evennia/wiki/Directory-Overview#the-game-directory
|
||||
|
||||
You can delete this readme file when you've read it and you can
|
||||
re-arrange things in this game-directory to suit your own sense of
|
||||
organisation (the only exception is the directory structure of the
|
||||
`server/` directory, which Evennia expects). If you change the structure
|
||||
you must however also edit/add to your settings file to tell Evennia
|
||||
where to look for things.
|
||||
|
||||
Your game's main configuration file is found in
|
||||
`server/conf/settings.py` (but you don't need to change it to get
|
||||
started). If you just created this directory (which means you'll already
|
||||
have a `virtualenv` running if you followed the default instructions),
|
||||
`cd` to this directory then initialize a new database using
|
||||
|
||||
evennia migrate
|
||||
|
||||
To start the server, stand in this directory and run
|
||||
|
||||
evennia start
|
||||
|
||||
This will start the server, logging output to the console. Make
|
||||
sure to create a superuser when asked. By default you can now connect
|
||||
to your new game using a MUD client on `localhost`, port `4000`. You can
|
||||
also log into the web client by pointing a browser to
|
||||
`http://localhost:4001`.
|
||||
|
||||
# Getting started
|
||||
|
||||
From here on you might want to look at one of the beginner tutorials:
|
||||
http://github.com/evennia/evennia/wiki/Tutorials.
|
||||
|
||||
Evennia's documentation is here:
|
||||
https://github.com/evennia/evennia/wiki.
|
||||
|
||||
Enjoy!
|
6
__init__.py
Normal file
6
__init__.py
Normal 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
14
commands/README.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
# commands/
|
||||
|
||||
This folder holds modules for implementing one's own commands and
|
||||
command sets. All the modules' classes are essentially empty and just
|
||||
imports the default implementations from Evennia; so adding anything
|
||||
to them will start overloading the defaults.
|
||||
|
||||
You can change the organisation of this directory as you see fit, just
|
||||
remember that if you change any of the default command set classes'
|
||||
locations, you need to add the appropriate paths to
|
||||
`server/conf/settings.py` so that Evennia knows where to find them.
|
||||
Also remember that if you create new sub directories you must put
|
||||
(optionally empty) `__init__.py` files in there so that Python can
|
||||
find your modules.
|
0
commands/__init__.py
Normal file
0
commands/__init__.py
Normal file
285
commands/builder.py
Normal file
285
commands/builder.py
Normal 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
776
commands/command.py
Normal 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
133
commands/default_cmdsets.py
Normal 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
38
server/README.md
Normal file
|
@ -0,0 +1,38 @@
|
|||
# server/
|
||||
|
||||
This directory holds files used by and configuring the Evennia server
|
||||
itself.
|
||||
|
||||
Out of all the subdirectories in the game directory, Evennia does
|
||||
expect this directory to exist, so you should normally not delete,
|
||||
rename or change its folder structure.
|
||||
|
||||
When running you will find four new files appear in this directory:
|
||||
|
||||
- `server.pid` and `portal.pid`: These hold the process IDs of the
|
||||
Portal and Server, so that they can be managed by the launcher. If
|
||||
Evennia is shut down uncleanly (e.g. by a crash or via a kill
|
||||
signal), these files might erroneously remain behind. If so Evennia
|
||||
will tell you they are "stale" and they can be deleted manually.
|
||||
- `server.restart` and `portal.restart`: These hold flags to tell the
|
||||
server processes if it should die or start again. You never need to
|
||||
modify those files.
|
||||
- `evennia.db3`: This will only appear if you are using the default
|
||||
SQLite3 database; it a binary file that holds the entire game
|
||||
database; deleting this file will effectively reset the game for
|
||||
you and you can start fresh with `evennia migrate` (useful during
|
||||
development).
|
||||
|
||||
## server/conf/
|
||||
|
||||
This subdirectory holds the configuration modules for the server. With
|
||||
them you can change how Evennia operates and also plug in your own
|
||||
functionality to replace the default. You usually need to restart the
|
||||
server to apply changes done here. The most important file is the file
|
||||
`settings.py` which is the main configuration file of Evennia.
|
||||
|
||||
## server/logs/
|
||||
|
||||
This subdirectory holds various log files created by the running
|
||||
Evennia server. It is also the default location for storing any custom
|
||||
log files you might want to output using Evennia's logging mechanisms.
|
1
server/__init__.py
Normal file
1
server/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
1
server/conf/__init__.py
Normal file
1
server/conf/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
19
server/conf/at_initial_setup.py
Normal file
19
server/conf/at_initial_setup.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
"""
|
||||
At_initial_setup module template
|
||||
|
||||
Custom at_initial_setup method. This allows you to hook special
|
||||
modifications to the initial server startup process. Note that this
|
||||
will only be run once - when the server starts up for the very first
|
||||
time! It is called last in the startup process and can thus be used to
|
||||
overload things that happened before it.
|
||||
|
||||
The module must contain a global function at_initial_setup(). This
|
||||
will be called without arguments. Note that tracebacks in this module
|
||||
will be QUIETLY ignored, so make sure to check it well to make sure it
|
||||
does what you expect it to.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def at_initial_setup():
|
||||
pass
|
54
server/conf/at_search.py
Normal file
54
server/conf/at_search.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
"""
|
||||
Search and multimatch handling
|
||||
|
||||
This module allows for overloading two functions used by Evennia's
|
||||
search functionality:
|
||||
|
||||
at_search_result:
|
||||
This is called whenever a result is returned from an object
|
||||
search (a common operation in commands). It should (together
|
||||
with at_multimatch_input below) define some way to present and
|
||||
differentiate between multiple matches (by default these are
|
||||
presented as 1-ball, 2-ball etc)
|
||||
at_multimatch_input:
|
||||
This is called with a search term and should be able to
|
||||
identify if the user wants to separate a multimatch-result
|
||||
(such as that from a previous search). By default, this
|
||||
function understands input on the form 1-ball, 2-ball etc as
|
||||
indicating that the 1st or 2nd match for "ball" should be
|
||||
used.
|
||||
|
||||
This module is not called by default, to use it, add the following
|
||||
line to your settings file:
|
||||
|
||||
SEARCH_AT_RESULT = "server.conf.at_search.at_search_result"
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def at_search_result(matches, caller, query="", quiet=False, **kwargs):
|
||||
"""
|
||||
This is a generic hook for handling all processing of a search
|
||||
result, including error reporting.
|
||||
|
||||
Args:
|
||||
matches (list): This is a list of 0, 1 or more typeclass instances,
|
||||
the matched result of the search. If 0, a nomatch error should
|
||||
be echoed, and if >1, multimatch errors should be given. Only
|
||||
if a single match should the result pass through.
|
||||
caller (Object): The object performing the search and/or which should
|
||||
receive error messages.
|
||||
query (str, optional): The search query used to produce `matches`.
|
||||
quiet (bool, optional): If `True`, no messages will be echoed to caller
|
||||
on errors.
|
||||
|
||||
Keyword Args:
|
||||
nofound_string (str): Replacement string to echo on a notfound error.
|
||||
multimatch_string (str): Replacement string to echo on a multimatch error.
|
||||
|
||||
Returns:
|
||||
processed_result (Object or None): This is always a single result
|
||||
or `None`. If `None`, any error reporting/handling should
|
||||
already have happened.
|
||||
|
||||
"""
|
63
server/conf/at_server_startstop.py
Normal file
63
server/conf/at_server_startstop.py
Normal 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
55
server/conf/cmdparser.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
"""
|
||||
Changing the default command parser
|
||||
|
||||
The cmdparser is responsible for parsing the raw text inserted by the
|
||||
user, identifying which command/commands match and return one or more
|
||||
matching command objects. It is called by Evennia's cmdhandler and
|
||||
must accept input and return results on the same form. The default
|
||||
handler is very generic so you usually don't need to overload this
|
||||
unless you have very exotic parsing needs; advanced parsing is best
|
||||
done at the Command.parse level.
|
||||
|
||||
The default cmdparser understands the following command combinations
|
||||
(where [] marks optional parts.)
|
||||
|
||||
[cmdname[ cmdname2 cmdname3 ...] [the rest]
|
||||
|
||||
A command may consist of any number of space-separated words of any
|
||||
length, and contain any character. It may also be empty.
|
||||
|
||||
The parser makes use of the cmdset to find command candidates. The
|
||||
parser return a list of matches. Each match is a tuple with its first
|
||||
three elements being the parsed cmdname (lower case), the remaining
|
||||
arguments, and the matched cmdobject from the cmdset.
|
||||
|
||||
|
||||
This module is not accessed by default. To tell Evennia to use it
|
||||
instead of the default command parser, add the following line to
|
||||
your settings file:
|
||||
|
||||
COMMAND_PARSER = "server.conf.cmdparser.cmdparser"
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def cmdparser(raw_string, cmdset, caller, match_index=None):
|
||||
"""
|
||||
This function is called by the cmdhandler once it has
|
||||
gathered and merged all valid cmdsets valid for this particular parsing.
|
||||
|
||||
raw_string - the unparsed text entered by the caller.
|
||||
cmdset - the merged, currently valid cmdset
|
||||
caller - the caller triggering this parsing
|
||||
match_index - an optional integer index to pick a given match in a
|
||||
list of same-named command matches.
|
||||
|
||||
Returns:
|
||||
list of tuples: [(cmdname, args, cmdobj, cmdlen, mratio), ...]
|
||||
where cmdname is the matching command name and args is
|
||||
everything not included in the cmdname. Cmdobj is the actual
|
||||
command instance taken from the cmdset, cmdlen is the length
|
||||
of the command name and the mratio is some quality value to
|
||||
(possibly) separate multiple matches.
|
||||
|
||||
"""
|
||||
# Your implementation here
|
38
server/conf/connection_screens.py
Normal file
38
server/conf/connection_screens.py
Normal 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")
|
||||
)
|
51
server/conf/inlinefuncs.py
Normal file
51
server/conf/inlinefuncs.py
Normal 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
52
server/conf/inputfuncs.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
"""
|
||||
Input functions
|
||||
|
||||
Input functions are always called from the client (they handle server
|
||||
input, hence the name).
|
||||
|
||||
This module is loaded by being included in the
|
||||
`settings.INPUT_FUNC_MODULES` tuple.
|
||||
|
||||
All *global functions* included in this module are considered
|
||||
input-handler functions and can be called by the client to handle
|
||||
input.
|
||||
|
||||
An input function must have the following call signature:
|
||||
|
||||
cmdname(session, *args, **kwargs)
|
||||
|
||||
Where session will be the active session and *args, **kwargs are extra
|
||||
incoming arguments and keyword properties.
|
||||
|
||||
A special command is the "default" command, which is will be called
|
||||
when no other cmdname matches. It also receives the non-found cmdname
|
||||
as argument.
|
||||
|
||||
default(session, cmdname, *args, **kwargs)
|
||||
|
||||
"""
|
||||
|
||||
# def oob_echo(session, *args, **kwargs):
|
||||
# """
|
||||
# Example echo function. Echoes args, kwargs sent to it.
|
||||
#
|
||||
# Args:
|
||||
# session (Session): The Session to receive the echo.
|
||||
# args (list of str): Echo text.
|
||||
# kwargs (dict of str, optional): Keyed echo text
|
||||
#
|
||||
# """
|
||||
# session.msg(oob=("echo", args, kwargs))
|
||||
#
|
||||
#
|
||||
# def default(session, cmdname, *args, **kwargs):
|
||||
# """
|
||||
# Handles commands without a matching inputhandler func.
|
||||
#
|
||||
# Args:
|
||||
# session (Session): The active Session.
|
||||
# cmdname (str): The (unmatched) command name
|
||||
# args, kwargs (any): Arguments to function.
|
||||
#
|
||||
# """
|
||||
# pass
|
30
server/conf/lockfuncs.py
Normal file
30
server/conf/lockfuncs.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
"""
|
||||
|
||||
Lockfuncs
|
||||
|
||||
Lock functions are functions available when defining lock strings,
|
||||
which in turn limits access to various game systems.
|
||||
|
||||
All functions defined globally in this module are assumed to be
|
||||
available for use in lockstrings to determine access. See the
|
||||
Evennia documentation for more info on locks.
|
||||
|
||||
A lock function is always called with two arguments, accessing_obj and
|
||||
accessed_obj, followed by any number of arguments. All possible
|
||||
arguments should be handled with *args, **kwargs. The lock function
|
||||
should handle all eventual tracebacks by logging the error and
|
||||
returning False.
|
||||
|
||||
Lock functions in this module extend (and will overload same-named)
|
||||
lock functions from evennia.locks.lockfuncs.
|
||||
|
||||
"""
|
||||
|
||||
# def myfalse(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
# """
|
||||
# called in lockstring with myfalse().
|
||||
# A simple logger that always returns false. Prints to stdout
|
||||
# for simplicity, should use utils.logger for real operation.
|
||||
# """
|
||||
# print "%s tried to access %s. Access denied." % (accessing_obj, accessed_obj)
|
||||
# return False
|
105
server/conf/mssp.py
Normal file
105
server/conf/mssp.py
Normal file
|
@ -0,0 +1,105 @@
|
|||
"""
|
||||
|
||||
MSSP (Mud Server Status Protocol) meta information
|
||||
|
||||
Modify this file to specify what MUD listing sites will report about your game.
|
||||
All fields are static. The number of currently active players and your game's
|
||||
current uptime will be added automatically by Evennia.
|
||||
|
||||
You don't have to fill in everything (and most fields are not shown/used by all
|
||||
crawlers anyway); leave the default if so needed. You need to reload the server
|
||||
before the updated information is made available to crawlers (reloading does
|
||||
not affect uptime).
|
||||
|
||||
After changing the values in this file, you must register your game with the
|
||||
MUD website list you want to track you. The listing crawler will then regularly
|
||||
connect to your server to get the latest info. No further configuration is
|
||||
needed on the Evennia side.
|
||||
|
||||
"""
|
||||
|
||||
MSSPTable = {
|
||||
# Required fields
|
||||
"NAME": "Mygame", # usually the same as SERVERNAME
|
||||
# Generic
|
||||
"CRAWL DELAY": "-1", # limit how often crawler may update the listing. -1 for no limit
|
||||
"HOSTNAME": "", # telnet hostname
|
||||
"PORT": ["4000"], # telnet port - most important port should be *last* in list!
|
||||
"CODEBASE": "Evennia",
|
||||
"CONTACT": "", # email for contacting the mud
|
||||
"CREATED": "", # year MUD was created
|
||||
"ICON": "", # url to icon 32x32 or larger; <32kb.
|
||||
"IP": "", # current or new IP address
|
||||
"LANGUAGE": "", # name of language used, e.g. English
|
||||
"LOCATION": "", # full English name of server country
|
||||
"MINIMUM AGE": "0", # set to 0 if not applicable
|
||||
"WEBSITE": "", # http:// address to your game website
|
||||
# Categorisation
|
||||
"FAMILY": "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",
|
||||
}
|
24
server/conf/portal_services_plugins.py
Normal file
24
server/conf/portal_services_plugins.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
"""
|
||||
Start plugin services
|
||||
|
||||
This plugin module can define user-created services for the Portal to
|
||||
start.
|
||||
|
||||
This module must handle all imports and setups required to start
|
||||
twisted services (see examples in evennia.server.portal.portal). It
|
||||
must also contain a function start_plugin_services(application).
|
||||
Evennia will call this function with the main Portal application (so
|
||||
your services can be added to it). The function should not return
|
||||
anything. Plugin services are started last in the Portal startup
|
||||
process.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def start_plugin_services(portal):
|
||||
"""
|
||||
This hook is called by Evennia, last in the Portal startup process.
|
||||
|
||||
portal - a reference to the main portal application.
|
||||
"""
|
||||
pass
|
24
server/conf/server_services_plugins.py
Normal file
24
server/conf/server_services_plugins.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
"""
|
||||
|
||||
Server plugin services
|
||||
|
||||
This plugin module can define user-created services for the Server to
|
||||
start.
|
||||
|
||||
This module must handle all imports and setups required to start a
|
||||
twisted service (see examples in evennia.server.server). It must also
|
||||
contain a function start_plugin_services(application). Evennia will
|
||||
call this function with the main Server application (so your services
|
||||
can be added to it). The function should not return anything. Plugin
|
||||
services are started last in the Server startup process.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def start_plugin_services(server):
|
||||
"""
|
||||
This hook is called by Evennia, last in the Server startup process.
|
||||
|
||||
server - a reference to the main server application.
|
||||
"""
|
||||
pass
|
37
server/conf/serversession.py
Normal file
37
server/conf/serversession.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
"""
|
||||
ServerSession
|
||||
|
||||
The serversession is the Server-side in-memory representation of a
|
||||
user connecting to the game. Evennia manages one Session per
|
||||
connection to the game. So a user logged into the game with multiple
|
||||
clients (if Evennia is configured to allow that) will have multiple
|
||||
sessions tied to one Account object. All communication between Evennia
|
||||
and the real-world user goes through the Session(s) associated with that user.
|
||||
|
||||
It should be noted that modifying the Session object is not usually
|
||||
necessary except for the most custom and exotic designs - and even
|
||||
then it might be enough to just add custom session-level commands to
|
||||
the SessionCmdSet instead.
|
||||
|
||||
This module is not normally called. To tell Evennia to use the class
|
||||
in this module instead of the default one, add the following to your
|
||||
settings file:
|
||||
|
||||
SERVER_SESSION_CLASS = "server.conf.serversession.ServerSession"
|
||||
|
||||
"""
|
||||
|
||||
from evennia.server.serversession import ServerSession as BaseServerSession
|
||||
|
||||
|
||||
class ServerSession(BaseServerSession):
|
||||
"""
|
||||
This class represents a player's session and is a template for
|
||||
individual protocols to communicate with Evennia.
|
||||
|
||||
Each account gets one or more sessions assigned to them whenever they connect
|
||||
to the game server. All communication between game and account goes
|
||||
through their session(s).
|
||||
"""
|
||||
|
||||
pass
|
50
server/conf/settings.py
Normal file
50
server/conf/settings.py
Normal 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.")
|
41
server/conf/web_plugins.py
Normal file
41
server/conf/web_plugins.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
"""
|
||||
Web plugin hooks.
|
||||
"""
|
||||
|
||||
|
||||
def at_webserver_root_creation(web_root):
|
||||
"""
|
||||
This is called as the web server has finished building its default
|
||||
path tree. At this point, the media/ and static/ URIs have already
|
||||
been added to the web root.
|
||||
|
||||
Args:
|
||||
web_root (twisted.web.resource.Resource): The root
|
||||
resource of the URI tree. Use .putChild() to
|
||||
add new subdomains to the tree.
|
||||
|
||||
Returns:
|
||||
web_root (twisted.web.resource.Resource): The potentially
|
||||
modified root structure.
|
||||
|
||||
Example:
|
||||
from twisted.web import static
|
||||
my_page = static.File("web/mypage/")
|
||||
my_page.indexNames = ["index.html"]
|
||||
web_root.putChild("mypage", my_page)
|
||||
|
||||
"""
|
||||
return web_root
|
||||
|
||||
|
||||
def at_webproxy_root_creation(web_root):
|
||||
"""
|
||||
This function can modify the portal proxy service.
|
||||
Args:
|
||||
web_root (evennia.server.webserver.Website): The Evennia
|
||||
Website application. Use .putChild() to add new
|
||||
subdomains that are Portal-accessible over TCP;
|
||||
primarily for new protocol development, but suitable
|
||||
for other shenanigans.
|
||||
"""
|
||||
return web_root
|
15
server/logs/README.md
Normal file
15
server/logs/README.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
This directory contains Evennia's log files. The existence of this README.md file is also necessary
|
||||
to correctly include the log directory in git (since log files are ignored by git and you can't
|
||||
commit an empty directory).
|
||||
|
||||
- `server.log` - log file from the game Server.
|
||||
- `portal.log` - log file from Portal proxy (internet facing)
|
||||
|
||||
Usually these logs are viewed together with `evennia -l`. They are also rotated every week so as not
|
||||
to be too big. Older log names will have a name appended by `_month_date`.
|
||||
|
||||
- `lockwarnings.log` - warnings from the lock system.
|
||||
- `http_requests.log` - this will generally be empty unless turning on debugging inside the server.
|
||||
|
||||
- `channel_<channelname>.log` - these are channel logs for the in-game channels They are also used
|
||||
by the `/history` flag in-game to get the latest message history.
|
15
typeclasses/README.md
Normal file
15
typeclasses/README.md
Normal 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
0
typeclasses/__init__.py
Normal file
104
typeclasses/accounts.py
Normal file
104
typeclasses/accounts.py
Normal 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
62
typeclasses/channels.py
Normal 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
80
typeclasses/characters.py
Normal 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
81
typeclasses/effects.py
Normal 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
171
typeclasses/exits.py
Normal 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
|
41
typeclasses/mob_actions.py
Normal file
41
typeclasses/mob_actions.py
Normal 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
47
typeclasses/mobs.py
Normal 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
264
typeclasses/objects.py
Normal 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
287
typeclasses/rooms.py
Normal 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
164
typeclasses/scripts.py
Normal 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
0
utils/__init__.py
Normal file
63
utils/building.py
Normal file
63
utils/building.py
Normal 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
1109
utils/crafting.py
Normal file
File diff suppressed because it is too large
Load diff
71
utils/priodict.py
Normal file
71
utils/priodict.py
Normal 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
120
utils/spath.py
Normal 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
34
utils/utils.py
Normal 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
0
web/__init__.py
Normal file
13
web/static_overrides/README.md
Normal file
13
web/static_overrides/README.md
Normal 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/`
|
3
web/static_overrides/webclient/css/README.md
Normal file
3
web/static_overrides/webclient/css/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
You can replace the CSS files for Evennia's webclient here.
|
||||
|
||||
You can find the original files in `evennia/web/static/webclient/css/`
|
3
web/static_overrides/webclient/js/README.md
Normal file
3
web/static_overrides/webclient/js/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
You can replace the javascript files for Evennia's webclient page here.
|
||||
|
||||
You can find the original files in `evennia/web/static/webclient/js/`
|
3
web/static_overrides/website/css/README.md
Normal file
3
web/static_overrides/website/css/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
You can replace the CSS files for Evennia's homepage here.
|
||||
|
||||
You can find the original files in `evennia/web/static/website/css/`
|
3
web/static_overrides/website/images/README.md
Normal file
3
web/static_overrides/website/images/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
You can replace the image files for Evennia's home page here.
|
||||
|
||||
You can find the original files in `evennia/web/static/website/images/`
|
4
web/template_overrides/README.md
Normal file
4
web/template_overrides/README.md
Normal 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.
|
3
web/template_overrides/webclient/README.md
Normal file
3
web/template_overrides/webclient/README.md
Normal 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/`
|
7
web/template_overrides/website/README.md
Normal file
7
web/template_overrides/website/README.md
Normal 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/`
|
3
web/template_overrides/website/flatpages/README.md
Normal file
3
web/template_overrides/website/flatpages/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
Flatpages require a default.html template, which can be overwritten by placing it in this folder.
|
||||
|
||||
You can find the original files in `evennia/web/website/templates/website/flatpages/`
|
3
web/template_overrides/website/registration/README.md
Normal file
3
web/template_overrides/website/registration/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
The templates involving login/logout can be overwritten here.
|
||||
|
||||
You can find the original files in `evennia/web/website/templates/website/registration/`
|
18
web/urls.py
Normal file
18
web/urls.py
Normal 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
10
world/README.md
Normal 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
0
world/__init__.py
Normal file
26
world/batch_cmds.ev
Normal file
26
world/batch_cmds.ev
Normal file
|
@ -0,0 +1,26 @@
|
|||
#
|
||||
# A batch-command file is a way to build a game world
|
||||
# in a programmatic way, by placing a sequence of
|
||||
# build commands after one another. This allows for
|
||||
# using a real text editor to edit e.g. descriptions
|
||||
# rather than entering text on the command line.
|
||||
#
|
||||
# A batch-command file is loaded with @batchprocess in-game:
|
||||
#
|
||||
# @batchprocess[/interactive] tutorial_examples.batch_cmds
|
||||
#
|
||||
# A # as the first symbol on a line begins a comment and
|
||||
# marks the end of a previous command definition. This is important,
|
||||
# - every command must be separated by at least one line of comment.
|
||||
#
|
||||
# All supplied commands are given as normal, on their own line
|
||||
# and accept arguments in any format up until the first next
|
||||
# comment line begins. Extra whitespace is removed; an empty
|
||||
# line in a command definition translates into a newline.
|
||||
#
|
||||
# See `evennia/contrib/tutorial_examples/batch_cmds.ev` for
|
||||
# an example of a batch-command code. See also the batch-code
|
||||
# system for loading python-code this way.
|
||||
#
|
||||
|
||||
|
115
world/batches/init.ev
Normal file
115
world/batches/init.ev
Normal 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
219
world/prototypes.py
Normal 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
23
world/recipes_base.py
Normal 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
46
world/spells.py
Normal 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))
|
Loading…
Reference in a new issue