2
# Twisted, the Framework of Your Internet
3
# Copyright (C) 2001 Matthew W. Lefkowitz
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.
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.
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
18
"""Basic twisted.reality simulation classes.
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
42
#the default reality which things will use.
45
class Event(reflect.Settable):
47
Essentially a dicitonary -- this encapsulates the idea of an event
48
which can be published."""
51
"""A dictionary which may contain ambiguous entries.
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.
62
if type(key)==types.StringType:
63
key = string.lower(key)
68
self.data[key] = [value]
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.
79
if type(key)==types.StringType:
80
key = string.lower(key)
81
x = self.data.get(key, [])
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.
89
if type(key)==types.StringType:
90
key = string.lower(key)
97
class Thing(observable.Publisher,
98
observable.Subscriber,
100
"""Thing(name[,reality])
102
name: a string, which will be both the unique keyed name of this
103
object, and the displayed name.
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'.
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
114
# Most rooms aren't doors; they don't "go" anywhere.
116
# most things are opaque.
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
125
# See the "room" classes below for more details.
127
# what 'reality' this belongs to.
129
# whatever this player last "talked about"; it will be used when
132
# if you're performing actions, are you an administrator?
135
def __init__(self, name, reality=''):
136
"""Thing(name [, reality])
141
self.__index = Ambiguous()
142
self.__version = self.__version
143
self.__description = {"__MAIN__":""}
146
self._containers = []
149
# More state setup, these with constraints.
150
self.name = str(name)
152
self.reality = _default
154
self.reality = reality
156
def set_reality(self, reality):
157
if self.reality is reality:
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
168
# The 'true name' string of this object.
170
# The name of this object that gets displayed when players look at
174
def set_name(self, name):
177
Set the name of this Thing.
179
assert type(name)==types.StringType, "Name must be a string."
180
if name == self.name:
182
if self.name is not None:
183
self.removeSynonym(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)
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)
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.
207
self.__changeVisibility(1)
213
def indefiniteArticle(self, observer):
214
"""Thing.indefiniteArticle(observer) -> 'a ' or 'an '
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'):
224
def definiteArticle(self, observer):
225
"""Thing.definiteArticle(observer) -> 'the '
227
Returns 'the' or nothing, depending on whether or not it would be
228
appropriate to label this object with a definite article. """
231
aan=indefiniteArticle
237
def article(self, observer):
238
"""Thing.article(observer) -> 'a ' or 'an ' or 'the '
240
Determine the article of an object.
242
return self.definiteArticle(observer)
244
def capNounPhrase(self,observer):
245
"The capitalized version of this object's noun phrase."
246
return string.capitalize(self.nounPhrase(observer))
248
def nounPhrase(self, observer):
249
"""Thing.nounPhrase(observer) -> string
250
A brief phrase describing this object, such as 'the blue fish' or
252
return self.article(observer)+self.shortName(observer)
255
contained_preposition = "on"
257
def containedPhrase(self, observer, containedThing):
258
"""Thing.containedPhrase(observer, containedThing) -> 'A containedThing is in the self'
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. """
264
return "%s is %s %s." % (string.capitalize(containedThing.nounPhrase(observer)),
265
self.contained_preposition,
266
self.nounPhrase(observer))
268
def presentPhrase(self, observer):
269
"""Thing.presentPhrase(observer) -> 'A self is in the box', 'Bob is holding the self'
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.'. """
274
location = self.location
276
return "%s%s is nowhere" % self.indefiniteArticle(observer)
278
return location.containedPhrase(observer, self)
281
def shortName(self, observer):
282
"""Thing.shortName(observer) -> 'blue box'
283
The unadorned name of this object, as it appears to the specified
286
displayName = self.displayName
288
if callable(displayName):
289
return displayName(observer)
295
def get_things(self):
296
"Thing.things -> a list of the contents of this object."
297
return tuple(self._contained)
299
def get_allThings(self):
300
"""Thing.allThings -> a complete list of contents for this object.
302
This is the list of contents of an object, plus any objects in surfaces
303
which are visible from it.
306
for thing in self.things:
307
if not thing.component:
310
for thing2 in thing.allThings:
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
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
326
for thing in self.allThings:
327
if thing.isObviousTo(observer):
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.
338
if callable(self.gender):
339
return self.gender(observer)
343
def himHer(self, observer):
344
"""Thing.himHer(observer) -> 'him', 'her', or 'it'
346
returns 'him' 'her' or 'it' depending on the perceived gender
348
return {'m':'him','f':'her','n':'it'}[self.__gender(observer)]
350
def capHimHer(self, observer):
351
"""Thing.capHimHer(observer) 'Him', 'Her', or 'It'
353
see Thing.himHer()"""
354
return string.capitalize(self.himHer(observer))
356
def hisHer(self, observer):
357
"""Thing.hisHer(observer) -> 'his', 'her', or 'its'
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)]
363
def capHisHer(self, observer):
364
"""Thing.capHisHer(observer) 'His', 'Her', or 'Its'
366
see Thing.hisHer()"""
367
return string.capitalize(self.hisHer(observer))
369
def heShe(self, observer):
370
"""Thing.heShe(observer) -> 'he', 'she', or 'it'
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)]
376
def capHeShe(self, observer):
377
"""Thing.capHeShe(observer) -> 'his', 'her', or 'its'
379
returns 'he', 'she', or 'it', depending on the perceived
380
gender of this object """
381
return string.capitalize(self.heShe(observer))
383
def format(self, persplist):
384
"""Thing.format(list of things and strings) -> string
386
Renders a list which represents a phrase based on this object
387
as the observer. For example:
391
print y.format(['You dance with ',mop.nounPhrase,'.'])
393
would yeild: 'You dance with the mop.' """
395
if type(persplist)==types.StringType:
397
elif type(persplist)==types.NoneType:
399
elif callable(persplist):
400
return persplist(self)
402
persplist = list(persplist)
403
if persplist and reflect.isinst(persplist[0],Thing):
404
persplist[0] = persplist[0].capNounPhrase
405
x = StringIO.StringIO()
407
if reflect.isinst(i,Thing):
408
val=i.nounPhrase(self)
410
# this could be one of the things we just defined (wrt
411
# gender) or it could be an observable.Dynamic
418
def hears(self, *args):
419
"""Thing.hears(*list of perceptibles) -> None
421
Causes this Player to hear this list as formatted by
424
string=self.format(args)
425
if self._hasIntelligence():
426
self.intelligence.seeEvent(string)
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)
434
def broadcastToPair(self, target,
435
to_subject, to_target, to_other):
436
"""Thing.broadcastToPair(target, to_subject,
437
to_target, to_other) -> None
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
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)
453
def broadcastToOne(self, to_subject, to_other):
454
"""Thing.broadcastToOne(to_subject, to_other) -> None
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.
461
self.broadcastToPair(target=None,
462
to_subject=to_subject,
474
"""Thing.pairHears(self, subject, target, to_subject,
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.
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.")
490
In this example, when bob executes "wave to jethro", the
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."
497
This sort of interaction is useful almost anywhere a verb
498
enables a player to take action on another player.
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)
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),
517
def allHear(self, *args):
518
"""Thing.allHear(*list to be formatted)
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).
524
Its arguments should be in the same form as 'hear'
526
Examples: room.allHear(disembodied_voice," says 'hello'.")
527
room.allHear("Nothing happens here.")
529
for thing in self.things:
530
apply(thing.hears,args)
532
def oneHears(self, subject, to_subject, to_other):
533
"""Thing.oneHears(subject, to_subject, to_other)
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.
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."))
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,)
551
apply(subject.hears, to_subject)
552
for thing in self.things:
553
if thing is not subject:
554
apply(thing.hears, to_other)
556
def _hasIntelligence(self):
557
return hasattr(self,"intelligence")
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()
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)
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))
590
def descriptionTo(self, player):
591
return string.join(map(player.format,self.__description.values()))
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'):
600
self.reallySet('focus',focus)
601
if self._hasIntelligence() and reflect.isinst(focus,Thing):
602
focus._fullUpdateTo(self)
604
def when_focus_description(self, focus, channel,
606
if self._hasIntelligence():
608
self.intelligence.seeDescription(key, self.format(value))
610
self.intelligence.dontSeeDescription(key)
612
def when_focus_name(self,focus,channel,changed):
613
"Hook called when our focus publishes a new name."
614
if self._hasIntelligence():
616
self.intelligence.seeName(changed.shortName(self))
618
if changed is not self:
619
self.intelligence.seeItem(changed,
620
changed.presentPhrase(self))
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():
626
self.intelligence.seeExit(exit.direction,exit.destination)
628
self.intelligence.dontSeeExit(exit.direction)
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))
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)
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')
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
655
try: del self.intelligence
656
except AttributeError: pass
657
self.reallySet('intelligence',intelligence)
658
self.intelligence.thing = self
661
### Client Interaction
663
def request(self, question, default, callback):
664
"""Thing.request(question,default,callback)
666
question: a question you want to ask the player
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.
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
680
self.intelligence.request(question,default,callback)
682
def execute(self, sentencestring):
683
"""Thing.execute(string)
684
Execute a string as if this player typed it at a prompt.
687
s = sentence.Sentence(sentencestring,self)
689
except error.RealityException, re:
690
self.hears(re.format(self))
692
except AssertionError, ae:
693
self.hears(ae.args[0])
695
def userInput(self, sentencestring):
696
"""Thing.userInput(sentencestring)
697
This method insulates self.execute from Gloop.
700
x = self.execute(sentencestring)
701
if type(x) == types.StringType:
705
sio = StringIO.StringIO()
706
traceback.print_exc(file=sio)
707
print "while executing", repr(sentencestring)
709
self.hears(sio.getvalue())
715
def getVerb(self, verbstring, preposition):
716
"""Thing.getVerb(verbName, preposition) -> callable or None
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,
726
def verb_dance_with(self, sentence):
727
self.hears('You dance with ',self,'.')
729
defines the class 'mop', which I can dance with, and
732
def verb_smell(self, sentence):
733
self.hears(self, ' smells nice.')
735
defines a flower that I can smell."""
738
return getattr(self, "verb_%s_%s"%(verbstring,preposition), None)
740
return getattr(self, "verb_%s"%verbstring, None)
743
def getAmbient(self, verbstring):
744
"""Thing.getAmbient(self, verbstring) -> callable or None
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).
752
class ComfyChair(twisted.library.furniture.Chair):
753
def ambient_go(self, sentence):
754
self.hears('But you\'re so comfy!')
756
is a particularly nasty thing to sit in.
758
return getattr(self, "ambient_%s" % verbstring, self.ambient_)
761
def getAbility(self, verbstring):
762
"""Thing.getAbility(verbName) callable or None
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).
769
return getattr(self, 'ability_%s' % verbstring, None)
774
def set_place(self, place):
775
assert 0, "You may not set the place of an object manually."
777
def set_location(self, location):
778
"""Thing.location = location
779
Change something's physical location.
781
* Argument: Event w/ 'source', 'destination' and 'mover' attributes
783
* to destination: 'enter'
784
* to all source locations: 'leave'
786
oldlocation = self.location
787
movement = Event(source = oldlocation,
788
destination = location,
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)
803
re-set this player's focus.
805
if self._hasIntelligence():
806
self.focus = self.place.focusProxy(self)
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.
815
def del_location(self):
816
"""del Thing.location
817
Location an object at 'nowhere'."""
820
def move(self, destination, actor):
821
"""Thing.move(destination, actor) -> None
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. """
828
error.Failure("It's stuck.")
829
self.location = destination
831
# special bits (THE FEWER OF THESE THE BETTER!!!)
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.
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.
844
def set_surface(self, surf):
845
"""Thing.surface = 1 or 0
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.
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:
856
# get a list of all the containers who might want to know about this
862
allContainers = self.allContainers
864
for thing in self.allThings:
870
for container in allContainers:
871
container.publish(channel, event)
873
def del_surface(self):
875
Set the 'surface' attribute of this thing back to whatever its
878
self.set_surface(self.__class__.surface)
879
self.reallyDel('surface')
881
def set_locations(self):
882
"Thing.locations = ... Raise an AttributeError."
883
raise AttributeError("'locations' attribute not settable")
885
def get_locations(self):
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.
894
location = self.location
895
while reflect.isinst(location, Thing):
896
locations.append(location)
897
if not location.surface:
899
location = location.location
900
return tuple(locations)
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.
909
assert comp == 1 or comp == 0, '"Component" should be a boolean.'
910
if comp == self.component:
914
self.__changeVisibility(not comp)
915
self.reallySet('component',1)
917
self.reallySet('component',0)
918
self.__changeVisibility(not comp)
921
"""Thing.place -> Thing or None
922
This returns the `place' of an object.
924
if not self.locations:
926
return self.locations[-1]
928
def __resetPlace(self):
929
"""(private) Thing.__resetPlace() -> None
931
Make sure an object's 'place' attribute is properly set. """
932
self.reallySet("place", self.locations[-1])
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 """
945
for container in self.allContainers:
947
destination = container
950
container.publish(channel,
952
destination=destination,
957
def get_allContainers(self):
959
for container in self.containers:
960
cont.append(container)
961
if container.surface:
962
for container2 in container.allContainers:
963
cont.append(container2)
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.
970
return tuple(self._containers)
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 )
978
for container in oldContainers:
979
if ((container not in newContainers)
980
and (container is not self.location)):
981
container.toss( self )
983
def _publishEnterOrLeave(self, channel,
984
mover, source, destination,
986
"(private): common event sending code"
988
events = [Event(source = source,
989
destination = destination,
991
simulated = simulated)]
994
if mover.surface or simulated:
995
for subthing in mover.allThings:
998
destination = destination,
1000
simulated = simulated))
1001
for event in events:
1002
self.publish(channel, event)
1004
for container in self.allContainers:
1005
for event in events:
1006
container.publish(channel, event)
1008
def grab(self, thing, source=None):
1009
"""Location.grab(thing[,destination]) -> None
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))
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',
1027
def toss(self, thing, destination=None):
1028
"""Container.toss(thing[, destination])
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.)"""
1034
assert thing in self._contained,\
1035
"can't toss %s from %s" % (thing, self)
1037
self._publishEnterOrLeave('leave',
1040
destination=destination)
1041
for i in thing.synonyms:
1042
self.__remref(i,thing)
1043
thing._containers.remove(self)
1044
self._contained.remove(thing)
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:
1052
for container in self._containers:
1053
container.__addref(synonym,self)
1054
self.__synonyms.append(synonym)
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):
1064
for container in self._containers:
1065
container.__remref(synonym,self)
1066
self.__synonyms.remove(synonym)
1069
def set_synonyms(self, syns):
1070
"""Thing.synonyms = [list of synonyms]
1071
Add a list of synonyms at once."""
1075
def get_synonyms(self):
1076
return tuple(self.__synonyms)
1078
def get_exits(self):
1079
return tuple(self.__exits.keys())
1081
def __addref(self,k,t):
1082
self.__index.put(k,t)
1084
def __remref(self,k,t):
1085
self.__index.remove(k,t)
1088
return "<%s '%s'>" % (self.__class__.__name__,
1093
"""Thing.destroy() -> None: Destroy this object.
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.
1100
* 'destroy' to self: Event with 'destroyed' attribute.
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:
1112
for l in self.__index.data.values():
1114
print self, x, x.location
1115
# Then, take all of my non-component stuff and kill it.
1116
self.reality._removeThing(self)
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.
1126
for item in self.__index.get(name):
1128
for loc in self.things:
1130
for item in loc._find(name):
1134
def find(self, name):
1135
"""Thing.find(name) -> Thing
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)
1145
raise error.CantFind(name)
1147
raise error.Ambiguity(name, lst)
1149
def locate(self, word):
1150
"""Thing.locate -> Thing
1151
Tell this Player to locate a Thing by a synonym it may be
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
1161
elif word == 'this':
1163
elif word in ('him','her','it'):
1165
return self.antecedent
1168
for loc in self, self.location, self.focus:
1170
for found in loc._find(word):
1173
stuff = stuff.keys()
1178
raise error.CantFind(word)
1180
raise error.Ambiguity(word, stuff)
1182
def findDirection(self, destination):
1183
"""Thing.findDirection(destination) -> string
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.)
1189
for k, v in self.__exits.items():
1190
if v == destination:
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.
1201
where=self.__exits[direction]
1203
raise error.NoExit(self,direction)
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)
1211
elif reflect.isinst(where,types.StringType):
1212
raise error.NoExit(self,direction,where)
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.
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)
1228
assert 0, "Not a valid description"
1230
def get_description(self):
1231
if type(self.__description['__MAIN__']==types.StringType):
1232
return self.__description['__MAIN__']
1233
return "<not a string>"
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
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))
1247
del self.__description[key]
1249
self.__description[key] = value
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.
1255
# keep in mind that setting the exits is additive.
1256
for direction, room in exits.items():
1257
self.connectExit(direction, room)
1259
def connectExit(self, direction, room):
1260
"""Thing.connectExit(direction, room)
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,
1266
self.__exits[direction] = room
1268
def disconnectExit(self, direction):
1269
"""Thing.disconnectExit(direction)
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,
1279
def __upgrade_0(self):
1280
print 'upgrading from version 0 to version 1'
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)
1290
def __upgrade_2(self):
1291
print 'upgrading',self,'from version 2 to version 3'
1292
if self._containers == [None]:
1293
self._containers = []
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)
1301
def __upgrade_3(self):
1302
print 'upgrading',self,'from version 3 to version 4:',
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:
1310
loc.__v3UpgradeRemoveSyns(self)
1311
del self._dov3update
1313
for contained in copy.copy(self._contained):
1314
# haven't initialized the other object yet
1316
if hasattr(contained, '_containers'):
1318
self.__v3UpgradeRemoveSyns(contained)
1321
v3uplst = getattr(contained, '_dov3update', [])
1322
v3uplst.append(self)
1323
contained._dov3update = v3uplst
1326
def __setstate__(self, dict):
1327
"""Persistent state callback.
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
1335
def __getstate__(self):
1336
"""Persistent state callback.
1338
dict = copy.copy(self.__dict__)
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)):
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
1354
def printSource(self,write):
1355
"""Print out a Python source-code representation of this object.
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.
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
1366
write("%s.%s(%s)(\n"%(self.__class__.__module__,
1367
self.__class__.__name__,
1370
# now, about that 'hack'...
1371
dct = copy.copy(self.__dict__)
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',
1382
# Not exactly ephemeral, but specified in the
1386
for eph in ephemeral:
1387
if dct.has_key(eph):
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))
1395
# if I've got a displayName, and it's a string, remove it from my list
1398
if type(self.displayName) is types.StringType:
1399
s.remove(string.lower(self.displayName))
1404
c = copy.copy(dct['_containers'])
1405
del dct['_containers']
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)
1412
c.remove(self.location)
1416
dct['containers'] = c
1419
if dct.has_key("subscribers"):
1420
subs = copy.copy(dct['subscribers'])
1422
# If I only have WhenMethod subscribers (or no subscribers at
1423
# all) this attribute is superfluous.
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
1432
for k,sub in subs.items():
1435
if not reflect.isinst(subn,
1437
WhenMethodSubscription):
1444
dct['subscribers'] = subs
1446
del dct['subscribers']
1448
exits = dct['_Thing__exits']
1449
del dct['_Thing__exits']
1451
dct['exits'] = exits
1452
if dct.has_key('focus') and dct.has_key('location'):
1453
if dct['focus']==dct['location']:
1456
if dct.has_key('code_space'):
1457
del dct['code_space']
1459
if dct.has_key('intelligence'):
1460
# i=dct['intelligence']
1461
# if reflect.isinst(i,LocalIntelligence):
1462
del dct['intelligence']
1464
d = dct['_Thing__description']
1465
del dct['_Thing__description']
1467
if len(d) == 1 and d.has_key('__MAIN__'):
1470
dct['description'] = d
1472
# oh by the way, this is more for human readability than
1473
# anything. since you can end up with [...] lists and what
1476
# Remove all ephemeral objects (network connections, et. al.)
1477
for k, v in dct.items():
1478
if reflect.isinst(v, styles.Ephemeral):
1482
# sort according to attribute name
1483
items.sort(lambda a, b: cmp(a[0], b[0]))
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)
1493
write("\t%s=%s%s\n" % (k,repr(v),nn))
1499
observable.registerWhenMethods(Thing)