~certify-web-dev/twisted/certify-trunk

« back to all changes in this revision

Viewing changes to twisted/reality/thing.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2004-06-21 22:01:11 UTC
  • mto: (2.2.3 sid)
  • mto: This revision was merged to the branch mainline in revision 3.
  • Revision ID: james.westby@ubuntu.com-20040621220111-vkf909euqnyrp3nr
Tags: upstream-1.3.0
ImportĀ upstreamĀ versionĀ 1.3.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
 
2
 
# Twisted, the Framework of Your Internet
3
 
# Copyright (C) 2001 Matthew W. Lefkowitz
4
 
5
 
# This library is free software; you can redistribute it and/or
6
 
# modify it under the terms of version 2.1 of the GNU Lesser General Public
7
 
# License as published by the Free Software Foundation.
8
 
9
 
# This library is distributed in the hope that it will be useful,
10
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12
 
# Lesser General Public License for more details.
13
 
14
 
# You should have received a copy of the GNU Lesser General Public
15
 
# License along with this library; if not, write to the Free Software
16
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
 
 
18
 
"""Basic twisted.reality simulation classes.
19
 
"""
20
 
 
21
 
# System Imports.
22
 
import types
23
 
import copy
24
 
import string
25
 
 
26
 
import cStringIO
27
 
StringIO = cStringIO
28
 
del cStringIO
29
 
 
30
 
# Sibling Imports
31
 
import error
32
 
import sentence
33
 
import source
34
 
 
35
 
# Twisted Imports
36
 
from twisted.python import observable, reflect
37
 
from twisted.protocols import protocol
38
 
from twisted.persisted import styles
39
 
# backward compatibility
40
 
#import twisted.reality
41
 
 
42
 
#the default reality which things will use.
43
 
_default = None
44
 
 
45
 
class Event(reflect.Settable):
46
 
    """Event()
47
 
    Essentially a dicitonary -- this encapsulates the idea of an event
48
 
    which can be published."""
49
 
 
50
 
class Ambiguous:
51
 
    """A dictionary which may contain ambiguous entries.
52
 
    """
53
 
    def __init__(self):
54
 
        """Initialize.
55
 
        """
56
 
        self.data = {}
57
 
 
58
 
    def put(self, key,value):
59
 
        """Put a value into me with the given key.
60
 
        If I already have a reference to that key, further retrievals will be ambiguous.
61
 
        """
62
 
        if type(key)==types.StringType:
63
 
            key = string.lower(key)
64
 
        try:
65
 
            x = self.data[key]
66
 
            x.append(value)
67
 
        except:
68
 
            self.data[key] = [value]
69
 
 
70
 
        self.data = self.data
71
 
 
72
 
    def get(self,key):
73
 
        """Retrieve a value.
74
 
        
75
 
        This always returns a list; if it is empty, then I do not contain the
76
 
        given key.  If it has one entry, then the retrieval is not ambiguous.
77
 
        If it has more than one, the retrieval is ambiguous.
78
 
        """
79
 
        if type(key)==types.StringType:
80
 
            key = string.lower(key)
81
 
        x = self.data.get(key, [])
82
 
        return x
83
 
 
84
 
    def remove(self,key,value):
85
 
        """Remove one value for a particular key.
86
 
        If there are multiple entries under this key, disambiguate.  Otherwise,
87
 
        remove this key entirely.
88
 
        """
89
 
        if type(key)==types.StringType:
90
 
            key = string.lower(key)
91
 
        x = self.data[key]
92
 
        x.remove(value)
93
 
        if len(x)==0:
94
 
            del self.data[key]
95
 
        self.data = self.data
96
 
 
97
 
class Thing(observable.Publisher,
98
 
            observable.Subscriber,
99
 
            reflect.Settable):
100
 
    """Thing(name[,reality])
101
 
 
102
 
    name: a string, which will be both the unique keyed name of this
103
 
    object, and the displayed name.
104
 
 
105
 
    reality: an instance of a twisted.reality.Reality, or None.  If it
106
 
    is unspecified, the reality of this object will be set to the
107
 
    value of the module-global '_default'.
108
 
    """
109
 
 
110
 
    # Version: this is independant of the twisted reality version; it
111
 
    # is important so that the pickles can be re-read and frobbed as
112
 
    # necessary
113
 
    __version = 4
114
 
    # Most rooms aren't doors; they don't "go" anywhere.
115
 
    destination = None
116
 
    # most things are opaque.
117
 
    transparent = 0
118
 
 
119
 
    # most things aren't containers; they don't have an "inside", to
120
 
    # speak of.  This isn't a hard and fast rule, since it's useful to
121
 
    # be able to combine objects in this relationship, but this is a
122
 
    # useful hint.
123
 
    hollow=0
124
 
 
125
 
    # See the "room" classes below for more details.
126
 
    enterable = 0
127
 
    # what 'reality' this belongs to.
128
 
    reality = None
129
 
    # whatever this player last "talked about"; it will be used when
130
 
    # they refer to 'it'
131
 
    antecedent = None
132
 
    # if you're performing actions, are you an administrator?
133
 
    wizbit=0
134
 
 
135
 
    def __init__(self, name, reality=''):
136
 
        """Thing(name [, reality])
137
 
 
138
 
        Initialize a Thing.
139
 
        """
140
 
        # State setup.
141
 
        self.__index = Ambiguous()
142
 
        self.__version = self.__version
143
 
        self.__description = {"__MAIN__":""}
144
 
        self.__exits = {}
145
 
        self.__synonyms = []
146
 
        self._containers = []
147
 
        self._contained = []
148
 
 
149
 
        # More state setup, these with constraints.
150
 
        self.name = str(name)
151
 
        if reality == '':
152
 
            self.reality = _default
153
 
        else:
154
 
            self.reality = reality
155
 
 
156
 
    def set_reality(self, reality):
157
 
        if self.reality is reality:
158
 
            return
159
 
        if self.reality is not None:
160
 
            self.reality._removeThing(self)
161
 
        if reality is not None:
162
 
            reality._addThing(self)
163
 
        self.reallySet('reality',reality)
164
 
        for thing in self.things:
165
 
            thing.reality = reality
166
 
 
167
 
 
168
 
    # The 'true name' string of this object.
169
 
    name=None
170
 
    # The name of this object that gets displayed when players look at
171
 
    # it.
172
 
    displayName=None
173
 
 
174
 
    def set_name(self, name):
175
 
        """Thing.name = name
176
 
 
177
 
        Set the name of this Thing.
178
 
        """
179
 
        assert type(name)==types.StringType, "Name must be a string."
180
 
        if name == self.name:
181
 
            return
182
 
        if self.name is not None:
183
 
            self.removeSynonym(self.name)
184
 
        oldname=self.name
185
 
        self.reallySet('name',name)
186
 
        if self.reality is not None:
187
 
            self.reality._updateName(self,oldname,name)
188
 
        self.addSynonym(name)
189
 
        if not self.displayName:
190
 
            self.publish('name',self)
191
 
 
192
 
    def set_displayName(self, name):
193
 
        """Thing.displayName = name
194
 
        Change the displayed name of this thing.  """
195
 
        if type(name)==types.StringType:
196
 
            self.addSynonym(name)
197
 
        if self.displayName and type(self.displayName) == types.StringType:
198
 
            self.removeSynonym(self.displayName)
199
 
        self.reallySet('displayName',name)
200
 
        self.publish('name',self)
201
 
 
202
 
    def on_name(self, data):
203
 
        """Thing.on_name(data) -> None
204
 
        Called when 'name' is published, to upate the visible name of
205
 
        this object in all of its containers.
206
 
        """
207
 
        self.__changeVisibility(1)
208
 
 
209
 
    ### Perspectives
210
 
 
211
 
    article=None
212
 
 
213
 
    def indefiniteArticle(self, observer):
214
 
        """Thing.indefiniteArticle(observer) -> 'a ' or 'an '
215
 
 
216
 
        Utility function which figures out the appropriate indefinite
217
 
        article to display based to the given observer based on the
218
 
        short name displayed to that observer.  """
219
 
        name=self.shortName(observer)
220
 
        if string.lower(name[0]) in ('a','e','i','o','u'):
221
 
            return 'an '
222
 
        else: return 'a '
223
 
 
224
 
    def definiteArticle(self, observer):
225
 
        """Thing.definiteArticle(observer) -> 'the '
226
 
 
227
 
        Returns 'the' or nothing, depending on whether or not it would be
228
 
        appropriate to label this object with a definite article.  """
229
 
        return 'the '
230
 
 
231
 
    aan=indefiniteArticle
232
 
    the=definiteArticle
233
 
 
234
 
    unique=0
235
 
 
236
 
 
237
 
    def article(self, observer):
238
 
        """Thing.article(observer) -> 'a ' or 'an ' or 'the '
239
 
 
240
 
        Determine the article of an object.
241
 
        """
242
 
        return self.definiteArticle(observer)
243
 
 
244
 
    def capNounPhrase(self,observer):
245
 
        "The capitalized version of this object's noun phrase."
246
 
        return string.capitalize(self.nounPhrase(observer))
247
 
 
248
 
    def nounPhrase(self, observer):
249
 
        """Thing.nounPhrase(observer) -> string
250
 
        A brief phrase describing this object, such as 'the blue fish' or
251
 
        'a bicycle'."""
252
 
        return self.article(observer)+self.shortName(observer)
253
 
 
254
 
 
255
 
    contained_preposition = "on"
256
 
 
257
 
    def containedPhrase(self, observer, containedThing):
258
 
        """Thing.containedPhrase(observer, containedThing) -> 'A containedThing is in the self'
259
 
        
260
 
        This returns a phrase which relates self's relationship to
261
 
        containedThing.  It's used to calculate containedThing's presentPhrase
262
 
        to the given observer. """
263
 
 
264
 
        return "%s is %s %s." % (string.capitalize(containedThing.nounPhrase(observer)),
265
 
                                self.contained_preposition,
266
 
                                self.nounPhrase(observer))
267
 
                
268
 
    def presentPhrase(self, observer):
269
 
        """Thing.presentPhrase(observer) -> 'A self is in the box', 'Bob is holding the self'
270
 
        
271
 
        A brief phrase describing the presence and status of this object, such
272
 
        as 'A fish is here' or 'A bicycle is leaning against the rack.'.  """
273
 
        
274
 
        location = self.location
275
 
        if location is None:
276
 
            return "%s%s is nowhere" % self.indefiniteArticle(observer)
277
 
        
278
 
        return location.containedPhrase(observer, self)
279
 
 
280
 
 
281
 
    def shortName(self, observer):
282
 
        """Thing.shortName(observer) -> 'blue box'
283
 
        The unadorned name of this object, as it appears to the specified
284
 
        observer.  """
285
 
        
286
 
        displayName = self.displayName
287
 
        if displayName:
288
 
            if callable(displayName):
289
 
                return displayName(observer)
290
 
            else:
291
 
                return displayName
292
 
        else:
293
 
            return self.name
294
 
 
295
 
    def get_things(self):
296
 
        "Thing.things -> a list of the contents of this object."
297
 
        return tuple(self._contained)
298
 
 
299
 
    def get_allThings(self):
300
 
        """Thing.allThings -> a complete list of contents for this object.
301
 
        
302
 
        This is the list of contents of an object, plus any objects in surfaces
303
 
        which are visible from it.
304
 
        """
305
 
        stuff = {}
306
 
        for thing in self.things:
307
 
            if not thing.component:
308
 
                stuff[thing] = None
309
 
            if thing.surface:
310
 
                for thing2 in thing.allThings:
311
 
                    stuff[thing2] = None
312
 
        return stuff.keys()
313
 
 
314
 
    def getVisibleThings(self, observer):
315
 
        """Thing.getVisibleThings(observer) -> list of Things
316
 
        This function will return all things which this thing contains (obvious
317
 
        or not) which are visible to the player"""
318
 
        # of course, invisibility isn't implemented yet.
319
 
        return self.allThings
320
 
    
321
 
    def getThings(self, observer):
322
 
        """Thing.getThings(observer) -> list of Things
323
 
        This function filters out the objects which are not obvious to the
324
 
        observer.  """
325
 
        things = []
326
 
        for thing in self.allThings:
327
 
            if thing.isObviousTo(observer):
328
 
                things.append(thing)
329
 
        
330
 
        return things
331
 
 
332
 
    gender='n'
333
 
 
334
 
    def __gender(self, observer):
335
 
        """(private) Thing.__gender(observer) -> 'm', 'f' or 'n'
336
 
        what gender this object appears to be to the given observer.
337
 
        """
338
 
        if callable(self.gender):
339
 
            return self.gender(observer)
340
 
        else:
341
 
            return self.gender
342
 
 
343
 
    def himHer(self, observer):
344
 
        """Thing.himHer(observer) -> 'him', 'her', or 'it'
345
 
 
346
 
        returns 'him' 'her' or 'it' depending on the perceived gender
347
 
        of this object. """
348
 
        return {'m':'him','f':'her','n':'it'}[self.__gender(observer)]
349
 
 
350
 
    def capHimHer(self, observer):
351
 
        """Thing.capHimHer(observer) 'Him', 'Her', or 'It'
352
 
 
353
 
        see Thing.himHer()"""
354
 
        return string.capitalize(self.himHer(observer))
355
 
 
356
 
    def hisHer(self, observer):
357
 
        """Thing.hisHer(observer) -> 'his', 'her', or 'its'
358
 
 
359
 
        returns 'his', 'her', or 'its', depending on the perceived
360
 
        gender of this object """
361
 
        return {'m':'his','f':'her','n':'its'}[self.__gender(observer)]
362
 
 
363
 
    def capHisHer(self, observer):
364
 
        """Thing.capHisHer(observer) 'His', 'Her', or 'Its'
365
 
 
366
 
        see Thing.hisHer()"""
367
 
        return string.capitalize(self.hisHer(observer))
368
 
 
369
 
    def heShe(self, observer):
370
 
        """Thing.heShe(observer) -> 'he', 'she', or 'it'
371
 
 
372
 
        returns 'he', 'she', or 'it', depending on the perceived
373
 
        gender of this object """
374
 
        return {'m':'he','f':'she','n':'it'}[self.__gender(observer)]
375
 
 
376
 
    def capHeShe(self, observer):
377
 
        """Thing.capHeShe(observer) -> 'his', 'her', or 'its'
378
 
 
379
 
        returns 'he', 'she', or 'it', depending on the perceived
380
 
        gender of this object """
381
 
        return string.capitalize(self.heShe(observer))
382
 
 
383
 
    def format(self, persplist):
384
 
        """Thing.format(list of things and strings) -> string
385
 
 
386
 
        Renders a list which represents a phrase based on this object
387
 
        as the observer.  For example:
388
 
 
389
 
        x = Thing('mop')
390
 
        y = Player('bob')
391
 
        print y.format(['You dance with ',mop.nounPhrase,'.'])
392
 
 
393
 
        would yeild: 'You dance with the mop.' """
394
 
 
395
 
        if type(persplist)==types.StringType:
396
 
            return persplist
397
 
        elif type(persplist)==types.NoneType:
398
 
            return ""
399
 
        elif callable(persplist):
400
 
            return persplist(self)
401
 
        else:
402
 
            persplist = list(persplist)
403
 
        if persplist and reflect.isinst(persplist[0],Thing):
404
 
            persplist[0] = persplist[0].capNounPhrase
405
 
        x = StringIO.StringIO()
406
 
        for i in persplist:
407
 
            if reflect.isinst(i,Thing):
408
 
                val=i.nounPhrase(self)
409
 
            elif callable(i):
410
 
                # this could be one of the things we just defined (wrt
411
 
                # gender) or it could be an observable.Dynamic
412
 
                val=i(self)
413
 
            else:
414
 
                val=str(i)
415
 
            x.write(val)
416
 
        return x.getvalue()
417
 
 
418
 
    def hears(self, *args):
419
 
        """Thing.hears(*list of perceptibles) -> None
420
 
 
421
 
        Causes this Player to hear this list as formatted by
422
 
        self.format()
423
 
        """
424
 
        string=self.format(args)
425
 
        if self._hasIntelligence():
426
 
            self.intelligence.seeEvent(string)
427
 
 
428
 
    def broadcast(self, *evt):
429
 
        """Thing.broadcast(...) -> None
430
 
        Display some event text to all who can see this item."""
431
 
        for container in self._containers:
432
 
            apply(container.allHear, evt)
433
 
 
434
 
    def broadcastToPair(self, target,
435
 
                        to_subject, to_target, to_other):
436
 
        """Thing.broadcastToPair(target, to_subject,
437
 
                                 to_target, to_other) -> None
438
 
 
439
 
        Broadcast some event text to all who can see this actor and
440
 
        the specified target (in the style of pairHears).  Prefer this
441
 
        form, as it will deal with the actor and target having
442
 
        multiple and/or separate locations."""
443
 
        contList = self._containers
444
 
        if target is not None:
445
 
            contList = contList + target._containers
446
 
        contUniq = {}
447
 
        for container in contList:
448
 
            contUniq[container] = 1
449
 
        for container in contUniq.keys():
450
 
            container.pairHears(self, target, to_subject, to_target, to_other)
451
 
 
452
 
 
453
 
    def broadcastToOne(self, to_subject, to_other):
454
 
        """Thing.broadcastToOne(to_subject, to_other) -> None
455
 
 
456
 
        Broadcast some event text to all who can see this subject (in the style
457
 
        of oneHears).  Prefer this form, as it will deal with the actor and
458
 
        target having multiple and/or separate locations.
459
 
        """
460
 
        
461
 
        self.broadcastToPair(target=None,
462
 
                             to_subject=to_subject,
463
 
                             to_target=(),
464
 
                             to_other=to_other)
465
 
                       
466
 
 
467
 
 
468
 
    def pairHears(self,
469
 
                  subject,
470
 
                  target,
471
 
                  to_subject,
472
 
                  to_target,
473
 
                  to_other):
474
 
        """Thing.pairHears(self, subject, target, to_subject,
475
 
                           to_target, to_other)
476
 
 
477
 
        Sends a message to a list of 3 parties - an initiator of an
478
 
        action, a target of that action, and everyone else observing
479
 
        that action.  The messages to each party may be a single
480
 
        formattable object, or a list or tuple of such objects, which
481
 
        will be formatted by the 'hears' method on each observer.
482
 
 
483
 
        Example:
484
 
        room.pairHears(sentence.subject, target,
485
 
        to_subject=("You wave to ",target," and ",target.heShe," nods."),
486
 
        to_target= (sentence.subject," waves to you, and you nod."),
487
 
        to_other=(sentence.subject," waves to ",target," and ",target.heShe," nods.")
488
 
        )
489
 
 
490
 
        In this example, when bob executes "wave to jethro", the
491
 
        effect is:
492
 
 
493
 
        BOB hears:           "You wave to Jethro, and he nods."
494
 
        JETHRO hears:        "Bob waves to you, and you nod."
495
 
        EVERYONE ELSE hears: "Bob waves to Jethro, and he nods."
496
 
 
497
 
        This sort of interaction is useful almost anywhere a verb
498
 
        enables a player to take action on another player.
499
 
        """
500
 
 
501
 
        if type(to_subject) not in (types.TupleType,types.ListType):
502
 
            to_subject = (to_subject,)
503
 
        if type(to_target)  not in (types.TupleType,types.ListType):
504
 
            to_target  = (to_target,)
505
 
        if type(to_other)   not in (types.TupleType,types.ListType):
506
 
            to_other   = (to_other,)
507
 
        if subject is not None:
508
 
            apply(subject.hears,to_subject)
509
 
        if target is not None:
510
 
            apply(target.hears,to_target)
511
 
 
512
 
        map(lambda x, to_other=to_other: apply(x.hears,to_other),
513
 
            filter(lambda x, subject=subject, target=target: x not in (subject,target),
514
 
                   self.things)
515
 
            )
516
 
 
517
 
    def allHear(self, *args):
518
 
        """Thing.allHear(*list to be formatted)
519
 
 
520
 
        Sends a message to everyone in the room.  This method
521
 
        should be used when there isn't a specific Player who is the
522
 
        source of the message. (an example would be Gate disappearing).
523
 
 
524
 
        Its arguments should be in the same form as 'hear'
525
 
 
526
 
        Examples: room.allHear(disembodied_voice," says 'hello'.")
527
 
                  room.allHear("Nothing happens here.")
528
 
        """
529
 
        for thing in self.things:
530
 
            apply(thing.hears,args)
531
 
 
532
 
    def oneHears(self, subject, to_subject, to_other):
533
 
        """Thing.oneHears(subject, to_subject, to_other)
534
 
 
535
 
        Sends a one to everyone in the room except one player, to whom
536
 
        a different message is sent.  The arguments may be a valid
537
 
        formattable object (a String, Thing, Dynamic, or Exception) or
538
 
        a list or tuple of formattable objects.
539
 
 
540
 
        Example: room.oneHears(sentence.subject,
541
 
                                to_subject=("You pull on ",knob,
542
 
                                            " but it just sits there.")
543
 
                                to_other=(sentence.subject," pulls on ",knob,
544
 
                                          " and looks rather foolish."))
545
 
        """
546
 
        if type(to_subject) not in (types.TupleType,types.ListType):
547
 
            to_subject = (to_subject,)
548
 
        if type(to_other)   not in (types.TupleType,types.ListType):
549
 
            to_other   = (to_other,)
550
 
 
551
 
        apply(subject.hears, to_subject)
552
 
        for thing in self.things:
553
 
            if thing is not subject:
554
 
                apply(thing.hears, to_other)
555
 
 
556
 
    def _hasIntelligence(self):
557
 
        return hasattr(self,"intelligence")
558
 
 
559
 
    def del_focus(self):
560
 
        """del Thing.focus
561
 
        stop observing the object that you're focused on."""
562
 
        self.reallyDel('focus')
563
 
        if self._hasIntelligence():
564
 
            self.intelligence.seeNoItems()
565
 
            self.intelligence.seeNoDescriptions()
566
 
            self.intelligence.seeNoExits()
567
 
 
568
 
    def isObviousTo(self, player):
569
 
        """Thing.isObviousTo(player)
570
 
        This returns whether or not this item should appear in
571
 
        inventory listings.  It's different from "visible" because a
572
 
        player presumably wouldn't be able to interact very well with
573
 
        invisible objects, but certain objects that are part of the
574
 
        room's description may be non-obvious."""
575
 
        return (not self.component) and (self is not player)
576
 
 
577
 
 
578
 
    def _fullUpdateTo(self,player):
579
 
        """(internal) Thing._fullUpdateTo(player) -> None
580
 
        Update a player's UI regarding the state of this object."""
581
 
        i = player.intelligence
582
 
        i.seeName(self.shortName(player))
583
 
        for key, value in self.__description.items():
584
 
            i.seeDescription(key,self.format(value))
585
 
        for direction, exit in self.__exits.items():
586
 
            i.seeExit(direction,exit)
587
 
        for item in self.getThings(player):
588
 
            i.seeItem(item, item.presentPhrase(player))
589
 
 
590
 
    def descriptionTo(self, player):
591
 
        return string.join(map(player.format,self.__description.values()))
592
 
 
593
 
    def set_focus(self, focus):
594
 
        """Thing.focus = newfocus
595
 
        begin observing another object"""
596
 
        assert focus is None or reflect.isinst(focus, Thing),\
597
 
               "Focus must be a Thing, or None"
598
 
        if hasattr(self,'focus'):
599
 
            del self.focus
600
 
        self.reallySet('focus',focus)
601
 
        if self._hasIntelligence() and reflect.isinst(focus,Thing):
602
 
            focus._fullUpdateTo(self)
603
 
 
604
 
    def when_focus_description(self, focus, channel,
605
 
                               (key, value)):
606
 
        if self._hasIntelligence():
607
 
            if value:
608
 
                self.intelligence.seeDescription(key, self.format(value))
609
 
            else:
610
 
                self.intelligence.dontSeeDescription(key)
611
 
 
612
 
    def when_focus_name(self,focus,channel,changed):
613
 
        "Hook called when our focus publishes a new name."
614
 
        if self._hasIntelligence():
615
 
            if focus is changed:
616
 
                self.intelligence.seeName(changed.shortName(self))
617
 
            else:
618
 
                if changed is not self:
619
 
                    self.intelligence.seeItem(changed,
620
 
                                              changed.presentPhrase(self))
621
 
 
622
 
    def when_focus_exit(self, focus, channel, exit):
623
 
        "Hook called when exits are added to or removed from my focus"
624
 
        if self._hasIntelligence():
625
 
            if exit.destination:
626
 
                self.intelligence.seeExit(exit.direction,exit.destination)
627
 
            else:
628
 
                self.intelligence.dontSeeExit(exit.direction)
629
 
 
630
 
    def when_focus_enter(self,focus,channel,movementEvent):
631
 
        "Hook called when things enter my focus"
632
 
        if self._hasIntelligence():
633
 
            mover = movementEvent.mover
634
 
            if mover.isObviousTo(self):
635
 
                self.intelligence.seeItem(mover, mover.presentPhrase(self))
636
 
 
637
 
    def when_focus_leave(self,focus,channel,movementEvent):
638
 
        "Hook called when things leave my focus"
639
 
        if self._hasIntelligence():
640
 
            mover = movementEvent.mover
641
 
            if mover.isObviousTo(self):
642
 
                self.intelligence.dontSeeItem(mover)
643
 
 
644
 
    def del_intelligence(self):
645
 
        """del Thing.intelligence
646
 
        remove the user-interface component of this Thing"""
647
 
        del self.intelligence.thing
648
 
        self.reallyDel('intelligence')
649
 
 
650
 
    def set_intelligence(self, intelligence):
651
 
        """Thing.intelligence = intelligence
652
 
        Change the user-interface component currently governing this
653
 
        Thing.  NOTE: this is poorly named and likely to change in the
654
 
        near future.  """
655
 
        try:   del self.intelligence
656
 
        except AttributeError: pass
657
 
        self.reallySet('intelligence',intelligence)
658
 
        self.intelligence.thing = self
659
 
        self.reFocus()
660
 
 
661
 
    ### Client Interaction
662
 
 
663
 
    def request(self, question, default, callback):
664
 
        """Thing.request(question,default,callback)
665
 
 
666
 
        question: a question you want to ask the player
667
 
 
668
 
        default: a default response you supply
669
 
        callback: An object with two methods --    
670
 
          ok: a callable object that takes a single string argument.
671
 
              This will be called if the user sends back a response.
672
 
 
673
 
          cancel: this will be called if the user performs an action 
674
 
                  that indicates they will not be sending a response.
675
 
                  There is no guarantee that this will ever be called
676
 
                  in the event of a disconnection.  (It SHOULD be
677
 
                  garbage collected for sure, but garbage collection
678
 
                  is tricky. ^_^) """
679
 
        
680
 
        self.intelligence.request(question,default,callback)
681
 
        
682
 
    def execute(self, sentencestring):
683
 
        """Thing.execute(string)
684
 
        Execute a string as if this player typed it at a prompt.
685
 
        """
686
 
        try:
687
 
            s = sentence.Sentence(sentencestring,self)
688
 
            return s.run()
689
 
        except error.RealityException, re:
690
 
            self.hears(re.format(self))
691
 
            return re
692
 
        except AssertionError, ae:
693
 
            self.hears(ae.args[0])
694
 
 
695
 
    def userInput(self, sentencestring):
696
 
        """Thing.userInput(sentencestring)
697
 
        This method insulates self.execute from Gloop.
698
 
        """
699
 
        try:
700
 
            x = self.execute(sentencestring)
701
 
            if type(x) == types.StringType:
702
 
                return x
703
 
        except:
704
 
            import traceback
705
 
            sio = StringIO.StringIO()
706
 
            traceback.print_exc(file=sio)
707
 
            print "while executing", repr(sentencestring)
708
 
            print sio.getvalue()
709
 
            self.hears(sio.getvalue())
710
 
        return None
711
 
 
712
 
 
713
 
    ambient_ = None
714
 
 
715
 
    def getVerb(self, verbstring, preposition):
716
 
        """Thing.getVerb(verbName, preposition) -> callable or None
717
 
 
718
 
        Return the appropriate method to call for the given verb.  Verbs are
719
 
        code which is attached to instances by writing methods of the form
720
 
        verb_<verbname>_<preposition>(self, sentence), or verb_<verbname>(self,
721
 
        sentence).
722
 
 
723
 
        For example:
724
 
 
725
 
        class Mop(Thing):
726
 
          def verb_dance_with(self, sentence):
727
 
            self.hears('You dance with ',self,'.')
728
 
 
729
 
        defines the class 'mop', which I can dance with, and
730
 
 
731
 
        class Flower(Thing):
732
 
          def verb_smell(self, sentence):
733
 
            self.hears(self, ' smells nice.')
734
 
 
735
 
        defines a flower that I can smell."""
736
 
        
737
 
        if preposition:
738
 
            return getattr(self, "verb_%s_%s"%(verbstring,preposition), None)
739
 
        else:
740
 
            return getattr(self, "verb_%s"%verbstring, None)
741
 
 
742
 
 
743
 
    def getAmbient(self, verbstring):
744
 
        """Thing.getAmbient(self, verbstring) -> callable or None
745
 
 
746
 
        Get an ambient verb (one which is present on a location, rather than an
747
 
        object in the sentence).  These are resolved both on the player's
748
 
        locations (in order from outermost to innermost).
749
 
 
750
 
        For example:
751
 
        
752
 
        class ComfyChair(twisted.library.furniture.Chair):
753
 
          def ambient_go(self, sentence):
754
 
            self.hears('But you\'re so comfy!')
755
 
 
756
 
        is a particularly nasty thing to sit in.
757
 
        """
758
 
        return getattr(self, "ambient_%s" % verbstring, self.ambient_)
759
 
 
760
 
 
761
 
    def getAbility(self, verbstring):
762
 
        """Thing.getAbility(verbName) callable or None
763
 
 
764
 
        Return the appropriate method to call for the given ability.  Abilities
765
 
        are intrinsic verbs which a player can execute -- they are methods of 
766
 
        the form ability_<name>(self, sentence) (similiar to verbs).
767
 
        """
768
 
        
769
 
        return getattr(self, 'ability_%s' % verbstring, None)
770
 
    
771
 
    # place = None
772
 
    location = None
773
 
 
774
 
    def set_place(self, place):
775
 
        assert 0, "You may not set the place of an object manually."
776
 
 
777
 
    def set_location(self, location):
778
 
        """Thing.location = location
779
 
        Change something's physical location.
780
 
        Publishes:
781
 
          * Argument: Event w/ 'source', 'destination' and 'mover' attributes
782
 
          * to self: 'move'
783
 
          * to destination: 'enter'
784
 
          * to all source locations: 'leave'
785
 
        """
786
 
        oldlocation = self.location
787
 
        movement = Event(source      = oldlocation,
788
 
                         destination = location,
789
 
                         mover       = self)
790
 
        if location is not None:
791
 
            self.reality = location.reality
792
 
        if oldlocation is not None:
793
 
            oldlocation.toss(self, destination=location)
794
 
            self.reallyDel('location')
795
 
        if location is not None:
796
 
            self.publish('move',movement)
797
 
            self.reallySet('location', location)
798
 
            location.grab(self, source=oldlocation)
799
 
        self.reFocus()
800
 
 
801
 
    def reFocus(self):
802
 
        """
803
 
        re-set this player's focus.
804
 
        """
805
 
        if self._hasIntelligence():
806
 
            self.focus = self.place.focusProxy(self)
807
 
        
808
 
    def focusProxy(self, player):
809
 
        """This is a hook for darkness and the like; it indicates what your
810
 
        focus will default to when you enter a location.
811
 
        """
812
 
        return self
813
 
 
814
 
 
815
 
    def del_location(self):
816
 
        """del Thing.location
817
 
        Location an object at 'nowhere'."""
818
 
        self.location = None
819
 
 
820
 
    def move(self, destination, actor):
821
 
        """Thing.move(destination, actor) -> None
822
 
 
823
 
        Attempts to move an object to a destination by an Actor; if
824
 
        this object is immovable by this actor (to this destination)
825
 
        for some reason, this will raise a Failure. """
826
 
 
827
 
        if self.component:
828
 
            error.Failure("It's stuck.")
829
 
        self.location = destination
830
 
 
831
 
    # special bits (THE FEWER OF THESE THE BETTER!!!)
832
 
 
833
 
    # Special bits change the way that objects are managed, so if the
834
 
    # code managing them is buggy, the map will become corrupt.  This
835
 
    # is the source of many of the problems that arose with TR 1.
836
 
 
837
 
    component = 0
838
 
    surface = 0
839
 
 
840
 
    # To begin with, let's say we'll do transparency without any
841
 
    # special bits.  Transparent objects should be able to do things
842
 
    # quite easily by subclassing and overriding grab, toss, and find.
843
 
 
844
 
    def set_surface(self, surf):
845
 
        """Thing.surface = 1 or 0
846
 
 
847
 
        This sets (or unsets) the 'surface' boolean.  A 'surface' object
848
 
        broadcasts its list of contents (including components) to the
849
 
        room that it is found in.
850
 
        """
851
 
        assert surf == 1 or surf == 0, '"Surface" should be a boolean.'
852
 
        oldSurface = self.surface
853
 
        self.reallySet('surface',surf)
854
 
        if surf == self.surface:
855
 
            return
856
 
        # get a list of all the containers who might want to know about this
857
 
        if surf:
858
 
            channel = 'enter'
859
 
        else:
860
 
            channel = 'leave'
861
 
            
862
 
        allContainers = self.allContainers
863
 
        
864
 
        for thing in self.allThings:
865
 
            event = Event(
866
 
                source = self,
867
 
                destination = self,
868
 
                mover = thing,
869
 
                simulated = 1)
870
 
            for container in allContainers:
871
 
                container.publish(channel, event)
872
 
 
873
 
    def del_surface(self):
874
 
        """del Thing.surface
875
 
        Set the 'surface' attribute of this thing back to whatever its
876
 
        class default is.
877
 
        """
878
 
        self.set_surface(self.__class__.surface)
879
 
        self.reallyDel('surface')
880
 
 
881
 
    def set_locations(self):
882
 
        "Thing.locations = ... Raise an AttributeError."
883
 
        raise AttributeError("'locations' attribute not settable")
884
 
 
885
 
    def get_locations(self):
886
 
        """Thing.locations
887
 
        This is the list of locations that can "see" this object.
888
 
        Starting with the object's direct location, it continues up
889
 
        until the object's "place"; so it will collect all locations
890
 
        up the containment heirarchy until the object's "top level"
891
 
        location, i.e. the first one which isn't a surface.
892
 
        """
893
 
        locations = []
894
 
        location = self.location
895
 
        while reflect.isinst(location, Thing):
896
 
            locations.append(location)
897
 
            if not location.surface:
898
 
                break
899
 
            location = location.location
900
 
        return tuple(locations)
901
 
 
902
 
 
903
 
    def set_component(self,comp):
904
 
        """Thing.component = boolean
905
 
        This sets the 'component' boolean.  A 'component' object is a
906
 
        part of the object which contains it, and cannot be moved or
907
 
        altered independantly.
908
 
        """
909
 
        assert comp == 1 or comp == 0, '"Component" should be a boolean.'
910
 
        if comp == self.component:
911
 
            return
912
 
 
913
 
        if comp:
914
 
            self.__changeVisibility(not comp)
915
 
            self.reallySet('component',1)
916
 
        else:
917
 
            self.reallySet('component',0)
918
 
            self.__changeVisibility(not comp)
919
 
 
920
 
    def get_place(self):
921
 
        """Thing.place -> Thing or None
922
 
        This returns the `place' of an object.
923
 
        """
924
 
        if not self.locations:
925
 
            return None
926
 
        return self.locations[-1]
927
 
    
928
 
    def __resetPlace(self):
929
 
        """(private) Thing.__resetPlace() -> None
930
 
 
931
 
        Make sure an object's 'place' attribute is properly set. """
932
 
        self.reallySet("place", self.locations[-1])
933
 
 
934
 
 
935
 
    def __changeVisibility(self, bool):
936
 
        """(private) Thing.__changeVisibility(obj, bool)
937
 
        fake a movement event to tell an object's locations that
938
 
        something's name or visibility state changed """
939
 
        if bool:
940
 
            channel = 'enter'
941
 
            source = None
942
 
        else:
943
 
            channel = 'leave'
944
 
            destination = None
945
 
        for container in self.allContainers:
946
 
            if bool:
947
 
                destination = container
948
 
            else:
949
 
                source = container
950
 
            container.publish(channel,
951
 
                              Event(source=source,
952
 
                                    destination=destination,
953
 
                                    mover=self,
954
 
                                    simulated=1))
955
 
 
956
 
 
957
 
    def get_allContainers(self):
958
 
        cont = []
959
 
        for container in self.containers:
960
 
            cont.append(container)
961
 
            if container.surface:
962
 
                for container2 in container.allContainers:
963
 
                    cont.append(container2)
964
 
        return tuple(cont)
965
 
    
966
 
    def get_containers(self):
967
 
        """Thing.containers -> a list of objects that contain this
968
 
        This is a list of objects which directly contain this object.
969
 
        """
970
 
        return tuple(self._containers)
971
 
 
972
 
    def set_containers(self, newContainers):
973
 
        oldContainers = copy.copy(self._containers)
974
 
        for container in newContainers:
975
 
            if container not in oldContainers:
976
 
                container.grab( self )
977
 
 
978
 
        for container in oldContainers:
979
 
            if ((container not in newContainers)
980
 
                and (container is not self.location)):
981
 
                container.toss( self )
982
 
 
983
 
    def _publishEnterOrLeave(self, channel,
984
 
                             mover, source, destination,
985
 
                             simulated=0):
986
 
        "(private): common event sending code"
987
 
        if not simulated:
988
 
            events = [Event(source = source,
989
 
                            destination = destination,
990
 
                            mover = mover,
991
 
                            simulated = simulated)]
992
 
        else:
993
 
            events = []
994
 
        if mover.surface or simulated:
995
 
            for subthing in mover.allThings:
996
 
                events.append(Event(
997
 
                    source = source,
998
 
                    destination = destination,
999
 
                    mover = subthing,
1000
 
                    simulated = simulated))
1001
 
        for event in events:
1002
 
            self.publish(channel, event)
1003
 
        if self.surface:
1004
 
            for container in self.allContainers:
1005
 
                for event in events:
1006
 
                    container.publish(channel, event)
1007
 
 
1008
 
    def grab(self, thing, source=None):
1009
 
        """Location.grab(thing[,destination]) -> None
1010
 
 
1011
 
        Add thing to location's list of searchable children. (TODO:
1012
 
        doc this better) """
1013
 
        assert not thing in self._contained,\
1014
 
               "Duplicate grab: %s already has %s." % (str(self), str(thing))
1015
 
        
1016
 
        thing._containers.append(self)
1017
 
        self._contained.append(thing)
1018
 
        location = self.location
1019
 
        for i in thing.synonyms:
1020
 
            self.__addref(i,thing)
1021
 
        self._publishEnterOrLeave('enter',
1022
 
                                  mover=thing,
1023
 
                                  source=source,
1024
 
                                  destination=self)
1025
 
 
1026
 
 
1027
 
    def toss(self, thing, destination=None):
1028
 
        """Container.toss(thing[, destination])
1029
 
 
1030
 
        Remove an object from a container and sends the appropriate
1031
 
        events.  (Optionally, publish the given source with 'leave'
1032
 
        instead of constructing one.)"""
1033
 
        
1034
 
        assert thing in self._contained,\
1035
 
               "can't toss %s from %s" % (thing, self)
1036
 
        
1037
 
        self._publishEnterOrLeave('leave',
1038
 
                                  mover=thing,
1039
 
                                  source=self,
1040
 
                                  destination=destination)
1041
 
        for i in thing.synonyms:
1042
 
            self.__remref(i,thing)
1043
 
        thing._containers.remove(self)
1044
 
        self._contained.remove(thing)
1045
 
 
1046
 
    def addSynonym(self, synonym):
1047
 
        """Thing.addSynonym(synonym)
1048
 
        Adds a new synonym that this object can be referred to as"""
1049
 
        synonym = string.lower(synonym)
1050
 
        if synonym in self.synonyms:
1051
 
            return
1052
 
        for container in self._containers:
1053
 
            container.__addref(synonym,self)
1054
 
        self.__synonyms.append(synonym)
1055
 
        self.changed = 1
1056
 
 
1057
 
    def removeSynonym(self, synonym):
1058
 
        """Thing.removeSynonym(synonym)
1059
 
        Removes a synonym from this object, so that it can no longer
1060
 
        be referred to in that way."""
1061
 
        synonymn = string.lower(synonym)
1062
 
        if (synonym == self.name) or (not synonym in self.__synonyms):
1063
 
            return
1064
 
        for container in self._containers:
1065
 
            container.__remref(synonym,self)
1066
 
        self.__synonyms.remove(synonym)
1067
 
        self.changed = 1
1068
 
 
1069
 
    def set_synonyms(self, syns):
1070
 
        """Thing.synonyms = [list of synonyms]
1071
 
        Add a list of synonyms at once."""
1072
 
        for i in syns:
1073
 
            self.addSynonym(i)
1074
 
 
1075
 
    def get_synonyms(self):
1076
 
        return tuple(self.__synonyms)
1077
 
 
1078
 
    def get_exits(self):
1079
 
        return tuple(self.__exits.keys())
1080
 
 
1081
 
    def __addref(self,k,t):
1082
 
        self.__index.put(k,t)
1083
 
 
1084
 
    def __remref(self,k,t):
1085
 
        self.__index.remove(k,t)
1086
 
 
1087
 
    def __repr__(self):
1088
 
        return "<%s '%s'>" % (self.__class__.__name__,
1089
 
                              self.name)
1090
 
 
1091
 
 
1092
 
    def destroy(self):
1093
 
        """Thing.destroy() -> None: Destroy this object.
1094
 
        
1095
 
        If you write code which maintains a reference to a Thing, you probably
1096
 
        need to register a subscriber method so that you can drop that Thing
1097
 
        reference when it's destroyed.
1098
 
        
1099
 
        Publishes:
1100
 
            * 'destroy' to self: Event with 'destroyed' attribute.
1101
 
        """
1102
 
        self.component = 0
1103
 
        # First, make sure I don't have a container any more.
1104
 
        self.location = None
1105
 
        # then, tell everyone who cares I'm about to go away.
1106
 
        self.publish('destroy', Event(destroyed=self))
1107
 
        for thing in self.things:
1108
 
            if thing.location is self:
1109
 
                thing.location = None
1110
 
        for thing in self._contained:
1111
 
            self.toss(thing)
1112
 
        for l in self.__index.data.values():
1113
 
            for x in l:
1114
 
                print self, x, x.location
1115
 
        # Then, take all of my non-component stuff and kill it.
1116
 
        self.reality._removeThing(self)
1117
 
 
1118
 
    ### Searching
1119
 
 
1120
 
    def _find(self, name):
1121
 
        """Thing._find(name) -> list of Things
1122
 
        Search the contents of this Thing for a Thing known by the
1123
 
        synonym given.  Returns a list of all possible results.
1124
 
        """
1125
 
        stuff = {}
1126
 
        for item in self.__index.get(name):
1127
 
            stuff[item] = None
1128
 
        for loc in self.things:
1129
 
            if loc.surface:
1130
 
                for item in loc._find(name):
1131
 
                    stuff[item] = None
1132
 
        return stuff.keys()
1133
 
 
1134
 
    def find(self, name):
1135
 
        """Thing.find(name) -> Thing
1136
 
        
1137
 
        Search the contents of this Thing for a Thing known by the synonym
1138
 
        given.  If there is only one result, return it; otherwise, raise an
1139
 
        Ambiguity describing the failure."""
1140
 
        lst = self._find(name)
1141
 
        ll = len(lst)
1142
 
        if ll == 1:
1143
 
            return lst[0]
1144
 
        elif ll == 0:
1145
 
            raise error.CantFind(name)
1146
 
        elif ll > 1:
1147
 
            raise error.Ambiguity(name, lst)
1148
 
 
1149
 
    def locate(self, word):
1150
 
        """Thing.locate -> Thing
1151
 
        Tell this Player to locate a Thing by a synonym it may be
1152
 
        known by.  """
1153
 
        # In order that some objects may be garuanteed not to be found by the
1154
 
        # parser, name them with a $ as the first character.
1155
 
        if word and word[0] == '$':
1156
 
            raise error.CantFind(word)
1157
 
        elif word == 'here':
1158
 
            return self.location
1159
 
        elif word == 'me':
1160
 
            return self
1161
 
        elif word == 'this':
1162
 
            return self.focus
1163
 
        elif word in ('him','her','it'):
1164
 
            if self.antecedent:
1165
 
                return self.antecedent
1166
 
            
1167
 
        stuff = {}
1168
 
        for loc in self, self.location, self.focus:
1169
 
            if loc:
1170
 
                for found in loc._find(word):
1171
 
                    stuff[found] = None
1172
 
                    
1173
 
        stuff = stuff.keys()
1174
 
        ls = len(stuff)
1175
 
        if ls == 1:
1176
 
            return stuff[0]
1177
 
        elif ls == 0:
1178
 
            raise error.CantFind(word)
1179
 
        else:
1180
 
            raise error.Ambiguity(word, stuff)
1181
 
 
1182
 
    def findDirection(self, destination):
1183
 
        """Thing.findDirection(destination) -> string
1184
 
 
1185
 
        Search this room's list of exits for a given destination, and
1186
 
        return the direction it is in, returning None if no such
1187
 
        reverse exists.  (Note: this is slow.)
1188
 
        """
1189
 
        for k, v in self.__exits.items():
1190
 
            if v == destination:
1191
 
                return k
1192
 
        return None
1193
 
 
1194
 
    def findExit(self, direction):
1195
 
        """Thing.findExit(direction) -> Thing
1196
 
        Search this Room's list of exits for the given name.  raise
1197
 
        NoExit if not found.
1198
 
        """
1199
 
 
1200
 
        try:
1201
 
            where=self.__exits[direction]
1202
 
        except KeyError:
1203
 
            raise error.NoExit(self,direction)
1204
 
        else:
1205
 
            if reflect.isinst(where,Thing):
1206
 
                if where.destination:
1207
 
                    return where.destination
1208
 
                if not where.enterable:
1209
 
                    raise error.NoExit(self,direction)
1210
 
                return where
1211
 
            elif reflect.isinst(where,types.StringType):
1212
 
                raise error.NoExit(self,direction,where)
1213
 
 
1214
 
    def set_description(self,description):
1215
 
        """Thing.description = string OR hash
1216
 
        Set the basic (__MAIN__) description element to be the given
1217
 
        description OR add a dictionary's key:values to the description.
1218
 
 
1219
 
        This is a convenience function.  See 'describe'."""
1220
 
        if (type(description)==types.StringType or
1221
 
            callable(description)):
1222
 
            self.describe("__MAIN__", description)
1223
 
        elif (type(description)==types.DictType or
1224
 
              reflect.isinst(description,observable.Hash)):
1225
 
            for foo, bar in description.items():
1226
 
                self.describe(foo, bar)
1227
 
        else:
1228
 
            assert 0, "Not a valid description"
1229
 
 
1230
 
    def get_description(self):
1231
 
        if type(self.__description['__MAIN__']==types.StringType):
1232
 
            return self.__description['__MAIN__']
1233
 
        return "<not a string>"
1234
 
 
1235
 
 
1236
 
    def describe(self, key, value):
1237
 
        """Thing.describe(key, value) -> None
1238
 
        Set a description element of this Thing."""
1239
 
        # this is slightly hacky, but it prevents re-broadcast of
1240
 
        # redundant data.
1241
 
        hk = self.__description.has_key(key)
1242
 
        if not (hk and type(value) is types.StringType
1243
 
                and self.__description[key] == value):
1244
 
            self.publish('description',(key,value))
1245
 
            if value is None:
1246
 
                if hk:
1247
 
                    del self.__description[key]
1248
 
            else:
1249
 
                self.__description[key] = value
1250
 
 
1251
 
    def set_exits(self, exits):
1252
 
        """Thing.exits = {direction:room,...}
1253
 
        Add a dictionary of name:exit pairs to this Room's exits list.
1254
 
        """
1255
 
        # keep in mind that setting the exits is additive.
1256
 
        for direction, room in exits.items():
1257
 
            self.connectExit(direction, room)
1258
 
 
1259
 
    def connectExit(self, direction, room):
1260
 
        """Thing.connectExit(direction, room)
1261
 
 
1262
 
        add an exit to this room, exiting in the specified direction
1263
 
        to the specified other room. """
1264
 
        self.publish('exit', Event(direction=direction,
1265
 
                                   destination=room))
1266
 
        self.__exits[direction] = room
1267
 
 
1268
 
    def disconnectExit(self, direction):
1269
 
        """Thing.disconnectExit(direction)
1270
 
 
1271
 
        Remove an exit from this room, exiting in the specified
1272
 
        direction to the specified other room. """
1273
 
        del self.__exits[direction]
1274
 
        self.publish('exit', Event(direction=direction,
1275
 
                                   destination=None))
1276
 
 
1277
 
    ### Persistence
1278
 
 
1279
 
    def __upgrade_0(self):
1280
 
        print 'upgrading from version 0 to version 1'
1281
 
 
1282
 
    def __upgrade_1(self):
1283
 
        print 'upgrading',self,'from version 1 to version 2'
1284
 
        self._containers = [self.location]
1285
 
        self._contained = []
1286
 
        for child in self.__contents.keys():
1287
 
            if child.location is self:
1288
 
                self._contained.append(child)
1289
 
 
1290
 
    def __upgrade_2(self):
1291
 
        print 'upgrading',self,'from version 2 to version 3'
1292
 
        if self._containers == [None]:
1293
 
            self._containers = []
1294
 
 
1295
 
    def __v3UpgradeRemoveSyns(self, sub):
1296
 
        if self not in sub._containers:
1297
 
            self._contained.remove(sub)
1298
 
            for synonym in sub.synonyms:
1299
 
                self.__remref(synonym, sub)
1300
 
    
1301
 
    def __upgrade_3(self):
1302
 
        print 'upgrading',self,'from version 3 to version 4:',
1303
 
        del self.__contents
1304
 
        if self.__dict__.has_key('place'):
1305
 
            del self.__dict__['place']
1306
 
        # in this version, contents and
1307
 
        if hasattr(self, '_dov3update'):
1308
 
            for loc in self._dov3update:
1309
 
                print '-',
1310
 
                loc.__v3UpgradeRemoveSyns(self)
1311
 
            del self._dov3update
1312
 
                
1313
 
        for contained in copy.copy(self._contained):
1314
 
            # haven't initialized the other object yet
1315
 
            print '.',
1316
 
            if hasattr(contained, '_containers'):
1317
 
                print '.',
1318
 
                self.__v3UpgradeRemoveSyns(contained)
1319
 
            else:
1320
 
                print 'x',
1321
 
                v3uplst = getattr(contained, '_dov3update', [])
1322
 
                v3uplst.append(self)
1323
 
                contained._dov3update = v3uplst
1324
 
        print '!'
1325
 
 
1326
 
    def __setstate__(self, dict):
1327
 
        """Persistent state callback.
1328
 
        """
1329
 
        assert self.__version <= Thing.__version
1330
 
        self.__dict__.update(dict)
1331
 
        while self.__version != Thing.__version:
1332
 
            getattr(self,'_Thing__upgrade_%s' % self.__version)()
1333
 
            self.__version = self.__version + 1
1334
 
 
1335
 
    def __getstate__(self):
1336
 
        """Persistent state callback.
1337
 
        """
1338
 
        dict = copy.copy(self.__dict__)
1339
 
 
1340
 
        for k, v in dict.items():
1341
 
            if (reflect.isinst(v, styles.Ephemeral) or
1342
 
                # FIXME: sometimes (web distribution) it's OK to pickle
1343
 
                # protocols.  Here, it's not.  Jury's still out on this one.
1344
 
                reflect.isinst(v, protocol.Protocol)):
1345
 
                del dict[k]
1346
 
 
1347
 
        if dict.has_key('code_space'):
1348
 
            cs = copy.copy(dict['code_space'])
1349
 
            if cs.has_key('__builtins__'):
1350
 
                del cs['__builtins__']
1351
 
                dict['code_space'] = cs
1352
 
        return dict
1353
 
 
1354
 
    def printSource(self,write):
1355
 
        """Print out a Python source-code representation of this object.
1356
 
        
1357
 
        See the source code for a detailed description of how this is done; it
1358
 
        is not expected to be 100% reliable, merely informative.
1359
 
        """
1360
 
        if (not hasattr(self.reality,'sourcemods') or not
1361
 
            self.reality.sourcemods.has_key(self.__class__.__module__)):
1362
 
            write("import %s\n"%self.__class__.__module__)
1363
 
            if hasattr(self.reality,'sourcemods'):
1364
 
                self.reality.sourcemods[self.__class__.__module__]=1
1365
 
 
1366
 
        write("%s.%s(%s)(\n"%(self.__class__.__module__,
1367
 
                              self.__class__.__name__,
1368
 
                              repr(self.name)))
1369
 
 
1370
 
        # now, about that 'hack'...
1371
 
        dct = copy.copy(self.__dict__)
1372
 
 
1373
 
        # These will all automatically be re-generated when the object
1374
 
        # is constructed, so they're not worth writing to the file.
1375
 
        ephemeral = ('_Thing__index',
1376
 
                     '_Thing__version',
1377
 
                     '_contained',
1378
 
                     'reality',
1379
 
                     'thing_id',
1380
 
                     'changed',
1381
 
                     'place',
1382
 
                     # Not exactly ephemeral, but specified in the
1383
 
                     # constructor.
1384
 
                     'name')
1385
 
 
1386
 
        for eph in ephemeral:
1387
 
            if dct.has_key(eph):
1388
 
                del dct[eph]
1389
 
 
1390
 
        s = copy.copy(dct['_Thing__synonyms'])
1391
 
        del dct['_Thing__synonyms']
1392
 
        # take my name out of the synonyms list
1393
 
        s.remove(string.lower(self.name))
1394
 
        
1395
 
        # if I've got a displayName, and it's a string, remove it from my list
1396
 
        # of synonyms too.
1397
 
        
1398
 
        if type(self.displayName) is types.StringType:
1399
 
            s.remove(string.lower(self.displayName))
1400
 
            
1401
 
        if s:
1402
 
            dct['synonyms']=s
1403
 
 
1404
 
        c = copy.copy(dct['_containers'])
1405
 
        del dct['_containers']
1406
 
 
1407
 
        # I can't keep my location in the sourced map, because it will
1408
 
        # happily call grab twice; so in the map, the 'containers'
1409
 
        # attribute represents *additional* containers (which is what
1410
 
        # setting it would do anyway)
1411
 
        try:
1412
 
            c.remove(self.location)
1413
 
        except:
1414
 
            pass
1415
 
        if c:
1416
 
            dct['containers'] = c
1417
 
 
1418
 
 
1419
 
        if dct.has_key("subscribers"):
1420
 
            subs = copy.copy(dct['subscribers'])
1421
 
 
1422
 
            # If I only have WhenMethod subscribers (or no subscribers at
1423
 
            # all) this attribute is superfluous.
1424
 
 
1425
 
            # Other subscribers will also probably not write out to source
1426
 
            # properly, but WhenMethodSubscriptions will be set up
1427
 
            # automatically again (assuming all the attributes stay
1428
 
            # correct!) so I won't make the map unparseable unless it's
1429
 
            # necessary to indicate something I won't replicate properly
1430
 
            # next time.
1431
 
 
1432
 
            for k,sub in subs.items():
1433
 
                try:
1434
 
                    for subn in sub:
1435
 
                        if not reflect.isinst(subn,
1436
 
                                              observable.
1437
 
                                              WhenMethodSubscription):
1438
 
                            raise 'stop'
1439
 
                    del subs[k]
1440
 
                except 'stop':
1441
 
                    pass
1442
 
 
1443
 
            if subs:
1444
 
                dct['subscribers'] = subs
1445
 
            else:
1446
 
                del dct['subscribers']
1447
 
 
1448
 
        exits = dct['_Thing__exits']
1449
 
        del dct['_Thing__exits']
1450
 
        if exits:
1451
 
            dct['exits'] = exits
1452
 
        if dct.has_key('focus') and dct.has_key('location'):
1453
 
            if dct['focus']==dct['location']:
1454
 
                del dct['focus']
1455
 
 
1456
 
        if dct.has_key('code_space'):
1457
 
            del dct['code_space']
1458
 
 
1459
 
        if dct.has_key('intelligence'):
1460
 
            # i=dct['intelligence']
1461
 
            # if reflect.isinst(i,LocalIntelligence):
1462
 
            del dct['intelligence']
1463
 
 
1464
 
        d = dct['_Thing__description']
1465
 
        del dct['_Thing__description']
1466
 
 
1467
 
        if len(d) == 1 and d.has_key('__MAIN__'):
1468
 
            d = d['__MAIN__']
1469
 
 
1470
 
        dct['description'] = d
1471
 
 
1472
 
        # oh by the way, this is more for human readability than
1473
 
        # anything.  since you can end up with [...] lists and what
1474
 
        # have you.
1475
 
 
1476
 
        # Remove all ephemeral objects (network connections, et. al.)
1477
 
        for k, v in dct.items():
1478
 
            if reflect.isinst(v, styles.Ephemeral):
1479
 
                del dct[k]
1480
 
 
1481
 
        items = dct.items()
1482
 
        # sort according to attribute name
1483
 
        items.sort(lambda a, b: cmp(a[0], b[0]))
1484
 
        for k, v in items:
1485
 
            v = source.sanitize(v)
1486
 
            # delete keys from the dictionary as we go, in order to
1487
 
            # format the end of the argument list properly (with no comma)
1488
 
            del dct[k]
1489
 
            if dct:
1490
 
                nn = ','
1491
 
            else:
1492
 
                nn = ''
1493
 
            write("\t%s=%s%s\n" % (k,repr(v),nn))
1494
 
 
1495
 
        write(")\n")
1496
 
 
1497
 
# End of Thing
1498
 
 
1499
 
observable.registerWhenMethods(Thing)