cfad47cfa3/t3compiler/tads3/lib/extensions/SimpleAttachable.t

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#charset "us-ascii"
2
3
#include <adv3.h>
4
#include <en_us.h>
5
6
/*  
7
 *   SIMPLE ATTACHABLE
8
 *
9
 *   SimpleAttachable version 1.0 by Eric Eve
10
 *
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 
13
 *   it useful.
14
 *
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 
19
 *   it.
20
 *
21
 *   More formally, a SimpleAttachable enforces the following rules:
22
 *
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).
28
 *
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).
32
 *
33
 *   (3)    When a minor attachment is attached to a major attachment, the 
34
 *   minor attachment is moved into the major attachment. This automatically 
35
 *   enforces (4) below.
36
 *
37
 *   (4)    When a major attachment is moved (e.g. by being taken or pushed 
38
 *   around), its minor attachments automatically move with it.
39
 *
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 
42
 *   behind).
43
 *
44
 *   (6)    When a minor attachment is detached from a major attachment it 
45
 *   is moved into the major attachment's location. 
46
 *
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).
52
 *
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).
58
 *
59
 *   (9)    Normally, both the major and the minor attachments should be of 
60
 *   class SimpleAttachable. 
61
 *
62
 *
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.:
68
 *
69
 *   minorAttachmentItems = [redMagnet, blueMagnet]
70
 *
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.:
74
 *
75
 *   isMajorItemFor(obj) { return obj.ofKind(Magnet); }
76
 *
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.
79
 *
80
 */
81
82
ModuleID
83
  name = 'SimpleAttachable'
84
  byLine = 'by Eric Eve'
85
  htmlByLine = 'by <A href="mailto:eric.eve@hmc.ox.ac.uk">Eric Eve</a>'
86
  version = '1.0'  
87
;
88
89
90
class SimpleAttachable: Attachable
91
    
92
    /* Move the minor attachment into the major attachment. */
93
    handleAttach(other)
94
    {
95
        if(other.isMajorItemFor(self))           
96
            moveInto(other);
97
        
98
    }
99
    
100
    /* 
101
     *   When we're detached, if we were in the other object move us into the
102
     *   other object's location.
103
     */
104
    handleDetach(other)
105
    {
106
        if(isIn(other))
107
            moveInto(other.location);
108
    }
109
    
110
    /* 
111
     *   If a minor attachment is taken, first detach it from its major 
112
     *   attachment.
113
     */
114
    dobjFor(Take)
115
    {
116
        preCond = (nilToList(inherited) + objNotAttachedToMajor)
117
    }
118
    
119
    
120
    /*  
121
     *   If we're attached to a major attachment, treat TAKE US FROM MAJOR as
122
     *   equivalent to DETACH US FROM MAJOR.
123
     */
124
    dobjFor(TakeFrom) maybeRemapTo(isAttachedToMajor, DetachFrom, self,
125
                                   location)
126
       
127
    /*  
128
     *   If we're already attached to a major attachment, detach us from it 
129
     *   before attaching us to a different major atttachment.
130
     */
131
    dobjFor(AttachTo)
132
    {
133
        preCond = (nilToList(inherited) + objDetachedFromLocation)
134
    }
135
       
136
    iobjFor(AttachTo)
137
    {
138
        preCond = (nilToList(inherited) + objDetachedFromLocation)
139
    }
140
    
141
    /*  We're a major item for any item in our minorAttachmentItems list. */
142
    isMajorItemFor(obj)
143
    {
144
        return nilToList(minorAttachmentItems).indexOf(obj) != nil;
145
    }
146
    
147
    /*  
148
     *   The list of items that can be attached to us for which we would be 
149
     *   the major attachment item.
150
     */
151
    minorAttachmentItems = []
152
    
153
    /*   
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.
156
     */
157
    isAttachedToMajor = (location && location.isMajorItemFor(self))
158
    isAttachedToMinor = (contents.indexWhich({x: self.isMajorItemFor(x)}) !=
159
                      nil)
160
    
161
    /*   
162
     *   Define if this item be listed when it's a minor item attached to 
163
     *   another item.
164
     */
165
    isListedWhenAttached = true
166
    
167
    isListed = (isAttachedToMajor ? isListedWhenAttached : inherited )
168
    
169
    isListedInContents = (isAttachedToMajor ? nil : inherited )
170
    
171
    /*  
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 
174
     *   us.
175
     */    
176
    contentsLister =  (isAttachedToMinor ? majorAttachmentLister : inherited)
177
    
178
    inlineContentsLister = (isAttachedToMinor ? inlineListingAttachmentsLister :
179
                        inherited )
180
181
   
182
    /*  
183
     *   A SimpleAttachment can be attached to another SimpleAttachment if 
184
     *   one of the SimpleAttachments is a major item for the other.
185
     */
186
    canAttachTo(obj)
187
    {
188
        return isMajorItemFor(obj) || obj.isMajorItemFor(self);
189
    }
190
    
191
    
192
    /*  
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.
195
     */
196
    initializeThing()
197
    {
198
        inherited;
199
        
200
        if(location && location.isMajorItemFor(self))            
201
            attachTo(location);
202
    }
203
;
204
205
/*  
206
 *   Custom lister to show the contents of a major attachment as being 
207
 *   attached to it.
208
 */
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)
214
        { ")"; }
215
;
216
217
/*  Special precondition for use when taking a minor attachment. */
218
219
objNotAttachedToMajor: PreCondition
220
    
221
    /* 
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 
225
     *   one).
226
     */
227
    verifyPreCondition(obj) 
228
    { 
229
        if(obj.location.isMajorItemFor(obj))
230
            logicalRank(90, 'attached');
231
    }
232
233
    
234
    checkPreCondition(obj, allowImplicit)
235
    {
236
        /* 
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 
240
         *   be done.  
241
         */
242
        if (obj.attachedObjects.indexWhich(
243
            {x: x.isMajorItemFor(obj) && !obj.isPermanentlyAttachedTo(x) 
244
                  }) == nil)
245
            return nil;
246
247
                
248
        local major = obj.attachedObjects.valWhich({x: x.isMajorItemFor(obj)});
249
        
250
        /* 
251
         *   Try implicitly detaching us from our major attachment.  
252
         */
253
        if (allowImplicit && tryImplicitAction(DetachFrom, obj, major))
254
        {
255
            /* 
256
             *   if we're still attached to a major attachment, we failed, 
257
             *   so abort
258
             */
259
            if (obj.attachedObjects.indexWhich(
260
                {x: !obj.isPermanentlyAttachedTo(x) 
261
                  && x.isMajorItemFor(obj)}) != nil)
262
                exit;
263
264
            /* tell the caller we executed an implied action */
265
            return true;
266
        }
267
268
        /* we must detach first */
269
        reportFailure(&mustDetachMsg, obj);
270
        exit;
271
    }
272
;
273
274
/*  
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 
278
 *   detachments. 
279
 */
280
281
objDetachedFromLocation: PreCondition
282
    checkPreCondition(obj, allowImplicit)
283
    {
284
        /* 
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. 
288
         */
289
        
290
        local other = (obj == gDobj ? gIobj : gDobj);
291
        local loc = obj.location;
292
            
293
        if(!other.isMajorItemFor(obj) || 
294
            !(loc.isMajorItemFor(obj) && obj.isAttachedTo(loc)))
295
            return nil;
296
297
                
298
               
299
        /* 
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 
303
         *   be done.  
304
         */
305
        if (allowImplicit && tryImplicitAction(DetachFrom, obj, loc))
306
        {
307
            /* if we're still attached to anything, we failed, so abort */
308
            if (loc.isMajorItemFor(obj) && obj.isAttachedTo(loc))
309
                exit;
310
311
            /* tell the caller we executed an implied action */
312
            return true;
313
        }
314
315
        /* we must detach first */
316
        reportFailure(&mustDetachMsg, obj);
317
        exit;
318
    }
319
    
320
;