#----------------------------------------------------------------------
#	poo.py				JJS 05/20/99
#
#	This is the main module for POO (Pythonic MOO).
#	See: http://www.strout.net/python/poo/
#----------------------------------------------------------------------

import string
import pickle
import sys
import os
from types import *
from tostr import *
from time import time
import copy
import marksub
import msg
from pooparse import HandleCommand, CmdDef

gObjlist = {}		# global dictionary of POO objects
gHighID = 0			# highest ID used
gProps = {}			# maps an object to a property dictionary
gCmdDefs = {}		# maps a verb to a sequence of matching commands
gContents = {}		# maps an object to a list of contents
gCmdLine = ''		# copy of command typed by the user
gSafeGlobals = {}	# "safe" globals for user's environment
gLocals = {}		# maps user object to a dict of local vars
gUpdateList = []	# list of objects which want updates
gOwner = None		# object which owns current function (etc.)
gUser = None		# object who invoked the current command
gCallers = [None]	# sort of a traceback list of calling objects
gUnpickling = 0		# set to 1 when unpickling
gLastSave = time()	# time database was last saved
gFilePath = os.path.join(os.curdir,'poofiles') # path for user-accessible files
gTreasury = 100000	# credits for use by wizards

flushNoCR = 0		# set to 1 if should flush() after tell_NoCR
maxSaveTime = 1800	# maximum time between saves (seconds)
kObjValue = 90		# standard "value" of objects, in credits
kCreationTax = 10	# extra cost of creating an object -- goes to treasury
kRecycleTax = 40	# tax levied when recycling an object
kInbufLimit = 20	# maximum number of lines in "hear" buffer

#----------------------------------------------------------------------
# FUNCTION: copyList
#----------------------------------------------------------------------
def copyList( fromwhat, intowhat ):

	"""FUNCTION copyList(fromwhat, intowhat):
	This function copies one list into another, WITHOUT creating
	a new list instance.  This if you have A=B, and copy from C into B,
	A will also be changed.
	"""
	# LATER: check for list/tuple type; if not, coerce
	intowhat[:] = list(fromwhat)	# (thanks, Aaron!)

#----------------------------------------------------------------------
# CLASS: PooPickler
#----------------------------------------------------------------------

class PooPickler(pickle.Pickler):

	"""CLASS PooPickler:
	This class extends the normal Pickler object such that datatypes
	which can't by pickled are set to None, rather than crashing
	the server.
	"""

	# RADICALLY CHANGED on 4/20/98 to work with Python 1.5,
	# as per recommendation by our fearless leader (Guido)
	def save(self, object):
		try:
			pickle.Pickler.save(self, object)
		except pickle.PicklingError:
			pickle.Pickler.save(self, None)


#----------------------------------------------------------------------
# CLASS: Func
#----------------------------------------------------------------------
class Func:

	"""CLASS Func:
	This class implements a POO function.  Objects of this class walk
	like functions, but they are pickleable and do permission checks.
	"""
	
	#------------------------------------------------------------------
	# Func constructor
	#------------------------------------------------------------------
	def __init__(self,name='f',owner=None):
		
		"""Func constructor."""
		
		if gUnpickling: return
		name = string.replace(name,' ','')	
		attributes = {
			'owner':owner,		# function owner
			'name':name,		# function delaration (name and args)
			'x':1,				# if 0, can only be called as command
			'code':None,		# compiled code
			'source':[],		# source (list of strings)
			'presource':'',		# extra code before 'source'
			'desc':"",			# brief description
			'f':None,			# function defined by code
			'boundTo':None,		# POO object to be considered 'self'
			'definedOn':None }	# POO object where this Func was found
		for k in attributes.keys():
			self.__dict__[k] = attributes[k]
	
	#------------------------------------------------------------------
	# Func call method
	#------------------------------------------------------------------
	def __call__(self, *args):
		
		"""Func call method."""
		
		global gOwner, gUser, gCallers
		if not self.x:
			raise "PermError", self.name + " may only be called as a command"
		prevOwner = gOwner
		if self.owner:
			gOwner = self.owner
		if self.source and not self.code:
			self.compile()
		outval = None
		# prepare to attach "super" and other changables
		bdict = gSafeGlobals['__builtins__'].__dict__
		prevInfo = (bdict['super'],bdict['caller'])
		# update the stack of callers
		gCallers.append(self.boundTo)
		try:
			if self.code:
				# attach "super" and other automatic globals
				bdict['super'] = self.doSuper
				bdict['caller'] = gCallers[-2]
				bdict['user'] = gUser
				bdict['permHolder'] = gOwner
				# call the function created and stored in compile():
				if self.boundTo:
					# is this the most efficient way to do this?
					outval = apply(self.f, tuple( [self.boundTo]+list(args) ))
				else:
					outval = apply(self.f,args)
		finally:
			gCallers = gCallers[:-1]
			gOwner = prevOwner
			bdict['super'],bdict['caller'] = prevInfo
		gOwner = prevOwner
		return outval		

	#------------------------------------------------------------------
	# Func METHOD: compile
	#------------------------------------------------------------------
	def compile(self):
	
		"""Func METHOD: compile(self):
		compile self.source, store executable byte code."""
		
		if not self.source:
			self.__dict__['code'] = None
			return			
		pos = string.find(self.name,'(')
		paramdef = self.name[pos+1:-1]
		fullsource = 'def f(' + paramdef + '):\n ' \
			+ self.__dict__['presource'] + '\n ' \
			+ string.join(self.source,'\n ') + '\n'
		if gUnpickling:
			try: self.__dict__['code'] = compile(fullsource,'<'+self.name[:pos]+'>','exec')
			except: return
		else:
			self.__dict__['code'] = compile(fullsource,'<'+self.name[:pos]+'>','exec')
		# create a "dummy" function:
		locals = {'__builtins__':None}	# (is this necessary?)
		exec(self.code)	in gSafeGlobals,locals
		# copy (a reference to) the function for future use
		self.__dict__['f'] = locals['f']

	#------------------------------------------------------------------
	# Func METHOD: list
	#------------------------------------------------------------------
	def list(self, fromLine=1, toLine=99999):
	
		"""Func METHOD: list(self, fromLine=1, toLine=99999):
		list the source code [within the specified range]."""
		
		for line in self.source[fromLine-1:toLine]:
			print line

	#------------------------------------------------------------------
	# Func METHOD: arg
	#------------------------------------------------------------------
	def arg(self,num=0):
	
		"""Func METHOD: arg(self, num=0):
		find the specified argument name amoung self.name."""
		
		args = self.name[string.find(self.name,'(') +1 : \
			string.rfind(self.name,')') ]	
		try: return string.split(args,',')[num]
		except: return ''

	#------------------------------------------------------------------
	# Func conversion to string
	#------------------------------------------------------------------
	def __str__(self):
	
		"""Func conversion to string."""
		
		if self.boundTo: out = '' 	# str(self.boundTo) + '.'
		else: out = "unbound "
		if self.desc:
			out = out + self.name + " -- " + self.desc
		else:
			out = out + self.name
		return out

	#------------------------------------------------------------------
	# Func attribute setter
	#------------------------------------------------------------------
	def __setattr__(self,propname,value):
	
		"""Func attribute setter."""
	
		# owner can't be changed directly
		if gOwner and propname == 'owner':
			raise "PermError", "Can't directly change func owner"
		# other attributes can be changed only by owner
		if gOwner and not gOwner.wizard and gOwner != self.__dict__['owner']:
			raise "PermError", tostr(gOwner) \
				+ " can't modify " + propname + " of " + self.__str__()
		self.__dict__[propname] = value
		if propname == 'source':	# when source changes,
			self.code = None		# force a recompile
	
	#------------------------------------------------------------------
	# Func pickling support
	#------------------------------------------------------------------
	def __getstate__(self):
	
		"""Func pickling support."""
		
		temp = {}
		for key  in self.__dict__.keys():
			if key not in ['code','f']:
				temp[key] = self.__dict__[key]
		return temp

	#------------------------------------------------------------------
	# Func unpickling support
	#------------------------------------------------------------------
	def __setstate__(self,value):
	
		"""Func unpickling support."""
		
		for key in value.keys():
			self.__dict__[key] = value[key]
		self.compile()

	#------------------------------------------------------------------
	# Func METHOD: unbind
	#------------------------------------------------------------------
	def unbind(self):
	
		"""Func METHOD: unbind(self):
		set self.boundTo = None."""
		
		self.__dict__['boundTo'] = None
		return self

	#------------------------------------------------------------------
	# Func METHOD: doSuper
	#------------------------------------------------------------------
	def doSuper(self,*args):
	
		"""Func METHOD: doSuper(self,*args):
		Find the Func this overrides, and apply args to it.
		This is usually invoked via the 'super' variable."""
		
		func = self.findSuper()
		func.__dict__['boundTo'] = self.boundTo
		return apply(func,args)
		
	#------------------------------------------------------------------
	# Func METHOD: findSuper
	#------------------------------------------------------------------
	def findSuper(self):
	
		"""Func METHOD: findSuper(self):
		This method returns the Func which is apparently overridden
		by this one -- that is, it has the same name, but is defined
		further back in the ancestor chain.  Return it unbound.
		"""
			
		if not self.boundTo or type(self.boundTo)!=InstanceType:
			raise "UseError", "Func.findSuper() must be called on bound Func"
		for p in self.definedOn.parents:
			super = self.findSameName( p )
			if super: return super
		return None
	
	#------------------------------------------------------------------
	# Func METHOD: findSelf
	#------------------------------------------------------------------
	def findSameName(self,obj):

		"""Func METHOD: findSameName(self,obj):
		Given an object reference, find a property (on it or its
		ancestors) which refers to a Func with the same name
		as this Func.
		"""
		
		matches = filter(lambda x,y=self.name: \
			type(x.val) == InstanceType and \
			x.val.__class__ == Func and x.val.name == y, gProps[obj].values())
		if matches: return matches[0].val
		# not found here -- check ancestors
		for p in obj.parents:
			found = self.findSameName(p)
			if found: return found
		# still not found -- return None
		return None	

#----------------------------------------------------------------------
# CLASS: Prop
#----------------------------------------------------------------------
class Prop:

	"""CLASS Prop:
	This class implements a POO property.  These are used instead of
	normal Python attributes to give us full access control.
	"""
	
	#------------------------------------------------------------------
	# Prop constructor
	#------------------------------------------------------------------
	def __init__(self,owner=None,val=None):
	
		"""Prop constructor."""
		
		if gUnpickling: return		
		attributes = {
			'owner':owner,
			'val':val,
			'r':1,			# readable by others
			'w':0,			# writable by others
			'c':1 }			# ownership changes when inherited
		for k in attributes.keys():
			self.__dict__[k] = attributes[k]
		if type(val) == InstanceType and val.__class__ == Func:
			val.__dict__['owner'] = owner
	
	#------------------------------------------------------------------
	# Prop METHOD: permstr
	#------------------------------------------------------------------
	def permstr(self):
	
		"""Prop METHOD: permstr(self):
		return a string listing permissions as some combo of r,w,c,x."""
		
		perm = ''
		if self.r: perm = 'r'
		if self.w: perm = perm + 'w'
		if self.c: perm = perm + 'c'
		if type(self.val) == InstanceType and self.val.__class__ == Func \
			and self.val.x: perm = perm + 'x'
		return perm	
	
	#------------------------------------------------------------------
	# Prop conversion to string
	#------------------------------------------------------------------
	def __str__(self):
	
		"""Prop conversion to string."""
		
		return "Prop(owner="+str(self.owner)+", perm="+self.permstr()+")"
	
	#------------------------------------------------------------------
	# Prop attribute setter
	#------------------------------------------------------------------
	def __setattr__(self,propname,value):
	
		"""Prop attribute setter."""
		
		# property flags can only be changed by the owner or a wizard
		if gOwner and gOwner!=self.owner and not gOwner.wizard and \
		  (propname=='r' or propname=='w' or propname=='c'):
			raise "PermError", "Can't change property permissions"
		if propname=='x':
			raise "UseError", "'x' flag should be set on Func, not Prop"
			
		# ownership can only be changed by a wizard
		if gOwner and not gOwner.wizard and propname=='owner':
			raise "PermError", "Can't change property owner"
		self.__dict__[propname] = value

		# make sure owner of a function and its property are in sync
		# (unless we're unpickling, in which case it takes care of itself)
		if gUnpickling: return
		if propname == 'val' and type(value) == InstanceType \
		  and value.__class__ == Func:
			value.__dict__['owner'] = self.owner
		elif propname == 'owner' and type(self.val) == InstanceType \
		  and self.val.__class__ == Func:
			self.val.__dict__['owner'] = self.owner

#----------------------------------------------------------------------
# CLASS: Outfix
#----------------------------------------------------------------------
class Outfix:

	"""CLASS Outfix:
	This class is used to wrap an output stream so that output text
	is formatted to the user's preferences."""
	
	def __init__(self, out=sys.stdout, user=None):
		self.out = out
		self.user = user

	def write(self,s):
		try:
			outlines = marksub.marksub(s, \
					self.user.marksub,
					self.user.linelen)
		except:
			outlines = [s]
		try:
			sfx = self.user.linesuffix
			if type(sfx) != StringType:
				sfx = '\n'
				self.user.linesuffix = sfx
		except:
			sfx = '\n'

		self.out.write(string.join(outlines, sfx))

	def flush(self): self.out.flush()

	def close(self): self.out.close()

#----------------------------------------------------------------------
# CLASS: Obj
#----------------------------------------------------------------------
class Obj:

	"""CLASS: Obj
	This is the base class of any POO object -- rooms, users, widgets,
	etc.  It implements a 'multiple superinheritance' (see POO docs!).
	"""
	
	#------------------------------------------------------------------
	# Obj constructor
	#------------------------------------------------------------------
	def __init__(self,id=0,*parents):
	
		"""Obj constructor."""
		
		global gObjlist, gHighID, gProps
		if not gProps.has_key(self): gProps[self] = {}
		gContents[self] = []
		if gUnpickling: return
		if gOwner and not gOwner.wizard:
			raise "Hacker", "Hacking is grounds for termination of your account."
		if id and gObjlist.has_key(id):
			print "Error: object",id,"already exists."
			id = 0
		if id:
			gObjlist[id] = self
			if gHighID < id: gHighID = id
		attributes = {
			'__outstream': None,	# output stream
			'id': id,				# unique identifier
			'__isuser': 0,			# flag indicating a User object
			'_inbuf': [],			# input buffer
			'__value': kObjValue,	# creation value
			'__credits': 0,			# transferrable credits
			'__commands': {},		# commands defined on this object
			'parents': parents,		# parent objects
			'name': '',				# name of the object
			'aliases': (),			# name aliases
			'owner': self,			# object which controls this one
			'location': None,		# object that contains this one
			'wantsUpdates': 0,		# 1 if wants calls to update()
			'wizard': 0,			# 1 if has divine powers
			'programmer': 0,		# 1 if can use programmer commands
			'r': 1,					# 1 if publicly readable
			'w': 0,					# 1 if publicly writable
			'f': 0 }				# 1 if can have children ("fertile")
		for k in attributes.keys():
			self.__dict__[k] = attributes[k]
		
	#------------------------------------------------------------------
	# Obj conversion to string
	#------------------------------------------------------------------
	def __str__(self):
	
		"""Obj conversion to string."""
		
		return "#" + str(self.__dict__['id'])

	#------------------------------------------------------------------
	# Obj METHOD: tell
	#------------------------------------------------------------------
	def tell(self, *what):
	
		"""Obj METHOD: tell(self, *what):
		put the given string or list of strings in our auditory buffer.
		Terminate with a carriage return.
		"""
		
		bigstr = string.join(map(str,what),' ') + "\n"
		if self.__dict__['__outstream']:
			try:
				self.__dict__['__outstream'].write(bigstr)
				self.__dict__['__outstream'].flush()
				return
			except:
				# user appears to be disconnected; log them out
				self.Logout()
		
		# place in the input buffer (within INBUFLIMIT)
		if not callable(self.hear): return
		try:
			buf = self.__dict__['_inbuf']
		except:
			buf = []
			self.__dict__['_inbuf'] = buf
		if len(buf) >= kInbufLimit: return
		buf.append(bigstr)
		self.CheckUpdate()

	#------------------------------------------------------------------
	# Obj METHOD: tell_noCR
	#------------------------------------------------------------------
	def tell_noCR(self, *what):
	
		"""Obj METHOD: tell_noCR(self, *what):
		put the given string or list of strings in our auditory buffer.
		Do not terminate with a carriage return.
		"""
		
		bigstr = string.join(map(str,what),' ')
		if self.__dict__['__outstream']:
#			try:
				self.__dict__['__outstream'].write(bigstr)
				if flushNoCR:
					self.__dict__['__outstream'].flush()
				return
#			except:
				# user appears to be disconnected; log them out
				self.Logout()

		# place in the input buffer (within INBUFLIMIT)
		if not callable(self.hear): return
		try:
			buf = self.__dict__['_inbuf']
		except:
			buf = []
			self.__dict__['_inbuf'] = buf
		if len(buf) >= kInbufLimit: return
		buf.append(bigstr)
		self.CheckUpdate()

	#------------------------------------------------------------------
	# Obj METHOD: getprop
	#------------------------------------------------------------------
	def getprop(self,propname):
	
		"""Obj METHOD: getprop(self,propname):
		return the Prop defined on this object or an ancestor with
		the specified name.
		"""
		
		d = gProps[self]
		if propname in d.keys():
			prop = d[propname]
			# check read permissions
			# if no owner, assume it's (safe) POO code
			if not gOwner or \
				gOwner.__dict__['wizard'] or \
				prop.owner == gOwner or \
				prop.r:
					# we've found the property on self (noninherited)
					# return it as-is (make sure funcs are bound)
					if type(prop.val) == InstanceType and \
					   prop.val.__class__ == Func:
						prop.val.__dict__['boundTo'] = self
						prop.val.__dict__['definedOn'] = self
					return prop
			raise "PermError", "Can't read property "+propname
		for p in self.parents:
			prop = p.getprop(propname)
			if prop != None:
				# we've found it as an inherited value
				# if it's a function, return a COPY bound to this
				if type(prop.val) == InstanceType and \
				   prop.val.__class__ == Func:
				   	# do a quick copy of the Func, changing boundTo
					func2 = Func(prop.val.name,prop.val.owner)
					for i in prop.val.__dict__.keys():
						func2.__dict__[i] = prop.val.__dict__[i]
					func2.__dict__['boundTo'] = self
					func2.__dict__['definedOn'] = prop.val.definedOn
					# do a quick copy of the prop, pointing to Func
					prop2 = Prop(prop.owner, func2)
					prop2.__dict__['r'] = prop.__dict__['r']
					prop2.__dict__['w'] = prop.__dict__['w']
					prop2.__dict__['c'] = prop.__dict__['c']
					return prop2
				return prop
		return None

	#------------------------------------------------------------------
	# Obj attribute accessor
	#------------------------------------------------------------------
	def __getattr__(self,propname):
	
		"""Obj attribute accessor."""
		
		# note: don't perform inheritance on Python props
		if propname[:2] == '__':
			try: return self.__dict__[propname]
			except: raise AttributeError, propname
		prop = self.getprop(propname)
		if prop: return prop.val
		else: return None

	#------------------------------------------------------------------
	# Obj METHOD: canAddOrDeleteProp
	#------------------------------------------------------------------
	def canAddOrDeleteProp(self, propname):
	
		"""Obj METHOD: canAddOrDeleteProp(self, propname):
		return 1 if gOwner can add a property to this object
		"""

		if not propname: return 0
		if (propname[0] < 'a' or propname[0] > 'z') and \
		   (propname[0] < 'A' or propname[0] > 'Z'): return 0
		if ' ' in propname or '.' in propname: return 0
		return not gOwner \
		     or gOwner==self.owner \
		     or gOwner.wizard \
		     or self.w

	#------------------------------------------------------------------
	# Obj METHOD: canWriteProp
	#------------------------------------------------------------------
	def canWriteProp(self, propname, useCflag=1):
		
		"""Obj METHOD: canWriteProp(self, propname, useCflag=1):
		return 1 if gOwner can change the given property.
		"""

		# if no owner, assume it's (safe) POO code
		if not gOwner: return 1

		# python attributes can't safely be changed by anyone
		if len(propname) > 1 and propname[:2] == "__":
			return 0
		
		# location hierarchy can only be changed by move()
		if propname=='location':
			return gOwner==move
		
		# allow wizards to do anything
		if gOwner.wizard: return 1

		# check for wizard-only properties
		if propname=='owner' or \
		   propname=='programmer' or \
		   propname=='wizard' or \
		   propname[0] == '_': return 0		

		# built-in properties are only writable by the object owner
		if propname in self.__dict__.keys(): return gOwner == self.owner

		# check owner & permission bit on the property itself
		if gProps[self].has_key(propname):
			prop = gProps[self][propname]
			owner = prop.owner
		else:								# not defined here...
			prop = self.getprop(propname)	# inherited?
			if not prop: return self.canAddOrDeleteProp(propname)
			# if inherited, check flag "c"
			if prop.c and useCflag: owner = self.owner
			else: owner = prop.owner
		
		if owner != gOwner and not prop.w: return 0

		# if none of the above restrictions apply, must be OK
		return 1

	#------------------------------------------------------------------
	# Obj METHOD: setParents
	#------------------------------------------------------------------
	def setParents(self,parents):
	
		"""Obj METHOD: setParents(self,parents):
		change parents of this object to the given object or list.
		"""
		
		if not self.canWriteProp('parents'):
			raise "PermError", "Can't modify built-in parents"
		if type(parents) == ListType:
			parents = tuple(parents)
		elif type(parents) == InstanceType:
			parents = (parents,)
		elif type(parents) != TupleType:
			raise "ParamError", "parents must be an object, list, or tuple"
		for p in parents:
			if type(p) != InstanceType or \
			  (p.__class__ != Obj and p.__class__ != User and
			   p.__class__ != Directory):
				raise "ParamError", `p` + " is not a POO object"
			# is proposed parent fertile?
			if not p.f:
				raise "ParamError", tostr(p) + " is infertile"
			# is proposed parent already one of our	descendants?		
			if self in p.ancestors():
				raise "ParamError", tostr(p) + \
				  " cannot be both ancestor and descendant"
		self.__dict__['parents'] = parents
		
	#------------------------------------------------------------------
	# Obj METHOD: ancestors
	#------------------------------------------------------------------
	def ancestors(self):
	
		"""Obj METHOD: ancestors(self):
		return list of all this object's ancestors.
		"""
		
		lst = []
		for p in self.__dict__['parents']:
			lst = lst + [p] + p.ancestors()
		return lst

	#------------------------------------------------------------------
	# Obj attribute setter
	#------------------------------------------------------------------
	def __setattr__(self,propname,value):
	
		"""Obj attribute setter."""
		
		# if value is a Func, don't allow the assignment
		# unless caller is a wiz or the Func owner
		if type(value) == InstanceType and value.__class__ == Func:
			if gOwner and gOwner != value.owner and not gOwner.wizard:
				raise "PermError", "Can't store reference to " \
					+ str(value) + " owned by " + str(value.owner)
		
		# coerce types where appropriate
		if propname == "aliases":
			if type(value) == ListType:
				value = tuple(value)
			elif type(value) != TupleType:
				value = (value,)
		
		# if it's a built-in attribute, just check permission
		if self.__dict__.has_key(propname):
			if self.canWriteProp(propname):
				if propname == 'parents':
					self.setParents(value)
				else: self.__dict__[propname] = value
				if propname=='wantsUpdates': self.CheckUpdate()
			else: raise "PermError", "Can't modify built-in "+propname
			return
	
		# if it's an existing local property, change val
		if gProps[self].has_key(propname):
			if self.canWriteProp(propname):
				gProps[self][propname].val = value
				if propname=='update': self.CheckUpdate()
			else: raise "PermError", "Can't modify "+propname
			return

		# if it's an inherited property, add it anew here
		prop = self.getprop(propname)
		if prop:
			# implement "c" flag: when overriding a prop with c==1
			# the owner of the object owns the overridden property
			if prop.c: owner = self.owner
			else: owner = prop.owner
			if gOwner != owner and not prop.w and not gOwner.wizard:
				raise "PermError", "Can't modify "+propname
			np = Prop(owner,value)
			for k in ['r','w','c']:
				np.__dict__[k] = prop.__dict__[k]
			gProps[self][propname] = np
			if propname=='update': self.CheckUpdate()
			return
			
		# check to see if this would actually add a new property
		if not gProps[self].has_key(propname):
			if not self.canAddOrDeleteProp(propname):
				raise "PermError", str(gOwner) + " can't add property (" \
					+ propname + ") to "+str(self)
			else:
				gProps[self][propname] = Prop(gOwner,value)
				if propname=='update': self.CheckUpdate()
			return


	#------------------------------------------------------------------
	# Obj attribute deletion
	#------------------------------------------------------------------
	def __delattr__(self,propname):
	
		"""Obj attribute deletor."""
		
		if self.__dict__.has_key(propname):
			raise "PermError", "Can't delete built-in "+propname
		elif self.canAddOrDeleteProp(propname):
			del gProps[self][propname]
			if propname=='update': self.CheckUpdate()
		else: raise "PermError", "Can't delete property "+propname

	#------------------------------------------------------------------
	# Obj pickling support
	#------------------------------------------------------------------
	def __getstate__(self):
	
		"""Obj pickling support."""
		
		temp = {}
		for key  in self.__dict__.keys():
			if key not in ['__outstream']:
				temp[key] = self.__dict__[key]
		return temp

	#------------------------------------------------------------------
	# Obj unpickling support
	#------------------------------------------------------------------
	def __setstate__(self,value):
	
		"""Obj unpickling support."""
		
		for key in value.keys():
			self.__dict__[key] = value[key]
		self.__dict__['__outstream'] = None
			
	#------------------------------------------------------------------
	# Obj METHOD: DoUpdate
	#------------------------------------------------------------------
	def DoUpdate(self):
	
		"""Obj METHOD: DoUpdate(self):
		update this object by calling self.hear or self.update
		as appropriate.
		"""
		
		global gOwner
		prevOwner = gOwner
		buf = self._inbuf
		if buf:
			nextline = buf[0]
			del buf[0]
			try: self.hear(nextline)
			except: pass
			if not buf: self.CheckUpdate()
		elif self.wantsUpdates:
			gOwner = self
			try: self.update()
			except:
				self.owner.tell("Error calling " + str(self) + ".update():")
				self.owner.handleErr(sys.exc_type, sys.exc_value, sys.exc_traceback)
				if (time()%10 < 1):
					self.__dict__['wantsUpdates'] = 0
					self.owner.tell(str(self) + ".wantsUpdates has been set to 0")
				self.CheckUpdate()
		else:
			self.CheckUpdate()
		gOwner = prevOwner

	#------------------------------------------------------------------
	# Obj METHOD: CheckUpdate
	#------------------------------------------------------------------
	def CheckUpdate(self):
	
		"""Obj METHOD: CheckUpdate(self):
		check whether this object should receive updates or not,
		and update gUpdateList accordingly.
		"""
		
		wantit = (callable(self.update) and self.wantsUpdates) \
			or (callable(self.hear) and len(self._inbuf))
		if wantit and self not in gUpdateList:
			gUpdateList.append(self)
			# self.owner.tell(self.name + " (#" + str(self.id) + \
			#	") now receives updates.")
		elif not wantit and self in gUpdateList:
			gUpdateList.remove(self)
			# self.owner.tell(self.name + " (#" + str(self.id) + \
			#	") no longer receives updates.")

	#------------------------------------------------------------------
	# Obj METHOD: handleErr
	#------------------------------------------------------------------
	def handleErr(self,e_type, e_val, e_traceback):
	
		"""Obj METHOD: handleErr(self,e_type, e_val, e_traceback):
		crawl through the traceback and present only the most relevant line.
		"""
		
		# find the last traceback:
		tb = e_traceback
#		print tb.tb_frame.f_code.co_filename, tb.tb_lineno
		found = None
		if tb.tb_frame.f_code.co_filename[0] == '<':
			found = tb
		while tb.tb_next:
			tb = tb.tb_next
			if tb.tb_frame.f_code.co_filename[0] == '<':
				found = tb
#			print tb.tb_frame.f_code.co_filename, tb.tb_lineno
		if found: tb = found
		lineno = tb.tb_lineno
		codename = tb.tb_frame.f_code.co_filename
		if codename == '<string>':
			self.tell( e_type,'in command:', e_val)
		elif string.find(codename, ".py") > 0 and e_type == "SyntaxError":
			self.tell( "Compilation error: ", e_val )
		else:
			self.tell( e_type,'in line',lineno-2, 'of ',codename, ':', e_val)
		return lineno

	#------------------------------------------------------------------
	# Obj METHOD: do_cmd
	#------------------------------------------------------------------
	def do_cmd(self,cmd):
	
		"""Obj METHOD: do_cmd(self,cmd):
		attempt to execute the given POO command.
		"""
		
		global gCmdLine, gOwner, gUser
		# check permissions
		if gOwner and not gOwner.wizard and self.owner != gOwner:
			raise "PermError", "Must be wizard or owner to do_cmd()"
		gCmdLine = cmd
		gSafeGlobals['gCmdLine'] = gCmdLine
		if not cmd: return

		# change output and user characteristics
		prevInfo = (gUser, sys.stdout, gOwner)
		gUser, sys.stdout, gOwner = self, self.__dict__['__outstream'], self

		# if the cmd starts with ; or %, execute Python code
		if cmd[0] == '%' or cmd[0] == ';':
			try: locals = gLocals[self]
			except: locals = {'self':self,'this':self,'player':self,
				'caller':self, 'me':self, 'user':self, '__builtins__':None}
			try:
				if not self.programmer:
					raise "PermError", "requires programmer privs"
				exec(cmd[1:]) in gSafeGlobals, locals
			except:
				self.handleErr(sys.exc_type, sys.exc_value, sys.exc_traceback)
		else:
			# otherwise, attempt to execute the named command
			try:
				sys.stdout = self.__dict__['__outstream']
				HandleCommand(self,cmd)
			except:
				self.handleErr(sys.exc_type, sys.exc_value, sys.exc_traceback)
		if sys.stdout: sys.stdout.flush()

		# restore output and user characteristics
		gUser, sys.stdout, gOwner = prevInfo

	#------------------------------------------------------------------
	# Obj METHOD: proplist
	#------------------------------------------------------------------
	def proplist(self):
	
		"""Obj METHOD: proplist(self)
		return a list of all property names.
		"""
		
		# you must be able to read the object to get a proplist
		if gOwner and gOwner != self.owner and not gOwner.wizard and not self.r:
			raise "PermError", "Can't list properties of this object"
		props = filter(lambda x:x[0]!='_' and x!='password', \
				self.__dict__.keys() + gProps[self].keys())
		props.sort()
		return props

	#------------------------------------------------------------------
	# Obj METHOD: isa
	#------------------------------------------------------------------
	def isa(self,what):
	
		"""Obj METHOD: isa(self,what):
		return 1 if what is an ancestor of self.
		"""
		
		if type(what) != types.InstanceType:
			what = gProps[gObjlist[0]][what].val
		if what==self or what in self.parents: return 1
		for p in self.parents:
		    if p.isa(what): return 1
		return 0

	#------------------------------------------------------------------
	# Obj METHOD: contents
	#------------------------------------------------------------------
	def contents(self):
	
		"""Obj METHOD: contents(self):
		return list of objects contained by self.
		"""
		
		if gOwner and gOwner != self.owner and not gOwner.wizard and not self.r:
			raise "PermError", "Can't list contents of this object"
		aCopy = []
		aCopy[:] = gContents[self]
		return aCopy

	#----------------------------------------------------------------------
	# Obj METHOD: getCmdDef
	#----------------------------------------------------------------------
	def getCmdDef( self, verb, inherit=1 ):

		"""Obj METHOD: getCmdDef(self, verb, inherit=1):
		This method returns a (possibly empty) tuple of commands which
		begin with the given verb from this object's command dictionary.
		If no verb is specified, return all defined commands.
		"""

		# first look for command defs on this object (overriding parents)
		out = ()
		if gCmdDefs.has_key(self):
			defs = gCmdDefs[self]
			if not verb:
				for i in defs.values():
					out = out + i
			elif defs.has_key(verb):
				out = defs[verb]			

		# then, add commands from parents
		if not inherit: return out
		for p in self.parents:
			out = out + p.getCmdDef(verb,inherit)
		
		return out
	
	#----------------------------------------------------------------------
	# Obj METHOD: setCmdDef
	#----------------------------------------------------------------------
	def setCmdDef( self, verb, pattern, funcdef ):

		"""Obj METHOD: setCmdDef( self, verb, pattern, funcdef ):
		This function sets a command definition in the global dictionary.
		If the given function call is None or an empty string, the command
		will be removed.  If the given pattern matches an existing one
		exactly, the existing one will be replaced.  This function requires
		wiz privs.
		"""
		
		global gOwner, gCmdDefs
		if gOwner and not gOwner.wizard and gOwner != self.owner :
			raise "PermError", "Must be owner or wizard to call setCmdDef()"
		if funcdef:
			newcmd = CmdDef(gOwner, pattern, funcdef)
		else:
			newcmd = None
		# make sure we have an entry for this object in gCmdDefs
		if not gCmdDefs.has_key(self):
			gCmdDefs[self] = {}
		defs = gCmdDefs[self]
		# get list of commands on this object for this verb
		try:
			cmds = list(defs[verb])
		except:
			if newcmd: defs[verb] = (newcmd,)
			return
		for i in range(0,len(cmds)):
			if cmds[i].pat == pattern:
				# command exists already; replace it
				if newcmd: cmds[i] = newcmd
				else: del cmds[i]
				if cmds: defs[verb] = tuple(cmds)
				else: del defs[verb]
				if not gCmdDefs[self]: del gCmdDefs[self]
				return
		if newcmd: cmds.append(newcmd)
		else: return
		defs[verb] = tuple(cmds)

#----------------------------------------------------------------------
# CLASS: User
#----------------------------------------------------------------------
class User(Obj):

	"""CLASS User:
	This subclass of Obj defines the class of Users in the game.  They
	have all the properties of other POO objects, but also have
	facilities for being connected via the network.
	"""
	
	#------------------------------------------------------------------
	# User constructor
	#------------------------------------------------------------------
	def __init__(self,id=0,*parents):
	
		"""User constructor."""
		
		global gObjlist, gHighID, gProps
		if not gProps.has_key(self): gProps[self] = {}
		gContents[self] = []
		if gUnpickling: return
		Obj.__init__(self,id)
		attributes = {
			'__outstream': None,	# output stream
			'id': id,				# uniquet identifier
			'_editObj': None,		# object whose property we're editing
			'_editProp': '',		# name of property we're editing
			'_editBuf': [],			# edit buffer
			'_editState': None,		# edit state (temp storage)
			'_prompt': '<t>poo>',	# user prompt
			'_oldprompt': '',		# previous prompt
			'__isuser': 1 }			# yes, we're a user
		for k in attributes.keys():
			self.__dict__[k] = attributes[k]
		self.__dict__['parents'] = parents

	#------------------------------------------------------------------
	# User METHOD: connected
	#------------------------------------------------------------------
	def connected(self):
	
		"""User METHOD: connected(self):
		return 1 if this User is currently logged in.
		"""
		
		return (self.__dict__['__outstream'] != None)
	
	#------------------------------------------------------------------
	# User METHOD: Login
	#------------------------------------------------------------------
	def Login(self, stream):
	
		"""User METHOD: Login(self, stream):
		log into the POO server, and connect output to the given stream.
		"""
		
		global gOwner, gLocals
		if gOwner:
			raise "UseError", "Never call Login() directly!"
		gLocals[self] = {'self':self, 'this':self, 'player':self, 
				'caller':self, 'me':self, 'user':self, '__builtins__':None }
		self.__dict__['__outstream'] = stream
		self.loginTime = time()
		self.CheckUpdate()
		prevOwner = gOwner
		try: 
			sys.stdout = stream
			gOwner = self
			gObjlist[0].login(self)
		except: pass
		gOwner = prevOwner
		self.tell_noCR(self._prompt)

	#------------------------------------------------------------------
	# User METHOD: Logout
	#------------------------------------------------------------------
	def Logout(self):
	
		"""User METHOD: Logout(self):
		disconnect from the POO server."""

		# beware of the case where the user is already logged out
		# (perhaps because her connection hung -- see Object.tell_noCR)
		if self not in gLocals.keys(): return
		
		global gOwner
		if gOwner and not gOwner.wizard and self.owner != gOwner:
			raise "PermError", "Must be wizard or owner to Logout()"
		prevOwner = gOwner
		sys.stdout = self.__dict__['__outstream']
		self.__dict__['__outstream'] = None
		try:
			gOwner = self
			gObjlist[0].logout(self)
		except: pass
		gOwner = prevOwner
		self.CheckUpdate()
		del gLocals[self]
	
	#------------------------------------------------------------------
	# User METHOD: handleMsg
	#------------------------------------------------------------------
	def handleMsg(self,msg):
	
		"""User METHOD: handleMsg(self,msg):
		handle some input from the real-life user.
		We assume msg is a single line.
		"""
		if gOwner and gOwner != self:
			raise "UseError", "Never call handleMsg() directly!"
		#lines = string.split(msg,'\n')
		#lines = map(lambda x:string.rstrip(x), lines)
		#self._inbuf = self._inbuf + lines
		self._inbuf = self._inbuf + [msg]
		self.activeTime = time()

	#------------------------------------------------------------------
	# User METHOD: CheckUpdate
	#------------------------------------------------------------------
	def CheckUpdate(self):
	
		"""User METHOD: CheckUpdate(self):
		overrides Obj.CheckUpdate; a User should be updated if it
		is connected, and not otherwise.
		"""
		
		if self.__dict__['__outstream'] and self not in gUpdateList:
			gUpdateList.append(self)
		elif not self.__dict__['__outstream'] and self in gUpdateList:
			gUpdateList.remove(self)

	#------------------------------------------------------------------
	# User METHOD: DoUpdate
	#------------------------------------------------------------------
	def DoUpdate(self):
	
		"""User METHOD: DoUpdate(self):
		overrides Obj.DoUpdate; this method gets a command from the
		inbuf and attempts to execute it.
		"""
		
		# if there's a command in the inbuf, do it
		if self._inbuf:
			cmd = self._inbuf[0]
			self._inbuf = self._inbuf[1:]
			if self._editObj != None: self.doEdit( cmd )
			else: self.do_cmd( cmd )
			self.tell_noCR(self._prompt)
		# if user wants update() calls, do it
		if self.wantsUpdates and callable(self.update):
			try: self.update()
			except: pass
		# otherwise, do nothing (but show prompt)


	#------------------------------------------------------------------
	# User METHOD: startEdit
	#------------------------------------------------------------------
	def startEdit(self,object,propname,postEdit=None):
	
		"""User METHOD: startEdit(self,object,propname,postEdit=None):
		invoke the editor on the named object property.
		If present, postEdit should be a function which will receive
		the object reference, property name, and a "saved" parameter
		which will be 0 if user aborted, 1 if user saved changes.
		"""
		
		global gOwner
		if gOwner and not gOwner.wizard and self.owner != gOwner:
			raise "PermError", "Must be wizard or owner to startEdit()"
		# check permissions...
		# but don't apply C flag, since we're modifying it in place
		if not object.canWriteProp(propname,0):
			raise "PermError", "Can't modify property"
		self.__dict__['_editBuf'] = []
		prop = getattr(object,propname)
		if prop == None:
			raise "NotFoundError", "No property named " + propname
		if type(prop) == InstanceType and prop.__class__ == Func:
			self.__dict__['_editObj'] = prop
			self.__dict__['_editProp'] = "source"
			copyList(prop.source, self._editBuf)
		elif type(prop) == ListType or type(prop) == TupleType:
			self.__dict__['_editObj'] = object
			self.__dict__['_editProp'] = propname
			copyList(prop, self._editBuf)
		else:
			raise "TypeError", "Can't edit "+ str(type(prop.val)) +" objects"
		self.__dict__['_postEditFunc'] = postEdit

		# set editor prompt
		self.__dict__['_oldprompt'] = self._prompt
		self.__dict__['_prompt'] = 'edit>'

		# now, invoke the built-in editor, or the user's preferred one
		prevOwner = gOwner
		gOwner = self
		try:
			self.__dict__['_editState'] = \
					self.editor( self._editBuf, "", None )
		except:
			try:
				self.__dict__['_editState'] = \
					self.standardEdit( self._editBuf, "", None )
			except: pass
		gOwner = prevOwner
		
	#------------------------------------------------------------------
	# User METHOD: doEdit
	#------------------------------------------------------------------
	def doEdit(self, msg):
		if gOwner and gOwner != self:
			raise "UseError", "doEdit() must only be called on oneself"

		# invoke the built-in editor, or the user's preferred one
		try:
			state = self.editor( self._editBuf, msg, self._editState )
		except:
			state = self.standardEdit( self._editBuf, msg, self._editState )
		
		# if it was an exit or abort command, make sure we really do!
		if msg == '.x' and state != 'DONE': state = 'DONE'
		if msg == '.q' and state != 'ABORT': state = 'ABORT'

		if state == 'DONE':	self.endEdit(1)
		elif state == 'ABORT': self.endEdit(0)
		else: self.__dict__['_editState'] = state
		
	#------------------------------------------------------------------
	# User METHOD: endEdit
	#------------------------------------------------------------------
	def endEdit(self, save):
		if gOwner and gOwner != self:
			raise "UseError", "endEdit() must only be called on oneself"
		self.__dict__['_prompt'] = self._oldprompt
		if not self._editObj:
			raise "Error", "endEdit() called while not editing!"
		if save:
			setattr( self._editObj, self._editProp, tuple(self._editBuf) )
		if self._postEditFunc:
			try:
				self._postEditFunc( self, self._editObj, self._editProp, save )
			except:
				self.tell( "Error calling post-edit function " \
						+ str(self._postEditFunc) )
		self.__dict__['_editObj'] = None
		self.__dict__['_editProp'] = ''
		self.__dict__['_editBuf'] = []
		self.__dict__['_postEditFunc'] = None

	#------------------------------------------------------------------
	# User METHOD: enterFunc
	#------------------------------------------------------------------
	def enterFunc(self,object,funcname):
	
		"""User METHOD: enterFunc(self,object,funcname):
		start the editor on the given function, creating it if it
		does not already exist.
		"""
		
		if gOwner and gOwner != self:
			raise "UseError", "enterFunc() must only be called on oneself"
		if gOwner and not gOwner.programmer:
			raise "PermError", "enterFunc requires programmer privs"
		lpos = string.find(funcname,'(')
		if lpos < 0: args = '(self)'	# by default, make it a method
		else:
			args = string.strip(funcname[lpos:])
			if not args or args == ')':
				raise "DeclarationError", \
				"methods must have at least one parameter (self)"
			funcname = funcname[:lpos]
		setattr( object, funcname, Func(funcname+args, gOwner) )
		self.tell('Enter function code below:')
		self.startEdit( object, funcname )

	#------------------------------------------------------------------
	# User METHOD: standardEdit
	#------------------------------------------------------------------
	def standardEdit( self, buf, entry, state ):
	
		"""User METHOD: standardEdit( self, buf, entry, state ):
		This method implements the simple, standard POO editor.
		"""
		
		if gOwner and gOwner != self:
			raise "UseError", "standardEdit() must only be called on oneself"

		if not state:
			# new editing session 
			self.tell("[POO Editor -- enter .? for help]")
			return 'OK'

		if entry=='.?':		#	? - help
			self.tell("Editing Commands:")
			self.tell("   .l   List")
			self.tell("   .d   Delete Last Line")
			self.tell("   .da  Delete All Lines")
			self.tell("   .x   Save & Exit ('x' is optional)")
			self.tell("   .q   Quit (Abort)")

		elif entry=='.l':		#	l - list
			self.tell("---- Current Buffer ----")
			for line in buf:
				self.tell(line)
			self.tell("------------------------")

		elif entry=='.d':		#	d - delete last line
			if not buf:
				self.tell("Buffer is empty.")
				return
			del buf[-1]
			self.tell("Line deleted.")

		elif entry=='.da':		#	da - delete all
			buf[:] = []
			self.tell("All lines deleted.")

		elif entry=='.' or entry == '.x':		#	x - save & exit
			self.tell("Saved.")
			return 'DONE'

		elif entry=='.q':		#	q - quit (abort)
			self.tell("Aborted.")
			return 'ABORT'
			
		else:	#	not a known command?  Just append it to the buffer
			buf.append(entry)
		
		return 'OK'

#----------------------------------------------------------------------
# CLASS: Directory
#----------------------------------------------------------------------
class Directory(Obj):

	"""CLASS: Directory
	This subclass of Obj defines the class of Directories in the game.
	These are POO objects which are only used for keeping references to
	other objects.  They have the special characteristic that contents
	are treated like properties; that is, if Directory "foo" contains
	an object called "bar", it can be referred to as "foo.bar".

	Directories are severely restricted, so that they may be freely
	used within a quota system (i.e., they won't take up much memory).
	"""
		
	#------------------------------------------------------------------
	# Directory constructor
	#------------------------------------------------------------------
	def __init__(self,id=0,*parents):
	
		"""Directory constructor."""
		
		global gObjlist, gHighID, gProps
		if not gProps.has_key(self): gProps[self] = {}
		if not gContents.has_key(self): gContents[self] = []
		if gUnpickling: return
		for p in parents:
			if gOwner and not gOwner.wizard and p != getObj('$dir'):
				raise "PermError", "Directories must be derived only from $dir"
		if not parents:
			raise "PermError", "Directories must be derived only from $dir"
		Obj.__init__(self,id)
		self.__dict__['parents'] = parents

	#------------------------------------------------------------------
	# Directory attribute accessor
	#------------------------------------------------------------------
	def __getattr__(self,propname):
	
		"""Directory attribute gettor.
		Treats contents as if they were properties."""
		
		# try usual methods first...
		val = Obj.__getattr__(self,propname)
		if val: return val
		# if those fail, look in our contents
		matches = filter( \
				lambda x,y=propname:x.__dict__['name']==y,
				gContents[self])
		if matches: return matches[0]
		return None

	#------------------------------------------------------------------
	# Directory METHOD: canAddOrDeleteProp
	#------------------------------------------------------------------
	def canAddOrDeleteProp(self, propname):
	
		"""Directory METHOD: canAddOrDeleteProp(self, propname):
		overrides Obj.canAddorDeleteProp;
		only wizards can add properties to a Directory.
		"""
		
		if gOwner and not gOwner.wizard: return 0
		return Obj.canAddOrDeleteProp(self, propname)

	#------------------------------------------------------------------
	# Directory METHOD: canWriteProp
	#------------------------------------------------------------------
	def canWriteProp(self, propname, useCflag=1):
	
		"""Directory METHOD: canWriteprop(self, propname, useCflag=1):
		overrides the Obj method; prevents nonwizards from changing
		a directory's parents.
		"""
		
		# non-wizards can't change a directory's parents
		if propname == "parents" and gOwner and not gOwner.wizard:
			return 0
		
		return Obj.canWriteProp( self, propname, useCflag )

	#------------------------------------------------------------------
	# Directory METHOD: proplist
	#------------------------------------------------------------------
	def proplist(self):
	
		"""Directory METHOD: proplist(self):
		return a list of property and contents names.
		"""
		
		# first, get normal props
		props = Obj.proplist(self)
		# then, add contents
		props = props + map(lambda x:x.name, gContents[self])
		props.sort()
		return props


#----------------------------------------------------------------------
# FUNCTION: users
#----------------------------------------------------------------------
def users():
	"""FUNCTION: users():
	This function returns a list of all connected Users.
	"""
	
	global gObjlist
	return filter( lambda x:x.__isuser and x.__outstream,
			gObjlist.values() )

#----------------------------------------------------------------------
# FUNCTION: globalkeys
#----------------------------------------------------------------------
def globalkeys():
	"""FUNCTION: globalkeys():
	This function returns a list of global variable names, like
	doing globals().keys().
	"""
	return gSafeGlobals.keys()

#----------------------------------------------------------------------
# FUNCTION: getObj
#----------------------------------------------------------------------
def getObj(ref):
	"""FUNCTION: getObj(ref):
	This function returns a POO object by number, #num, or $refname.
	"""
	
	global gObjlist
	if type(ref) == IntType: return gObjlist[ref]
	if type(ref) == StringType:
		ref = string.join(string.split(ref,'@'),'at_')
		periodpos = string.find(ref,'.')
		if periodpos > 0:
			parts = string.split(ref,'.')
			obj = getObj(parts[0])
			for p in parts[1:]:
				obj = getattr(obj,p)
			return obj
		if ref[0] == '#':
			return gObjlist[ string.atoi(ref[1:]) ]
		if ref[0] == '$':
			return gProps[gObjlist[0]][ref[1:]].val
		if ref == "ALL":
			return gObjlist.values()
	raise "ParamError", "Invalid parameter (" + str(ref) + ") for getObj"

#----------------------------------------------------------------------
# FUNCTION: treasury
#----------------------------------------------------------------------
def treasury():

	"""FUNCTION: treasury():
	This function returns the current value of the wizard treasury.
	(Wizards only.)
	"""
	
	global gTreasury, gOwner
	if gOwner and not gOwner.wizard:
		raise "PermError", "Must be wizard to view treasury"
	return gTreasury

#----------------------------------------------------------------------
# FUNCTION: transfer
#----------------------------------------------------------------------
def transfer(src, dest, amount):

	"""FUNCTION: transfer(src, dest, amount):
	This function transfers credits from the source to the dest object.
	If source is None, credits come from the treasury; in this case,
	both gOwner and the dest object must be wizards.
	"""
	
	global gTreasury, gOwner
	if gOwner and not gOwner.wizard:
		raise "PermError", "Must be wizard to transfer funds"
	if src:
		if src.__dict__['__credits'] < amount:
			raise "CreditError", str(amount) + " credits requested, " \
				+ str(src.__dict__['__credits']) + " available"
		src.__dict__['__credits'] = src.__dict__['__credits'] - amount
		dest.__dict__['__credits'] = dest.__dict__['__credits'] + amount
	else:
		if not dest.wizard:
			raise "PermError", "Treasury funds may be transferred only to wizards"
		if gTreasury < amount:
			raise "CreditError", str(amount) + " credits requested, " \
				+ str(gTreasury) + " available"
		gTreasury = gTreasury - amount
		dest.__dict__['__credits'] = dest.__dict__['__credits'] + amount	

#----------------------------------------------------------------------
# FUNCTION: move
#----------------------------------------------------------------------
def move(what,where):

	"""FUNCTION: move(what,where):
	This function moves an object inside another one, if various checks
	are successfully negotiated.
	"""
		
	global gOwner

	# mover must be a wizard or owner of the object
	if gOwner and not gOwner.wizard: ### and gOwner!=what.owner:
		raise "PermError", "Must be wizard to move()"

	# if where is an object, it must return 1 from accept()
	if where:
		if callable(where.accept): OK = where.accept(what)
		else: OK = where.accept
		if not OK: raise "DeniedError", where.name + \
		  " refuses to accept " + what.name

		# disallow any loops in the containment hierarchy
		# this occurs if where is contained by what
		loc = where
		while loc:
			if loc == what:
				raise "ContainerLoopError", \
				  "Can't move "+what.name+" into "+where.name
			loc = loc.location

	# actually perform the move

	prevOwner = gOwner; gOwner = move
	oldwhere = what.location

	# ...remove from previous location's contents list
	if what.location:
		gContents[what.location].remove(what)
	
	# ...update location, and add to location's contents
	what.location = where
	if where:
		gContents[where].append(what)

	gOwner = prevOwner

	# call notification routines
	if oldwhere and callable(oldwhere.exitfunc): oldwhere.exitfunc(what)
	if where and callable(where.enterfunc): where.enterfunc(what)
	
#----------------------------------------------------------------------
# FUNCTION: create
#----------------------------------------------------------------------
def create(creator,parent,name=""):

	"""FUNCTION: create(creator, parent, name=''):
	This function creates a new object and returns a reference to it.
	"""
		
	global gHighID, gTreasury, gOwner
	
	# check parent object type, fertility, and creator funds
	if type(parent) != InstanceType or \
			  (parent.__class__ != Obj and parent.__class__ != User and
			   parent.__class__ != Directory):
		raise "ParamError", "Parent must be a POO object."
	if not parent.f:
		raise "ParamError", "Parent object must be fertile (flag f)."
	if gOwner and not gOwner.wizard:
		raise "PermError", "Object creation requires wizard privs."
	cost = kObjValue + kCreationTax
	if creator.__dict__['__credits'] < cost:
		raise "CreditError", str(cost) + " credits needed for object creation."
		
	# create a new object of the appropriate class
	oldclass = parent.__class__
	newobj = oldclass(gHighID+1, parent)

	# set owner and name
	newobj.__dict__['owner'] = creator
	if name: newobj.name = name
	else: newobj.name = "new" + parent.name

	# move credits around
	newobj.__dict__['__value'] = kObjValue
	gTreasury = gTreasury + kCreationTax
	creator.__dict__['__credits'] = creator.__dict__['__credits'] - cost

	# place the object in the creator's inventory
	move(newobj,creator)
	return newobj

#----------------------------------------------------------------------
# FUNCTION: destroy
#----------------------------------------------------------------------
def destroy(object):

	"""FUNCTION: destroy(object):
	The object is destroyed, and all references to it are removed.
	If it has any credits, they are given to its owner if possible, and
	otherwise to the treasury.  The object must not have any children.
	This is a very time-consuming operation, and should be used rarely.
	"""

	global gObjlist, gOwner, gTreasury, gUpdateList
	
	# check permissions
	if gOwner and not gOwner.wizard and gOwner != object.owner:
		raise "PermError", "Caller (" + str(gOwner) + \
				") must be owner or wizard to destroy()"
	
	# refuse to make orphans
	children = filter(lambda x,a=object:a in x.parents,gObjlist.values())
	if children:
		raise "OrphanError", "Object has children: "+tostr(children)

	# refuse to strand contents
	if object.contents():
		raise "ContentsError", "Object has contents: "+tostr(object.contents())

	# adjust credits
	gTreasury = gTreasury + kRecycleTax
	if (object.__dict__['__value'] > kRecycleTax):
		gained = object.__dict__['__value'] - kRecycleTax
	else:
		gained = 0
	try:
		recipient = object.__dict__['owner']
		recipient.__dict__['__credits'] = recipient.__dict__['__credits'] + gained
	except:
		gTreasury = gTreasury + gained
	
	# move into nothingness
	move(object,None)

	# destroy all properties, and its contents list
	del gProps[object]
	del gContents[object]

	# find all references to the object, anywhere, and remove them
	for ob in gObjlist.values():
		for k in ob.__dict__.keys():
			if ob.__dict__[k]==object: ob.__dict__[k] = None
		if gProps.has_key(ob):
			for k in gProps[ob].keys():
				try:
					if gProps[ob][k] == object or \
					 gProps[ob][k].val==object:
						del gProps[ob][k]
				except: pass

	# remove from gObjlist
	for k in gObjlist.keys():
		if gObjlist[k] == object: del gObjlist[k]

	# remove from gUpdateList
	for i in range(0,len(gUpdateList)):
		if gUpdateList[i] == object: del gUpdateList[i]

#----------------------------------------------------------------------
# FUNCTION: show
#----------------------------------------------------------------------
def show( message, parties, broadcast=1 ):
	"""FUNCTION: show( message, parties, broadcast=1):
	
		message: string containing key tags
		         e.g., "%1D %1:(gets) %2i."
		parties: dictionary mapping keys to objects
		         e.g., {1:caller, 2:dobj}
		broadcast: also broadcast a message to the container
				of the object specified by this key
			
	This function displays an effect involving several parties.  Each
	of the parties gets a customized message, and the location of
	the party specified by the broadcast key gets a general message
	sent to its broadcast function.
	"""
	# make lists out of the dictionary
	keys = parties.keys()
	values = parties.values()	# can we assume this is the same order?!?
	
	# get various customized outputs
	outputs = msg.Msg().sub_parties(parties,message,values)
	
	# tell each object (if it's really an object)
	for i in range(0,len(values)):
		if type(values[i]) == InstanceType:
			values[i].tell(outputs[0][i])
	
	# and finally, broadcast the general message if possible
	if not broadcast: return
	try: parties[broadcast].location.broadcast(outputs[1], values)
	except: pass
	
#----------------------------------------------------------------------
# FUNCTION: POOopen
#----------------------------------------------------------------------
def POOopen( filename, mode='r' ):

	"""FUNCTION: POOopen( filename, mode='r' ):
	This function opens a file under the POO directory for reading.
	Filenames with slashes (either direction) or colons are disallowed.
	"""
	
	# check privs
	if gOwner and not gOwner.wizard:
		raise "PermError", "open() requires wizard privs"
	
	# check for dangerous characters
	if '/' in filename or '\\' in filename or ':' in filename:
		raise "ParamError", "filenames cannot contain /, \\, or :"
	
	# append it to the POO file directory, and return the file ref
	fullpath = os.path.join( gFilePath, filename )
	return open( fullpath, mode )

#----------------------------------------------------------------------
# FUNCTION: gSave
#----------------------------------------------------------------------
def gSave(filename='poo.dat'):

	"""FUNCTION: gSave(filename='poo.dat'):
	This function saves the current database to a file (via pickle).
	"""
	
	global gObjlist, gProps, gContents, gCmdDefs, gTreasury, gLastSave
	# try saving the previous data file
	backupname = filename + ".bak"
	try: os.unlink(backupname)
	except: pass
	try: os.rename(filename, backupname)
	except: pass
	file = open(filename, 'w')
	PooPickler(file).dump((gObjlist,gProps,gContents,gCmdDefs,gTreasury))
	file.close()
	gLastSave = time()

#----------------------------------------------------------------------
# FUNCTION: gLoad
#----------------------------------------------------------------------
def gLoad(filename='poo.dat'):

	"""FUNCTION: gLoad(filename='poo.dat'):
	This function loads the database from a file (via pickle).
	"""
	
	global gObjlist, gProps, gContents, gCmdDefs, gSafeGlobals, \
			gHighID, gUnpickling, gUpdateList, gTreasury
	# unpickle the file
	gUnpickling = 1
	file = open(filename, 'r')
#	(gObjlist,gProps,gContents,gTreasury) = pickle.load(file)
	(gObjlist,gProps,gContents,gCmdDefs,gTreasury) = pickle.load(file)
	file.close()
	gUnpickling = 0
	# build the index
	kys = filter(lambda x:type(x)==types.IntType, gObjlist.keys())
	kys.sort()
	gHighID = kys[-1]
	# figure out which objects need updates
	gUpdateList = filter(lambda x:callable(x.update), gObjlist.values())
	# set up any automatic globals which refer to the object set
	if getObj(0).pub: gSafeGlobals['pub'] = getObj(0).pub

	# apply corrections, if any
	# (currently none)

#----------------------------------------------------------------------
# FUNCTION: gUpdate
#----------------------------------------------------------------------
def gUpdate():

	"""FUNCTION: gUpdate():
	This function updates all the objects in the database which need
	updating.  It should be called periodically by the main program
	to make the engine crank.  It also periodically saves the database.
	"""
	# update all objects in the update list
	for ob in gUpdateList:
		ob.DoUpdate()
	# consider saving the database
	t = time() - gLastSave
	if t > maxSaveTime:			# later: do more sophisticated checks here!
		gSave()

#----------------------------------------------------------------------
# FUNCTION: newDatabase
#----------------------------------------------------------------------
def newDatabase():
	
	"""FUNCTION: newDatabase():
	This function creates a new database from scratch ...
	used for bootstrapping the initial database.
	"""
	
	global gObjlist, gHighID
	print "Creating new database..."
	gObjlist = {}
	
	gObjlist[0] = Obj(0)
	gObjlist[0].name = "root"
	print str(gObjlist[0]), ':', gObjlist[0].name
	
	gObjlist[1] = Obj(1)
	gObjlist[1].name = "object"
	gObjlist[1].f = 1
	print str(gObjlist[1]), ':', gObjlist[1].name

	gObjlist[2] = User(2,gObjlist[1])
	gObjlist[2].name = "Implementor"
	gObjlist[2].wizard = 1
	gObjlist[2].programmer = 1
	print str(gObjlist[2]), ':', gObjlist[2].name

	gObjlist[3] = Obj(3,gObjlist[1])
	gObjlist[3].name = "place"
	gObjlist[3].accept = 1
	print str(gObjlist[3]), ':', gObjlist[3].name
	move(gObjlist[2],gObjlist[3])

	gObjlist[4] = Directory(4,gObjlist[1])
	gObjlist[4].name = "directory"
	print str(gObjlist[4]), ':', gObjlist[4].name

	gHighID = 4


#----------------------------------------------------------------------
# FUNCTION: makeSafeGlobals
#----------------------------------------------------------------------
def makeSafeGlobals():

	"""FUNCTION: makeSafeGlobals():
	This function returns a set of global variables that will be safe
	for POO coders to use.
	"""
	
	import md5
	import imp
	from time import time,ctime
	from qsplit import *
	from whrandom import randint
	import poohelp
	poohelp.hLoad()
	
	safebuiltins = imp.new_module('safebuiltins')

	exceptions = ['__import__','open','reload','unload','__name__',
		'execfile', 'globals']

	for name in __builtins__.keys():
	        if name not in exceptions:
			safebuiltins.__dict__[name] = __builtins__[name]
	safebuiltins.__dict__['super'] = None
	safebuiltins.__dict__['caller'] = None

	d = {	'getObj':getObj,
			'gCmdLine':gCmdLine,
			'__builtins__':safebuiltins,
			'create':create,
			'ctime':ctime,
			'destroy':destroy,
			'Directory':Directory,
			'Func':Func,
			'help':poohelp.help,
			'globalkeys':globalkeys,
			'marksub':marksub,
			'md5':md5,
			'move':move,
			'msg':msg,
			'Obj':Obj,
			'open':POOopen,
			'Prop':Prop,
			'randint':randint,
			'show':show,
			'string':string,
			'split':qsplit,
			'time':time,
			'tostr':tostr,
			'treasury':treasury,
			'transfer':transfer,
			'User':User,
			'users':users }

	for item in d.values():
		if type(item) == ModuleType and hasattr(item,'__builtins__'):
			item.__builtins__ = safebuiltins

	g = globals()
	for k in g.keys():
		if k[-4:] == "Type": d[k] = g[k]
	
	return d

#----------------------------------------------------------------------
# FUNCTION: initialize
#----------------------------------------------------------------------

def initialize(newDB=0):

	"""FUNCTION: initialize(newDB=0):
	This function sets up POO globals variables, loads files, etc.
	"""

	global gSafeGlobals
	gSafeGlobals = makeSafeGlobals()
	if newDB: newDatabase()
	else: gLoad()

#----------------------------------------------------------------------
#			----- end of poo.py -----
#----------------------------------------------------------------------