- implemented @destroy as per the MUX help specifications. As part of this, fixed the object recycling routines to actually properly replace GARBAGE-flagged objects (it crashed before).

- Set up a global cleaner event to clean all @destroyed objects every 30 minutes (makes their dbrefs available).
- Added the @recover command for recovering @destroyed objects up until the point that the cleaner runs and actually destroys them. This can recover @destroyed objects, rooms and exits to the same state as before @destroy. It could easily be made to recover player objects too, but I'm thinking this would be a security issue.
- Added to @dig in order to allow for creating rooms with a particular parent. Also auto-creates exits in each room if desired. The only things that is not implemented is the aliases of the exits, I don't really know how to do that.
- Changed the @create command format to match the @dig (it uses : to mark the parent instead of = now, since MUX' @dig reserve = to the exit list.)
- Added extra security in the example event to guard against the bug that causes the whole scheduler to freak out if the event_function() gives a traceback.
- Changed many instances of type to point to the defines_global.OTYPE instead of giving the integer explicitly.
/Starkiel
This commit is contained in:
Griatch 2009-04-30 15:01:59 +00:00
parent 8799a0fd55
commit 3eb4cddf42
8 changed files with 274 additions and 77 deletions

View file

@ -13,8 +13,8 @@ from src.events import IntervalEvent
from src.scheduler import add_event from src.scheduler import add_event
from src.objects.models import Object from src.objects.models import Object
#the logger is useful for debugging since there is no source object to send to #the logger is useful for debugging
from src.logger import log_infomsg from src.logger import log_errmsg
#Example of the event system. This example adds an event to the red_button parent #Example of the event system. This example adds an event to the red_button parent
#in parents/examples. It makes the button blink temptingly at a regular interval. #in parents/examples. It makes the button blink temptingly at a regular interval.
@ -46,7 +46,6 @@ class EventBlinkButton(IntervalEvent):
#stored with the gamesrc/parent/ drawer as a base) #stored with the gamesrc/parent/ drawer as a base)
parent = 'examples.red_button' parent = 'examples.red_button'
buttons = Object.objects.global_object_script_parent_search(parent) buttons = Object.objects.global_object_script_parent_search(parent)
#log_infomsg("buttons found: %s" % buttons)
for b in buttons: for b in buttons:
try: try:
@ -55,10 +54,10 @@ class EventBlinkButton(IntervalEvent):
#button has no blink() method. Just ignore. #button has no blink() method. Just ignore.
pass pass
except: except:
#show other tracebacks to owner of object. #show other tracebacks to log and owner of object.
#this is important, we must handle this exception #This is important, we must handle these exceptions gracefully!
#gracefully!
b.get_owner().emit_to(sys.exc_info()[1]) b.get_owner().emit_to(sys.exc_info()[1])
log_errmsg(sys.exc_info()[1])
#create and add the event to the global handler #create and add the event to the global handler
blink_event = EventBlinkButton() blink_event = EventBlinkButton()

View file

@ -6,7 +6,7 @@ gamesrc/parents and set SCRIPT_DEFAULT_OBJECT = 'custom_basicobject'
in game/settings.py. in game/settings.py.
Generally, if you want to conveniently set future objects to inherit from this Generally, if you want to conveniently set future objects to inherit from this
script parent (not as a default), this files and others like it need to be script parent, this files and others like it need to be
located under the game/gamesrc/parent directory. located under the game/gamesrc/parent directory.
""" """
from game.gamesrc.parents.base.basicobject import BasicObject from game.gamesrc.parents.base.basicobject import BasicObject

View file

@ -5,7 +5,7 @@ particular object. See example.py in gamesrc/commands for more info
on the pluggable command system. on the pluggable command system.
Assuming this script remains in gamesrc/parents/examples, create an object Assuming this script remains in gamesrc/parents/examples, create an object
of this type using @create button=examples.red_button of this type using @create button:examples.red_button
This file also shows the use of the Event system to make the button This file also shows the use of the Event system to make the button
send a message to the players at regular intervals. Note that if you create a send a message to the players at regular intervals. Note that if you create a

View file

@ -28,7 +28,7 @@ class CommandTable(object):
self.ctable = {} self.ctable = {}
def add_command(self, command_string, function, priv_tuple=None, def add_command(self, command_string, function, priv_tuple=None,
extra_vals=None, auto_help=False, staff_help=False): extra_vals=None, auto_help=False, staff_only=False):
""" """
Adds a command to the command table. Adds a command to the command table.
@ -57,7 +57,7 @@ class CommandTable(object):
#add automatic help text from the command's doc string #add automatic help text from the command's doc string
topicstr = command_string topicstr = command_string
entrytext = function.__doc__ entrytext = function.__doc__
add_help(topicstr, entrytext, staff_only=staff_help, add_help(topicstr, entrytext, staff_only=staff_only,
force_create=True, auto_help=True) force_create=True, auto_help=True)
def get_command_tuple(self, func_name): def get_command_tuple(self, func_name):

View file

@ -195,9 +195,9 @@ def cmd_set(command):
attrib_args = eq_args[1].split(':', 1) attrib_args = eq_args[1].split(':', 1)
if len(attrib_args) > 1: if len(attrib_args) > 1:
# We're dealing with an attribute/value pair. # We're dealing with an attribute/value pair.
attrib_name = attrib_args[0].upper() attrib_name = attrib_args[0]
splicenum = eq_args[1].find(':') + 1 splicenum = eq_args[1].find(':') + 1
attrib_value = eq_args[1][splicenum:] attrib_value = (eq_args[1][splicenum:]).strip()
# In global_defines.py, see NOSET_ATTRIBS for protected attribute names. # In global_defines.py, see NOSET_ATTRIBS for protected attribute names.
if not Attribute.objects.is_modifiable_attrib(attrib_name): if not Attribute.objects.is_modifiable_attrib(attrib_name):
@ -210,7 +210,10 @@ def cmd_set(command):
victim.set_attribute(attrib_name, attrib_value) victim.set_attribute(attrib_name, attrib_value)
else: else:
# No value was given, this means we delete the attribute. # No value was given, this means we delete the attribute.
verb = 'cleared' ok = victim.clear_attribute(attrib_name)
if ok: verb = 'attribute cleared'
else: verb = 'is not a known attribute. If it is a flag, use !flag to clear it'
victim.clear_attribute(attrib_name) victim.clear_attribute(attrib_name)
source_object.emit_to("%s - %s %s." % (victim.get_name(), attrib_name, verb)) source_object.emit_to("%s - %s %s." % (victim.get_name(), attrib_name, verb))
else: else:
@ -266,14 +269,14 @@ def cmd_create(command):
""" """
@create @create
Usage: @create objname [=parent] Usage: @create objname [:parent]
Creates a new object. If parent is given, the object is created as a child of this Creates a new object. If parent is given, the object is created as a child of this
parent. The parent script is assumed to be located under game/gamesrc/parents parent. The parent script is assumed to be located under game/gamesrc/parents
and any further directory structure is given in Python notation. So if you and any further directory structure is given in Python notation. So if you
have a correct parent object defined in parents/examples/red_button.py, you could have a correct parent object defined in parents/examples/red_button.py, you could
load create a new object inheriting from this parent like this: load create a new object inheriting from this parent like this:
@create button=example.red_button @create button:examples.red_button
""" """
source_object = command.source_object source_object = command.source_object
@ -281,13 +284,13 @@ def cmd_create(command):
source_object.emit_to("You must supply a name!") source_object.emit_to("You must supply a name!")
return return
eq_args = command.command_argument.split('=', 1) eq_args = command.command_argument.split(':', 1)
target_name = eq_args[0] target_name = eq_args[0]
# Create and set the object up. # Create and set the object up.
# TODO: This dictionary stuff is silly. Feex. # TODO: This dictionary stuff is silly. Feex.
odat = {"name": target_name, odat = {"name": target_name,
"type": 3, "type": defines_global.OTYPE_THING,
"location": source_object, "location": source_object,
"owner": source_object} "owner": source_object}
new_object = Object.objects.create_object(odat) new_object = Object.objects.create_object(odat)
@ -417,8 +420,8 @@ def cmd_open(command):
if len(eq_args) > 1: if len(eq_args) > 1:
# Opening an exit to another location via @open <Name>=<Dbref>[,<Name>]. # Opening an exit to another location via @open <Name>=<Dbref>[,<Name>].
comma_split = eq_args[1].split(',', 1) comma_split = eq_args[1].split(',', 1)
destination = source_object.search_for_object(comma_split[0])
# Use search_for_object to handle duplicate/nonexistant results. # Use search_for_object to handle duplicate/nonexistant results.
destination = source_object.search_for_object(comma_split[0])
if not destination: if not destination:
return return
@ -427,7 +430,7 @@ def cmd_open(command):
return return
odat = {"name": exit_name, odat = {"name": exit_name,
"type": 4, "type": defines_global.OTYPE_EXIT,
"location": source_object.get_location(), "location": source_object.get_location(),
"owner": source_object, "owner": source_object,
"home": destination} "home": destination}
@ -439,7 +442,7 @@ def cmd_open(command):
if len(comma_split) > 1: if len(comma_split) > 1:
second_exit_name = ','.join(comma_split[1:]) second_exit_name = ','.join(comma_split[1:])
odat = {"name": second_exit_name, odat = {"name": second_exit_name,
"type": 4, "type": defines_global.OTYPE_EXIT,
"location": destination, "location": destination,
"owner": source_object, "owner": source_object,
"home": source_object.get_location()} "home": source_object.get_location()}
@ -451,7 +454,7 @@ def cmd_open(command):
else: else:
# Create an un-linked exit. # Create an un-linked exit.
odat = {"name": exit_name, odat = {"name": exit_name,
"type": 4, "type": defines_global.OTYPE_EXIT,
"location": source_object.get_location(), "location": source_object.get_location(),
"owner": source_object, "owner": source_object,
"home": None} "home": None}
@ -644,26 +647,85 @@ GLOBAL_CMD_TABLE.add_command("@unlink", cmd_unlink,
def cmd_dig(command): def cmd_dig(command):
""" """
Creates a new object of type 'ROOM'. Creates a new room object.
@dig <Name> @dig[/teleport] roomname [:parent] [=exitthere,exithere]
""" """
source_object = command.source_object source_object = command.source_object
roomname = command.command_argument
args = command.command_argument
switches = command.command_switches
parent = ''
exits = []
#handle arguments
if ':' in args:
roomname, args = args.split(':',1)
if '=' in args:
parent, args = args.split('=',1)
if ',' in args:
exits = args.split(',',1)
else:
exits = args
else:
parent = args
elif '=' in args:
roomname, args = args.split('=',1)
if ',' in args:
exits = args.split(',',1)
else:
exits = [args]
else:
roomname = args
if not roomname: if not roomname:
source_object.emit_to("You must supply a name!") source_object.emit_to("You must supply a new room name.")
else: else:
# Create and set the object up. # Create and set the object up.
odat = {"name": roomname, odat = {"name": roomname,
"type": 2, "type": defines_global.OTYPE_ROOM,
"location": None, "location": None,
"owner": source_object} "owner": source_object}
new_object = Object.objects.create_object(odat) new_room = Object.objects.create_object(odat)
source_object.emit_to("Created a new room '%s'." % (new_room,))
source_object.emit_to("You create a new room: %s" % (new_object,)) if parent:
#(try to) set the script parent
if not new_room.set_script_parent(parent):
source_object.emit_to("%s is not a valid parent. Used default room." % parent)
if exits:
#create exits to (and possibly back from) the new room)
destination = new_room #search_for_object(roomname)
if destination and not destination.is_exit():
location = source_object.get_location()
#create an exit from here to the new room.
odat = {"name": exits[0].strip(),
"type": defines_global.OTYPE_EXIT,
"location": location,
"owner": source_object,
"home": destination}
new_object = Object.objects.create_object(odat)
source_object.emit_to("Created exit from %s to %s named '%s'." % (location,destination,new_object))
if len(exits)>1:
#create exit back to this room
odat = {"name": exits[1].strip(),
"type": defines_global.OTYPE_EXIT,
"location": destination,
"owner": source_object,
"home": location}
new_object = Object.objects.create_object(odat)
source_object.emit_to("Created exit back from %s to %s named '%s'" % (destination, location, new_object))
if 'teleport' in switches:
source_object.move_to(new_room)
GLOBAL_CMD_TABLE.add_command("@dig", cmd_dig, GLOBAL_CMD_TABLE.add_command("@dig", cmd_dig,
priv_tuple=("genperms.builder")) priv_tuple=("genperms.builder"),)
def cmd_name(command): def cmd_name(command):
""" """
@ -741,45 +803,144 @@ def cmd_description(command):
target_obj.set_description(new_desc) target_obj.set_description(new_desc)
GLOBAL_CMD_TABLE.add_command("@describe", cmd_description) GLOBAL_CMD_TABLE.add_command("@describe", cmd_description)
def cmd_recover(command):
"""
@recover
Recovers @destroyed non-player objects.
Usage:
@recover [dbref [,dbref2, etc]]
switches:
ROOM - recover as ROOM type instead of THING
EXIT - recover as EXIT type instead of THING
If no argument is given, a list of all recoverable objects will be given.
Objects scheduled for destruction with the @destroy command is cleaned out
by the game at regular intervals. Up until the time of the next cleanup you can
recover the object using this command (use @ps to check when the next cleanup is due).
Note that exits and objects in @destroyed rooms will not be automatically recovered
to its former state, you have to @recover those objects manually.
The object type is forgotten, so the object is returned as type ITEM if not the
switches /ROOM or /EXIT is given. Note that recovering an item as the wrong type will
most likely make it nonfunctional.
"""
source_object = command.source_object
args = command.command_argument
switches = command.command_switches
going_objects = Object.objects.filter(type__exact=defines_global.OTYPE_GOING)
if not args:
s = " Objects scheduled for destruction:"
if going_objects:
for o in going_objects:
s += '\n %s' % o
else:
s += " None."
source_object.emit_to(s)
return
if ',' in args:
objlist = args.split(',')
else:
objlist = [args]
for objname in objlist:
obj = Object.objects.list_search_object_namestr(going_objects, objname)
if len(obj) == 1:
if 'ROOM' in switches:
obj[0].type = defines_global.OTYPE_ROOM
source_object.emit_to("%s recovered as type ROOM." % obj[0])
elif 'EXIT' in switches:
obj[0].type = defines_global.OTYPE_EXIT
source_object.emit_to("%s recovered as type EXIT." % obj[0])
else:
obj[0].type = defines_global.OTYPE_THING
source_object.emit_to("%s recovered as type THING." % obj[0])
obj[0].save()
else:
source_object.emit_to("No (or multiple) matches for %s." % objname)
GLOBAL_CMD_TABLE.add_command("@recover", cmd_recover,
priv_tuple=("genperms.builder"),auto_help=True,staff_only=True)
def cmd_destroy(command): def cmd_destroy(command):
""" """
Destroy an object. @destroy
Destroys one or many objects.
Usage:
@destroy[/<switches>] obj [,obj2, obj3, ...]
switches:
override - The @destroy command will usually avoid accidentally destroying
player objects as well as objects with the SAFE flag. This
switch overrides this safety.
instant - Destroy the object immediately, without delay.
The objects are set to GOING and will be permanently destroyed next time the system
does cleanup. Until then non-player objects can still be saved by using the
@recover command. The contents of a room will be moved out before it is destroyed,
but its exits will also be destroyed. Note that player objects can not be recovered.
""" """
source_object = command.source_object source_object = command.source_object
switch_override = False args = command.command_argument
switches = command.command_switches
if not command.command_argument:
if not args:
source_object.emit_to("Destroy what?") source_object.emit_to("Destroy what?")
return return
if ',' in args:
targetlist = args.split(',')
else:
targetlist = [args]
# Safety feature. Switch required to delete players and SAFE objects. # Safety feature. Switch required to delete players and SAFE objects.
if "override" in command.command_switches: switch_override = False
if "override" in switches:
switch_override = True switch_override = True
for targetname in targetlist:
target_obj = source_object.search_for_object(targetname)
# Use search_for_object to handle duplicate/nonexistant results.
if not target_obj:
return
if target_obj.is_player() or target_obj.has_flag('SAFE'):
if source_object.id == target_obj.id:
source_object.emit_to("You can't destroy yourself.")
return
if not switch_override:
source_object.emit_to("You must use @destroy/override on Players and objects with the SAFE flag set.")
return
if target_obj.is_superuser():
source_object.emit_to("You can't destroy a superuser.")
return
elif target_obj.is_garbage():
source_object.emit_to("That object is already destroyed.")
return
elif target_obj.is_going() and 'instant' not in switches:
source_object.emit_to("That object is already scheduled for destruction.")
return
# Run any scripted things that happen before destruction.
target_obj.scriptlink.at_object_destruction(pobject=source_object)
#destroy the object (sets it to GOING)
target_obj.destroy()
if 'instant' in switches:
#sets to GARBAGE right away (makes dbref available)
target_obj.delete()
source_object.emit_to("You destroy %s." % target_obj.get_name())
else:
source_object.emit_to("You schedule %s for destruction." % target_obj.get_name())
target_obj = source_object.search_for_object(command.command_argument)
# Use search_for_object to handle duplicate/nonexistant results.
if not target_obj:
return
if target_obj.is_player():
if source_object.id == target_obj.id:
source_object.emit_to("You can't destroy yourself.")
return
if not switch_override:
source_object.emit_to("You must use @destroy/override on players.")
return
if target_obj.is_superuser():
source_object.emit_to("You can't destroy a superuser.")
return
elif target_obj.is_going() or target_obj.is_garbage():
source_object.emit_to("That object is already destroyed.")
return
# Run any scripted things that happen before destruction.
target_obj.scriptlink.at_object_destruction(pobject=source_object)
# Notify destroyer and do the deed.
source_object.emit_to("You destroy %s." % target_obj.get_name())
target_obj.destroy()
GLOBAL_CMD_TABLE.add_command("@destroy", cmd_destroy, GLOBAL_CMD_TABLE.add_command("@destroy", cmd_destroy,
priv_tuple=("genperms.builder")) priv_tuple=("genperms.builder"),auto_help=True,staff_only=True)

View file

@ -9,6 +9,8 @@ import time
from twisted.internet import task from twisted.internet import task
import session_mgr import session_mgr
from src import scheduler from src import scheduler
from src import defines_global
from src.objects.models import Object
class IntervalEvent(object): class IntervalEvent(object):
""" """
@ -71,6 +73,8 @@ class IntervalEvent(object):
self.set_lastfired() self.set_lastfired()
self.event_function() self.event_function()
class IEvt_Check_Sessions(IntervalEvent): class IEvt_Check_Sessions(IntervalEvent):
""" """
Event: Check all of the connected sessions. Event: Check all of the connected sessions.
@ -87,10 +91,30 @@ class IEvt_Check_Sessions(IntervalEvent):
""" """
session_mgr.check_all_sessions() session_mgr.check_all_sessions()
class IEvt_Destroy_Objects(IntervalEvent):
"""
Event: Clean out all objects marked for destruction.
"""
def __init__(self):
super(IEvt_Destroy_Objects, self).__init__()
self.name = 'IEvt_Destroy_Objects'
self.interval = 1800
self.description = "Destroy objects with the GOING flag set."
def event_function(self):
"""
This is the function that is fired every self.interval seconds.
"""
going_objects = Object.objects.filter(type__exact=defines_global.OTYPE_GOING)
for obj in going_objects:
obj.delete()
def add_global_events(): def add_global_events():
""" """
When the server is started up, this is triggered to add all of the When the server is started up, this is triggered to add all of the
events in this file to the scheduler. events in this file to the scheduler.
""" """
# Create an instance and add it to the scheduler. # Create an instance and add it to the scheduler.
scheduler.add_event(IEvt_Check_Sessions()) scheduler.add_event(IEvt_Check_Sessions())
scheduler.add_event(IEvt_Destroy_Objects())

View file

@ -60,7 +60,7 @@ class ObjectManager(models.Manager):
nextfree = self.filter(type__exact=defines_global.OTYPE_GARBAGE) nextfree = self.filter(type__exact=defines_global.OTYPE_GARBAGE)
if nextfree: if nextfree:
# We've got at least one garbage object to recycle. # We've got at least one garbage object to recycle.
return nextfree.id return nextfree[0]
else: else:
# No garbage to recycle, find the highest dbnum and increment it # No garbage to recycle, find the highest dbnum and increment it
# for our next free. # for our next free.
@ -74,15 +74,16 @@ class ObjectManager(models.Manager):
o_query = self.filter(name__iexact=ostring) o_query = self.filter(name__iexact=ostring)
else: else:
o_query = self.filter(name__icontains=ostring) o_query = self.filter(name__icontains=ostring)
return o_query.exclude(type__in=[defines_global.OTYPE_GARBAGE, return o_query.exclude(type__in=[defines_global.OTYPE_GARBAGE,
defines_global.OTYPE_GOING]) defines_global.OTYPE_GOING])
def global_object_script_parent_search(self, script_parent): def global_object_script_parent_search(self, script_parent):
""" """
Searches through all objects returning those which has a certain script parent. Searches through all objects returning those which has a certain script parent.
""" """
o_query = self.filter(script_parent__exact=script_parent) o_query = self.filter(script_parent__exact=script_parent)
return o_query.exclude(type__in=[defines_global.OTYPE_GARBAGE, return o_query.exclude(type__in=[defines_global.OTYPE_GARBAGE,
defines_global.OTYPE_GOING]) defines_global.OTYPE_GOING])
@ -107,6 +108,7 @@ class ObjectManager(models.Manager):
else: else:
return [prospect for prospect in searchlist if prospect.name_match(ostring, match_type=match_type)] return [prospect for prospect in searchlist if prospect.name_match(ostring, match_type=match_type)]
def object_totals(self): def object_totals(self):
""" """
Returns a dictionary with database object totals. Returns a dictionary with database object totals.
@ -121,6 +123,8 @@ class ObjectManager(models.Manager):
} }
return dbtotals return dbtotals
def player_alias_search(self, searcher, ostring): def player_alias_search(self, searcher, ostring):
""" """
Search players by alias. Returns a list of objects whose "ALIAS" Search players by alias. Returns a list of objects whose "ALIAS"
@ -136,7 +140,7 @@ class ObjectManager(models.Manager):
model="attribute").model_class() model="attribute").model_class()
results = Attribute.objects.select_related().filter(attr_name__exact="ALIAS").filter(attr_value__iexact=ostring) results = Attribute.objects.select_related().filter(attr_name__exact="ALIAS").filter(attr_value__iexact=ostring)
return [prospect.get_object() for prospect in results if prospect.get_object().is_player()] return [prospect.get_object() for prospect in results if prospect.get_object().is_player()]
def player_name_search(self, search_string): def player_name_search(self, search_string):
""" """
Combines an alias and global search for a player's name. If there are Combines an alias and global search for a player's name. If there are
@ -263,19 +267,26 @@ class ObjectManager(models.Manager):
* home: Reference to another object to home to. If not specified, use * home: Reference to another object to home to. If not specified, use
location key for home. location key for home.
""" """
#get_nextfree_dbnum() returns either an integer or an object to recycle.
next_dbref = self.get_nextfree_dbnum() next_dbref = self.get_nextfree_dbnum()
Object = ContentType.objects.get(app_label="objects",
model="object").model_class()
new_object = Object()
new_object.id = next_dbref if type(next_dbref) == type(int()):
new_object.type = odat["type"] #create object with new dbref
Object = ContentType.objects.get(app_label="objects",
model="object").model_class()
new_object = Object()
new_object.id = next_dbref
else:
#recycle an old object's dbref
new_object = next_dbref
new_object.type = odat["type"]
new_object.set_name(odat["name"]) new_object.set_name(odat["name"])
# If this is a player, we don't want him owned by anyone. # If this is a player, we don't want him owned by anyone.
# The get_owner() function will return that the player owns # The get_owner() function will return that the player owns
# himself. # himself.
if odat["type"] == 1: if odat["type"] == defines_global.OTYPE_PLAYER:
new_object.owner = None new_object.owner = None
new_object.zone = None new_object.zone = None
else: else:

View file

@ -502,7 +502,7 @@ class Object(models.Model):
% (self,)) % (self,))
# Set the object type to GOING # Set the object type to GOING
self.type = 5 self.type = defines_global.OTYPE_GOING
# Destroy any exits to and from this room, do this first # Destroy any exits to and from this room, do this first
self.clear_exits() self.clear_exits()
# Clear out any objects located within the object # Clear out any objects located within the object
@ -519,7 +519,7 @@ class Object(models.Model):
uobj[0].delete() uobj[0].delete()
# Set the object to type GARBAGE. # Set the object to type GARBAGE.
self.type = 6 self.type = defines_global.OTYPE_GARBAGE
self.save() self.save()
# Clear all attributes # Clear all attributes
@ -530,8 +530,8 @@ class Object(models.Model):
Destroys all of the exits and any exits pointing to this Destroys all of the exits and any exits pointing to this
object as a destination. object as a destination.
""" """
exits = self.get_contents(filter_type=4) exits = self.get_contents(filter_type=defines_global.OTYPE_EXIT)
exits += self.obj_home.all().filter(type__exact=4) exits += self.obj_home.all().filter(type__exact=defines_global.OTYPE_EXIT)
for exit in exits: for exit in exits:
exit.destroy() exit.destroy()
@ -781,6 +781,7 @@ class Object(models.Model):
parent_str: (string) String pythonic import path of the script parent parent_str: (string) String pythonic import path of the script parent
assuming the python path is game/gamesrc/parents. assuming the python path is game/gamesrc/parents.
""" """
if parent_str == None: if parent_str == None:
if self.is_player(): if self.is_player():
self.script_parent = settings.SCRIPT_DEFAULT_PLAYER self.script_parent = settings.SCRIPT_DEFAULT_PLAYER
@ -789,9 +790,10 @@ class Object(models.Model):
elif parent_str: elif parent_str:
#check if this is actually a reasonable script parent #check if this is actually a reasonable script parent
#(storing with a non-valid parent path causes havoc!) #(storing with a non-valid parent path causes havoc!)
parent_str = str(parent_str).strip()
if not scripthandler.scriptlink(self, parent_str): if not scripthandler.scriptlink(self, parent_str):
return False return False
self.script_parent = parent_str.strip() self.script_parent = parent_str
self.save() self.save()
return True return True