diff --git a/README b/README index c2d29a2f37..29988ae2c6 100644 --- a/README +++ b/README @@ -5,6 +5,7 @@ Evennia README http://evennia.com - < 2010 (earlier revisions) - May 2010 - merged ABOUT and README. Added Current status /Griatch - Aug 2010 - evennia devel merged into trunk /Griatch +- May 2011 - all commands implemented, web client, contribs /Griatch Contents: --------- @@ -23,21 +24,17 @@ Evennia Alpha SVN version About Evennia ------------- -Evennia is a proof-of-concept MU* server that aims to provide a functional -bare-bones base for developers. While there are quite a few codebases that do the same -(and very well in many cases), we are taking a unique spin on the problem. -Some of our flagship features include (or will one day include): -* Coded fully in Python using Django and Twisted -* Extensive web integration. -* The ability to build/administer through a web browser. -* Shared accounts between the website and the game. -* Optional web-based character creation. +Evennia is a MUD/MUX/MU* server that aims to provide a functional +bare-bones base for developers. Some of our main features are: + +* Coded and extended using normal Python modules. +* Extensive web integration due to our use of Django. +* Runs its own Twisted webserver. Comes with game website and ajax web-browser mud client. +* Extensive current and potential connectivity and protocol-support through Twisted. * Extremely easy-to-manipulate SQL database back-end via Django (djangoproject.com) -* Simple and easily extensible design. -* Very granular permissions. Individual and group based. -* Powerful an extremely extendable base system +* Powerful an extremely extendable bare-bones base system The essential points here are the web integration and the SQL backing via Django. The Django framework has database abstraction abilities that give us @@ -60,6 +57,14 @@ See the INSTALL file for help on setting up and running Evennia. Current Status -------------- + +May 2011: +The new version of Evennia, originally hitting trunk in Aug2010, is +maturing. All commands from the pre-Aug version, including IRC/IMC2 +support works again. An ajax web-client was added earlier in the year, +including moving Evennia to be its own webserver (no more need for +Apache or django-testserver). Contrib-folder added. + Aug 2010: Evennia-griatch-branch is ready for merging with trunk. This marks a rather big change in the inner workings of the server, but should @@ -77,17 +82,16 @@ to Events, Commands and Permissions. Contact, Support and Development ----------------------- -We are early in development, but we try to give support best we can -if you feel daring enough to play with the codebase. Make a post to -the mailing list or chat us up on IRC if you have questions. We also -have a bug tracker if you want to report bugs. Finally, if -you are willing to help with the code work, we much appreciate all help! -Visit either of the following resources: +This is still alpha software, but we try to give support best we can +if you have questions. Make a post to the mailing list or chat us up +on IRC. We also have a bug tracker if you want to report +bugs. Finally, if you are willing to help with the code work, we much +appreciate all help! Visit either of the following resources: * Evennia Webpage http://evennia.com -* Evennia wiki (documentation) +* Evennia manual (wiki) http://code.google.com/p/evennia/wiki/Index * Evennia Code Page (See INSTALL text for installation) @@ -107,8 +111,10 @@ evennia | |___(engine-related dirs) | |_______game (start the server) - |___gamesrc - |___(game-related dirs) + | |___gamesrc + | |___(game-related dirs) + | + |_______contrib The two main directories you will spend most of your time in are src/ and game/ (probably mostly game/). @@ -129,6 +135,11 @@ to make your dream game. game/ contains the main server settings and the actual evennia executable to start things. game/gamesrc/ holds all the templates for creating objects in your virtual world. +contrib/ contains optional code snippets. These are potentially useful +but deemed to be too game-specific to be part of the server itself. +Modules in contrib are not used unless you yourself decide to import +and use them. + With this little first orientation, you should head into the online Evennia wiki documentation to get going with the codebase. @@ -140,18 +151,15 @@ for capable admins to craft their own respective games. It is not the intention to provide a full-fledged, ready-to-run base, rather Evennia is offering the means to make such games. -2) Development of games on Evennia must be easy for anyone with some degree -of Python experience. Building needs to be easy, and per-room, per-object, -and environmental customizations need to be simple to do. +2) Development of games on Evennia must be easy for anyone with some +degree of Python experience. Building needs to be easy, and per-room, +per-object, and environmental customizations need to be simple to +do. This is handled by use of normal Python classes transparently +abstracting and wrapping the SQL backend. The user should not need to +use SQL or even know Django to any greater extent. 3) The server must utilize SQL as a storage back-end to allow for web->game -integration. See the details on Django later on in the document for more -details. - -4) Any and all game-specific configuration must reside in SQL, not -external configuration files. The only exception is the settings.py file -containing the SQL information. - +integration through Django. The Components -------------- diff --git a/game/gamesrc/scripts/examples/bodyfunctions.py b/game/gamesrc/scripts/examples/bodyfunctions.py index abad6dfd73..c4c0c3d73a 100644 --- a/game/gamesrc/scripts/examples/bodyfunctions.py +++ b/game/gamesrc/scripts/examples/bodyfunctions.py @@ -14,7 +14,6 @@ or you won't see any messages! import random from game.gamesrc.scripts.basescript import Script - class BodyFunctions(Script): """ This class defines the script itself @@ -24,7 +23,7 @@ class BodyFunctions(Script): self.key = "bodyfunction" self.desc = "Adds various timed events to a character." self.interval = 20 # seconds - self.start_delay # wait self.interval until first call + self.start_delay = True # wait self.interval until first call self.persistent = False def at_repeat(self): diff --git a/game/manage.py b/game/manage.py index 7784e23620..512edb8af6 100755 --- a/game/manage.py +++ b/game/manage.py @@ -40,7 +40,7 @@ if not os.path.exists('settings.py'): # this file. Try to *only* copy over things you really need to customize # and do *not* make any changes to src/settings_default.py directly. # This way you'll always have a sane default to fall back on -# (also, the master file may change with server updates). +# (also, the master config file may change with server updates). # from src.settings_default import * @@ -62,7 +62,7 @@ from src.settings_default import * ################################################### ################################################### -# Default Object typeclasses +# Typeclasses ################################################### ################################################### @@ -90,7 +90,7 @@ from src.settings_default import * ################################################### ################################################### -# Evennia components (django apps) +# Evennia components ################################################### """ diff --git a/src/commands/default/building.py b/src/commands/default/building.py index f461016a7d..54c45b5d24 100644 --- a/src/commands/default/building.py +++ b/src/commands/default/building.py @@ -386,19 +386,8 @@ class CmdCreate(ObjManipCommand): aliases = objdef['aliases'] typeclass = objdef['option'] - # analyze typeclass. If it starts at the evennia basedir, - # (i.e. starts with game or src) we let it be, otherwise we - # add a base path as defined in settings - if typeclass and not (typeclass.startswith('src.') or - typeclass.startswith('game.') or - typeclass.startswith('contrib')): - - typeclass = "%s.%s" % (settings.BASE_TYPECLASS_PATH, - typeclass) - # create object (if not a valid typeclass, the default # object typeclass will automatically be used) - lockstring = "control:id(%s);examine:perm(Builders);delete:id(%s) or perm(Wizards);get:all()" % (caller.id, caller.id) obj = create.create_object(typeclass, name, caller, home=caller, aliases=aliases, locks=lockstring) @@ -420,7 +409,6 @@ class CmdCreate(ObjManipCommand): caller.msg(string) -#TODO: make @debug more clever with arbitrary hooks? class CmdDebug(MuxCommand): """ Debug game entities @@ -438,11 +426,7 @@ class CmdDebug(MuxCommand): @debug/obj examples.red_button.RedButton This command helps when debugging the codes of objects and scripts. - It creates the given object and runs tests on its hooks. You can - supply both full paths (starting from the evennia base directory), - otherwise the system will start from the defined root directory - for scripts and objects respectively (defined in settings file). - + It creates the given object and runs tests on its hooks. """ key = "@debug" @@ -459,27 +443,11 @@ class CmdDebug(MuxCommand): path = self.args if 'obj' in self.switches or 'object' in self.switches: - # analyze path. If it starts at the evennia basedir, - # (i.e. starts with game or src) we let it be, otherwise we - # add a base path as defined in settings - if path and not (path.startswith('src.') or - path.startswith('game.')): - path = "%s.%s" % (settings.BASE_TYPECLASS_PATH, - path) - # create and debug the object self.caller.msg(debug.debug_object(path, self.caller)) self.caller.msg(debug.debug_object_scripts(path, self.caller)) elif 'script' in self.switches: - # analyze path. If it starts at the evennia basedir, - # (i.e. starts with game or src) we let it be, otherwise we - # add a base path as defined in settings - if path and not (path.startswith('src.') or - path.startswith('game.')): - path = "%s.%s" % (settings.BASE_SCRIPT_PATH, - path) - self.caller.msg(debug.debug_syntax_script(path)) @@ -629,16 +597,7 @@ class CmdDig(ObjManipCommand): if not typeclass: typeclass = settings.BASE_ROOM_TYPECLASS - # analyze typeclass path. If it starts at the evennia basedir, - # (i.e. starts with game or src) we let it be, otherwise we - # add a base path as defined in settings - if typeclass and not (typeclass.startswith('src.') or - typeclass.startswith('game.')): - typeclass = "%s.%s" % (settings.BASE_TYPECLASS_PATH, - typeclass) - # create room - lockstring = "control:id(%s) or perm(Immortal); delete:id(%s) or perm(Wizard); edit:id(%s) or perm(Wizard)" lockstring = lockstring % (caller.dbref, caller.dbref, caller.dbref) @@ -669,13 +628,7 @@ class CmdDig(ObjManipCommand): typeclass = to_exit["option"] if not typeclass: typeclass = settings.BASE_EXIT_TYPECLASS - # analyze typeclass. If it starts at the evennia basedir, - # (i.e. starts with game or src) we let it be, otherwise we - # add a base path as defined in settings - if typeclass and not (typeclass.startswith('src.') or - typeclass.startswith('game.')): - typeclass = "%s.%s" % (settings.BASE_TYPECLASS_PATH, - typeclass) + new_to_exit = create.create_object(typeclass, to_exit["name"], location, aliases=to_exit["aliases"], locks=lockstring, destination=new_room) @@ -701,13 +654,6 @@ class CmdDig(ObjManipCommand): typeclass = back_exit["option"] if not typeclass: typeclass = settings.BASE_EXIT_TYPECLASS - # analyze typeclass. If it starts at the evennia basedir, - # (i.e. starts with game or src) we let it be, otherwise we - # add a base path as defined in settings - if typeclass and not (typeclass.startswith('src.') or - typeclass.startswith('game.')): - typeclass = "%s.%s" % (settings.BASE_TYPECLASS_PATH, - typeclass) new_back_exit = create.create_object(typeclass, back_exit["name"], new_room, aliases=back_exit["aliases"], locks=lockstring, destination=location) @@ -1068,14 +1014,6 @@ class CmdOpen(ObjManipCommand): exit_aliases = self.lhs_objs[0]['aliases'] exit_typeclass = self.lhs_objs[0]['option'] - # analyze typeclass. If it starts at the evennia basedir, - # (i.e. starts with game or src) we let it be, otherwise we - # add a base path as defined in settings - if exit_typeclass and not (exit_typeclass.startswith('src.') or - exit_typeclass.startswith('game.')): - exit_typeclass = "%s.%s" % (settings.BASE_TYPECLASS_PATH, - exit_typeclass) - dest_name = self.rhs_objs[0]['name'] # first, check so the destination exists. @@ -1097,13 +1035,6 @@ class CmdOpen(ObjManipCommand): back_exit_aliases = self.rhs_objs[1]['name'] back_exit_typeclass = self.rhs_objs[1]['option'] - # analyze typeclass. If it starts at the evennia basedir, - # (i.e. starts with game or src) we let it be, otherwise we - # add a base path as defined in settings - if back_exit_typeclass and not (back_exit_typeclass.startswith('src.') or - back_exit_typeclass.startswith('game.')): - back_exit_typeclass = "%s.%s" % (settings.BASE_TYPECLASS_PATH, - back_exit_typeclass) # Create the back-exit self.create_exit(back_exit_name, destination, location, back_exit_aliases, back_exit_typeclass) @@ -1250,14 +1181,6 @@ class CmdTypeclass(MuxCommand): # we have an =, a typeclass was supplied. typeclass = self.rhs - # analyze typeclass. If it starts at the evennia basedir, - # (i.e. starts with game or src) we let it be, otherwise we - # add a base path as defined in settings - if typeclass and not (typeclass.startswith('src.') or - typeclass.startswith('game.')): - typeclass = "%s.%s" % (settings.BASE_TYPECLASS_PATH, - typeclass) - if not obj.access(caller, 'edit'): caller.msg("You are not allowed to do that.") return @@ -1827,34 +1750,38 @@ class CmdScript(MuxCommand): caller.msg(string) return - inp = self.rhs - if not inp.startswith('src.') and not inp.startswith('game.'): - # append the default path. - inp = "%s.%s" % (settings.BASE_SCRIPT_PATH, inp) - obj = caller.search(self.lhs) if not obj: return + string = "" - if "stop" in self.switches: - # we are stopping an already existing script - ok = obj.scripts.stop(inp) - if not ok: - string = "Script %s could not be stopped. Does it exist?" % inp - else: - string = "Script stopped and removed from object." - if "start" in self.switches: - # we are starting an already existing script - ok = obj.scripts.start(inp) - if not ok: - string = "Script %s could not be (re)started." % inp - else: - string = "Script started successfully." if not self.switches: # adding a new script, and starting it - ok = obj.scripts.add(inp, autostart=True) + ok = obj.scripts.add(self.rhs, autostart=True) if not ok: - string = "Script %s could not be added." % inp + string += "\nScript %s could not be added." % self.rhs else: string = "Script successfully added and started." - caller.msg(string) + + else: + paths = [self.rhs] + ["%s.%s" % (prefix, self.rhs) + for prefix in settings.SCRIPT_TYPECLASS_PATHS] + if "stop" in self.switches: + # we are stopping an already existing script + for path in paths: + ok = obj.scripts.stop(path) + if not ok: + string += "\nScript %s could not be stopped. Does it exist?" % path + else: + string = "Script stopped and removed from object." + break + if "start" in self.switches: + # we are starting an already existing script + for path in paths: + ok = obj.scripts.start(path) + if not ok: + string += "\nScript %s could not be (re)started." % path + else: + string = "Script started successfully." + break + caller.msg(string.strip()) diff --git a/src/locks/lockfuncs.py b/src/locks/lockfuncs.py index 0de09f7239..21fba3d0ae 100644 --- a/src/locks/lockfuncs.py +++ b/src/locks/lockfuncs.py @@ -420,7 +420,7 @@ def serversetting(accessing_obj, accessed_obj, *args, **kwargs): Usage: serversetting(IRC_ENABLED) - serversetting(BASE_SCRIPT_PATH, game.gamesrc.scripts) + serversetting(BASE_SCRIPT_PATH, [game.gamesrc.scripts]) A given True/False or integers will be converted properly. """ diff --git a/src/objects/models.py b/src/objects/models.py index 2ee90ae2ef..21395c23fd 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -418,9 +418,10 @@ class ObjectDB(TypedObject): # ObjectDB class access methods/properties # - # this is required to properly handle attributes + # this is required to properly handle attributes and typeclass loading. attribute_model_path = "src.objects.models" attribute_model_name = "ObjAttribute" + typeclass_paths = settings.OBJECT_TYPECLASS_PATHS # this is used by all typedobjects as a fallback try: diff --git a/src/players/models.py b/src/players/models.py index 6177c471af..f68534f573 100644 --- a/src/players/models.py +++ b/src/players/models.py @@ -261,9 +261,10 @@ class PlayerDB(TypedObject): except Exception: default_typeclass_path = "src.players.player.Player" - # this is required to properly handle attributes + # this is required to properly handle attributes and typeclass loading attribute_model_path = "src.players.models" attribute_model_name = "PlayerAttribute" + typeclass_paths = settings.PLAYER_TYPECLASS_PATHS # name property (wraps self.user.username) #@property diff --git a/src/scripts/models.py b/src/scripts/models.py index da71619a9b..5ad7534f4c 100644 --- a/src/scripts/models.py +++ b/src/scripts/models.py @@ -243,9 +243,10 @@ class ScriptDB(TypedObject): # # - # this is required to properly handle typeclass-dependent attributes + # this is required to properly handle attrubutes and typeclass loading attribute_model_path = "src.scripts.models" attribute_model_name = "ScriptAttribute" + typeclass_paths = settings.SCRIPT_TYPECLASS_PATHS # this is used by all typedobjects as a fallback try: diff --git a/src/settings_default.py b/src/settings_default.py index f7d5bd38a4..98d9702e11 100644 --- a/src/settings_default.py +++ b/src/settings_default.py @@ -161,20 +161,19 @@ CMDSET_DEFAULT = "game.gamesrc.commands.basecmdset.DefaultCmdSet" CMDSET_OOC = "game.gamesrc.commands.basecmdset.OOCCmdSet" ################################################### -# Default Object typeclasses +# Typeclasses ################################################### -# Note that all typeclasses must originally -# inherit from src.objects.objects.Object somewhere in -# their path. +# Base paths for typeclassed object classes. These paths must be +# defined relative evennia's root directory. They will be searched in +# order to find relative typeclass paths. +OBJECT_TYPECLASS_PATHS = ["game.gamesrc.objects", "game.gamesrc.objects.examples"] +SCRIPT_TYPECLASS_PATHS = ["game.gamesrc.scripts", "game.gamesrc.scripts.examples"] +PLAYER_TYPECLASS_PATHS = ["game.gamesrc.objects"] -# This sets the default base dir to search when importing -# things, so one doesn't have to write the entire -# path in-game. -BASE_TYPECLASS_PATH = "game.gamesrc.objects" # Typeclass for player objects (linked to a character) (fallback) BASE_PLAYER_TYPECLASS = "game.gamesrc.objects.baseobjects.Player" -# Typeclass and base for all following objects (fallback) +# Typeclass and base for all objects (fallback) BASE_OBJECT_TYPECLASS = "game.gamesrc.objects.baseobjects.Object" # Typeclass for character objects linked to a player (fallback) BASE_CHARACTER_TYPECLASS = "game.gamesrc.objects.baseobjects.Character" @@ -183,14 +182,6 @@ BASE_ROOM_TYPECLASS = "game.gamesrc.objects.baseobjects.Room" # Typeclass for Exit objects (fallback) BASE_EXIT_TYPECLASS = "game.gamesrc.objects.baseobjects.Exit" -################################################### -# Scripts -################################################### - -# Python path to a directory to start searching -# for scripts. -BASE_SCRIPT_PATH = "game.gamesrc.scripts" - ################################################### # Batch processors ################################################### diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 14d51c2176..21fce760c0 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -561,6 +561,7 @@ class TypedObject(SharedMemoryModel): # attribute model (ObjAttribute, PlayerAttribute etc). attribute_model_path = "src.typeclasses.models" attribute_model_name = "Attribute" + typeclass_paths = settings.OBJECT_TYPECLASS_PATHS def __eq__(self, other): return other and hasattr(other, 'id') and self.id == other.id @@ -647,20 +648,31 @@ class TypedObject(SharedMemoryModel): defpath = object.__getattribute__(self, 'default_typeclass_path') typeclass = object.__getattribute__(self, '_path_import')(defpath) #typeclass = self._path_import(defpath) - else: - typeclass = TYPECLASS_CACHE.get(path, None) - if typeclass: - # we've imported this before. We're done. - return typeclass - # not in cache. Import anew. - typeclass = object.__getattribute__(self, "_path_import")(path) - if not callable(typeclass): - # given path failed to import, fallback to default. - errstring = " %s" % typeclass # this is an error message - if hasattr(typeclass, '__file__'): - errstring += "\nThis seems to be just the path to a module. You need" + else: + # handle loading/importing of typeclasses, searching all paths. + # (self.typeclss_paths is a shortcut to settings.TYPECLASS_*_PATH + # where '*' is either OBJECT, SCRIPT or PLAYER depending on the typed + # object). + typeclass_paths = [path] + ["%s.%s" % (prefix, path) for prefix in self.typeclass_paths] + for tpath in typeclass_paths: + # try to find any matches to the typeclass path, in all possible permutations.. + typeclass = TYPECLASS_CACHE.get(tpath, None) + if typeclass: + # we've imported this before. We're done. + return typeclass + # not in cache. Try to import anew. + typeclass = object.__getattribute__(self, "_path_import")(tpath) + if callable(typeclass): + # don't return yet, we must cache this further down. + break + elif hasattr(typeclass, '__file__'): + errstring += "\n%s seems to be just the path to a module. You need" % tpath errstring += " to specify the actual typeclass name inside the module too." - errstring += "\n Typeclass '%s' failed to load." % path + else: + errstring += "\n%s" % typeclass # this will hold an error message. + + if not callable(typeclass): + # Still not a valid import. Fallback to default. defpath = object.__getattribute__(self, "default_typeclass_path") errstring += " Using Default class '%s'." % defpath self.db_typeclass_path = defpath @@ -669,7 +681,7 @@ class TypedObject(SharedMemoryModel): typeclass = object.__getattribute__(self, "_path_import")(defpath) errmsg(errstring) if not callable(typeclass): - # if typeclass still doesn't exist, we're in trouble. + # if typeclass still doesn't exist at this point, we're in trouble. # fall back to hardcoded core class. errstring = " %s\n%s" % (typeclass, errstring) errstring += " Default class '%s' failed to load." % defpath diff --git a/src/typeclasses/typeclass.py b/src/typeclasses/typeclass.py index c758046227..5dfdf8784b 100644 --- a/src/typeclasses/typeclass.py +++ b/src/typeclasses/typeclass.py @@ -45,7 +45,6 @@ class MetaTypeClass(type): def __str__(cls): return "%s" % cls.__name__ - class TypeClass(object): """ This class implements a 'typeclass' object. This is connected