From 94ceec3719868dba6f28df51ecfb6a472cc44b90 Mon Sep 17 00:00:00 2001 From: Greg Taylor Date: Mon, 4 Jun 2007 20:01:03 +0000 Subject: [PATCH] Scripting support is now in! See cmd_look (the end of it), scripthandler.py, and scripts/basicobject.py for very brief examples. I'm not sure how well this is going to scale, I had to kludge the import a bit due to some oddities with __import__. There has to be a better way to do this, hopefully I'll be able to figure it out. In any case, expect basicobject to start fleshing out. You'll be able to use it directly or sub-class it with your own stuff. --- apps/objects/models.py | 17 +++++++++++++++ commands/general.py | 3 +++ scripthandler.py | 49 ++++++++++++++++++++++++++++++++++-------- scripts/basicobject.py | 31 +++++++++++++++++++++++++- server.py | 1 + 5 files changed, 91 insertions(+), 10 deletions(-) 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":