9
* SimpleAttachable version 1.0 by Eric Eve
11
* This file defines the SimpleAttachable class together with some
12
* supporting objects. Feel free to use this in your own games if you find
15
* Attachables in general are complicated to handle, because they can
16
* behave in so many different ways. The SimpleAttachable class is meant
17
* to make handling one common case easier, in particular the case where a
18
* smaller object is attached to a larger object and then moves round with
21
* More formally, a SimpleAttachable enforces the following rules:
23
* (1) In any attachment relationship between SimpleAttachables, one
24
* object must be the major attachment, and all the others will be that
25
* object's minor attachments (if there's a fridge with a red magnet and a
26
* blue magnet attached, the fridge is the major attachement and the
27
* magnets are its minor attachments).
29
* (2) A major attachment can have many minor attachments attached to
30
* it at once, but a minor attachment can only be attached to one major
31
* attachment at a time (this is a consequence of (3) below).
33
* (3) When a minor attachment is attached to a major attachment, the
34
* minor attachment is moved into the major attachment. This automatically
37
* (4) When a major attachment is moved (e.g. by being taken or pushed
38
* around), its minor attachments automatically move with it.
40
* (5) When a minor attachment is taken, it is automatically detached
41
* from its major attachment (if I take a magnet, I leave the fridge
44
* (6) When a minor attachment is detached from a major attachment it
45
* is moved into the major attachment's location.
47
* (7) The same SimpleAttachable can be simultaneously a minor item
48
* for one object and a major item for one or more other objects (we could
49
* attach a metal paper clip to the magnet while the magnet is attached to
50
* the fridge; if we take the magnet the paper clip comes with it while the
51
* fridge is left behind).
53
* (8) If a SimpleAttachable is attached to a major attachment while
54
* it's already attached to another major attachment, it will first be
55
* detached from its existing major attachment before being attached to
56
* the new one (ATTACH MAGNET TO OVEN will trigger an implicit DETACH
57
* MAGNET FROM FRIDGE if the magnet was attached to the fridge).
59
* (9) Normally, both the major and the minor attachments should be of
60
* class SimpleAttachable.
63
* Setting up a SimpleAttachable is then straightforward, since all the
64
* complications are handled on the class. In the simplest case all the
65
* game author needs to do is to define the minorAttachmentItems property
66
* on the major SimpleAttachable to hold a list of items that can be
67
* attached to it, e.g.:
69
* minorAttachmentItems = [redMagnet, blueMagnet]
71
* If a more complex way of deciding what can be attached to a major
72
* SimpleAttachable is required, override its isMajorItemFor() method
73
* instead, so that it returns true for any obj that can be attached, e.g.:
75
* isMajorItemFor(obj) { return obj.ofKind(Magnet); }
77
* One further point to note: if you want a Container-type object to act
78
* as a major SimpleAttachment, you'll need to make it a ComplexContainer.
83
name = 'SimpleAttachable'
84
byLine = 'by Eric Eve'
85
htmlByLine = 'by <A href="mailto:eric.eve@hmc.ox.ac.uk">Eric Eve</a>'
90
class SimpleAttachable: Attachable
92
/* Move the minor attachment into the major attachment. */
95
if(other.isMajorItemFor(self))
101
* When we're detached, if we were in the other object move us into the
102
* other object's location.
107
moveInto(other.location);
111
* If a minor attachment is taken, first detach it from its major
116
preCond = (nilToList(inherited) + objNotAttachedToMajor)
121
* If we're attached to a major attachment, treat TAKE US FROM MAJOR as
122
* equivalent to DETACH US FROM MAJOR.
124
dobjFor(TakeFrom) maybeRemapTo(isAttachedToMajor, DetachFrom, self,
128
* If we're already attached to a major attachment, detach us from it
129
* before attaching us to a different major atttachment.
133
preCond = (nilToList(inherited) + objDetachedFromLocation)
138
preCond = (nilToList(inherited) + objDetachedFromLocation)
141
/* We're a major item for any item in our minorAttachmentItems list. */
144
return nilToList(minorAttachmentItems).indexOf(obj) != nil;
148
* The list of items that can be attached to us for which we would be
149
* the major attachment item.
151
minorAttachmentItems = []
154
* A pair of convenience methods to determined if we're attached to any
155
* items that are major or minor attachments relative to us.
157
isAttachedToMajor = (location && location.isMajorItemFor(self))
158
isAttachedToMinor = (contents.indexWhich({x: self.isMajorItemFor(x)}) !=
162
* Define if this item be listed when it's a minor item attached to
165
isListedWhenAttached = true
167
isListed = (isAttachedToMajor ? isListedWhenAttached : inherited )
169
isListedInContents = (isAttachedToMajor ? nil : inherited )
172
* Customise the listers so that if we contain minor items as
173
* attachments they're shows as being attached to us, not as being in
176
contentsLister = (isAttachedToMinor ? majorAttachmentLister : inherited)
178
inlineContentsLister = (isAttachedToMinor ? inlineListingAttachmentsLister :
183
* A SimpleAttachment can be attached to another SimpleAttachment if
184
* one of the SimpleAttachments is a major item for the other.
188
return isMajorItemFor(obj) || obj.isMajorItemFor(self);
193
* If I start the game located in an object that's a major item for me,
194
* presumbably we're meant to start off attached.
200
if(location && location.isMajorItemFor(self))
206
* Custom lister to show the contents of a major attachment as being
209
inlineListingAttachmentsLister: ContentsLister
210
showListEmpty(pov, parent) { }
211
showListPrefixWide(cnt, pov, parent)
212
{ " (to which <<cnt > 1 ? '{are|were}' : '{is|was}'>> attached "; }
213
showListSuffixWide(itemCount, pov, parent)
217
/* Special precondition for use when taking a minor attachment. */
219
objNotAttachedToMajor: PreCondition
222
* Other things being equal, prefer to take an item that's not a minor
223
* attachment (if the blue magnet is attached to the fridge and the red
224
* magnet is lying on the floor, then make TAKE MAGNET take the red
227
verifyPreCondition(obj)
229
if(obj.location.isMajorItemFor(obj))
230
logicalRank(90, 'attached');
234
checkPreCondition(obj, allowImplicit)
237
* if we don't already have any non-permanent attachments that
238
* are the major attachments for us, we're fine (as we don't
239
* require removing permanent attachments); nothing more needs to
242
if (obj.attachedObjects.indexWhich(
243
{x: x.isMajorItemFor(obj) && !obj.isPermanentlyAttachedTo(x)
248
local major = obj.attachedObjects.valWhich({x: x.isMajorItemFor(obj)});
251
* Try implicitly detaching us from our major attachment.
253
if (allowImplicit && tryImplicitAction(DetachFrom, obj, major))
256
* if we're still attached to a major attachment, we failed,
259
if (obj.attachedObjects.indexWhich(
260
{x: !obj.isPermanentlyAttachedTo(x)
261
&& x.isMajorItemFor(obj)}) != nil)
264
/* tell the caller we executed an implied action */
268
/* we must detach first */
269
reportFailure(&mustDetachMsg, obj);
275
* Special precondition to detach us from an existing major attachment
276
* before attaching us to another one. This needs to be different from
277
* objNotAttachedToMajor so that we don't perform any unnecessary
281
objDetachedFromLocation: PreCondition
282
checkPreCondition(obj, allowImplicit)
285
* If the other object involved in the command is not a majorItem
286
* for us, or we're not already in an object that we're attached
287
* to which is our major item, then there's nothing to do.
290
local other = (obj == gDobj ? gIobj : gDobj);
291
local loc = obj.location;
293
if(!other.isMajorItemFor(obj) ||
294
!(loc.isMajorItemFor(obj) && obj.isAttachedTo(loc)))
300
* if we don't already have any non-permanent attachments that
301
* are the major attachments for us, we're fine (as we don't
302
* require removing permanent attachments); nothing more needs to
305
if (allowImplicit && tryImplicitAction(DetachFrom, obj, loc))
307
/* if we're still attached to anything, we failed, so abort */
308
if (loc.isMajorItemFor(obj) && obj.isAttachedTo(loc))
311
/* tell the caller we executed an implied action */
315
/* we must detach first */
316
reportFailure(&mustDetachMsg, obj);