diff --git a/apps/objects/models.py b/apps/objects/models.py index fc68eb639b..7c8c65e8c4 100755 --- a/apps/objects/models.py +++ b/apps/objects/models.py @@ -1,6 +1,9 @@ import re + from django.db import models from django.contrib.auth.models import User, Group + +import scripthandler import defines_global import ansi @@ -95,6 +98,7 @@ class Object(models.Model): flags = models.TextField(blank=True, null=True) nosave_flags = models.TextField(blank=True, null=True) date_created = models.DateField(editable=False, auto_now_add=True) + scriptlink = None def __cmp__(self, other): """ @@ -544,6 +548,19 @@ class Object(models.Model): functions_general.log_errmsg("Object '%s(#%d)' has invalid location: #%s" % (self.name,self.id,self.location_id)) return False + def get_scriptlink(self): + """ + Returns an object's script parent. + """ + if not self.scriptlink: + self.scriptlink = scripthandler.scriptlink(self, self.get_attribute_value('__parent', 'basicobject')) + + if self.scriptlink: + # If the scriptlink variable can't be populated, this will fail + # silently and let the exception hit in the scripthandler. + return self.scriptlink + return None + def get_attribute_value(self, attrib, default=False): """ Returns the value of an attribute on an object. You may need to diff --git a/commands/general.py b/commands/general.py index 0d7a50b466..7564f24602 100644 --- a/commands/general.py +++ b/commands/general.py @@ -143,6 +143,9 @@ def cmd_look(cdat): for exit in con_exits: session.msg('%s' %(exit.get_name(),)) + # SCRIPT: Call the object's script's a_desc() method. + target_obj.get_scriptlink().a_desc(pobject) + def cmd_get(cdat): """ Get an object and put it in a player's inventory. diff --git a/scripthandler.py b/scripthandler.py index 59c93420f2..456d6b1803 100644 --- a/scripthandler.py +++ b/scripthandler.py @@ -1,3 +1,7 @@ +import os +from traceback import format_exc + +import functions_general """ This module is responsible for managing scripts and their connection to the Object class model. It is important to keep this as independent from the @@ -9,27 +13,54 @@ interaction with actual script methods should happen via calls to Objects. # contain references to the associated module for each key. cached_scripts = {} -def scriptlink(scriptname): +def scriptlink(source_obj, scriptname): """ Each Object will refer to this function when trying to execute a function contained within a scripted module. For the sake of ease of management, modules are cached and compiled as they are requested and stored in the cached_scripts dictionary. + + Returns a reference to an instance of the script's class as per it's + class_factory() method. + + source_obj: (Object) A reference to the object being scripted. + scriptname: (str) Name of the module to load (minus 'scripts'). """ # The module is already cached, just return it rather than re-load. - retval = cached_scripts.get('scripts.%s' % (scriptname), False) + retval = cached_scripts.get(scriptname, False) if retval: - return retval - - modname = 'scripts.%s' % (scriptname) - print 'Caching script module %s.' % (modname) + return retval.class_factory(source_obj) + + # Store the original working directory so we can revert. + orig_path = os.getcwd() + + # Split the script name up by periods to give us the directory we need + # to change to. I really wish we didn't have to do this, but there's some + # strange issue with __import__ and more than two directories worth of + # nesting. + path_split = scriptname.split('.') + newpath_str = '/'.join(path_split[:-1]) + # Lop the module name off the end. + modname = path_split[-1] try: + # Change the working directory to the location of the script and import. + os.chdir('scripts/%s/' % (newpath_str)) + functions_general.log_infomsg("SCRIPT: Caching and importing %s." % (modname)) modreference = __import__(modname) - cached_scripts[modname] = modreference + # Store the module reference for later fast retrieval. + cached_scripts[scriptname] = modreference except ImportError: - print 'Error importing %s.' % (modname) + functions_general.log_infomsg('Error importing %s: %s' % (modname, format_exc())) + os.chdir(orig_path) return + except OSError: + functions_general.log_infomsg('Invalid module path: %s' % (format_exc())) + os.chdir(orig_path) + return + finally: + # Change back to the original working directory. + os.chdir(orig_path) # The new script module has been cached, return the reference. - return modreference \ No newline at end of file + return modreference.class_factory(source_obj) \ No newline at end of file diff --git a/scripts/basicobject.py b/scripts/basicobject.py index 2d0a4632fc..2130d91dc0 100644 --- a/scripts/basicobject.py +++ b/scripts/basicobject.py @@ -2,4 +2,33 @@ This will be the base object type/interface that all scripts are derived from by default. It will have the necessary outline for developers to sub-class and override. """ -test = 123 + +class BasicObject: + def __init__(self, source_obj): + """ + Get our ducks in a row. + + source_obj: (Object) A reference to the object being scripted (the child). + """ + self.source_obj = source_obj + + def a_desc(self, looker): + """ + Perform this action when someone uses the LOOK command on the object. + + looker: (Object) Reference to the looker + """ + # Un-comment the line below for an example + #print "SCRIPT TEST: %s looked at %s." % (looker, self.source_obj) + pass + +def class_factory(source_obj): + """ + This method is called any script you retrieve (via the scripthandler). It + creates an instance of the class and returns it transparently. I'm not + sure how well this will scale, but we'll find out. We may need to + re-factor this eventually. + + source_obj: (Object) A reference to the object being scripted (the child). + """ + return BasicObject(source_obj) \ No newline at end of file diff --git a/server.py b/server.py index 3feee4ec4f..5fbd3cf39b 100755 --- a/server.py +++ b/server.py @@ -25,6 +25,7 @@ class EvenniaService(service.Service): log.startLogging(open(settings.LOGFILE, 'w')) self.cmd_alias_list = {} self.game_running = True + sys.path.append('.') # Database-specific startup optimizations. if settings.DATABASE_ENGINE == "sqlite3":