cfad47cfa3/t3compiler/tads3/lib/adv3/sense.t

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#charset "us-ascii"
2
3
/* 
4
 *   Copyright (c) 2000, 2006 Michael J. Roberts.  All Rights Reserved. 
5
 *   
6
 *   TADS 3 Library - senses
7
 *   
8
 *   This module defines objects and functions related to senses.  This
9
 *   file is language-independent.  
10
 */
11
12
/* include the library header */
13
#include "adv3.h"
14
15
16
/* ------------------------------------------------------------------------ */
17
/*
18
 *   Material: the base class for library objects that specify the way
19
 *   senses pass through objects.  
20
 */
21
class Material: object
22
    /*
23
     *   Determine how a sense passes through the material.  We'll return
24
     *   a transparency level.  (Individual materials should not need to
25
     *   override this method, since it simply dispatches to the various
26
     *   xxxThru methods.)
27
     */
28
    senseThru(sense)
29
    {
30
        /* dispatch to the xxxThru method for the sense */
31
        return self.(sense.thruProp);
32
    }
33
34
    /*
35
     *   For each sense, each material must define an appropriate xxxThru
36
     *   property that returns the transparency level for that sense
37
     *   through the material.  Any xxxThru property not defined in an
38
     *   individual material defaults to opaque.
39
     */
40
    seeThru = opaque
41
    hearThru = opaque
42
    smellThru = opaque
43
    touchThru = opaque
44
;
45
46
/*
47
 *   Adventium is the basic stuff of the game universe.  This is the
48
 *   default material for any object that doesn't specify a different
49
 *   material.  This type of material is opaque to all senses.  
50
 */
51
adventium: Material
52
    seeThru = opaque
53
    hearThru = opaque
54
    smellThru = opaque
55
    touchThru = opaque
56
;
57
58
/*
59
 *   Paper is opaque to sight and touch, but allows sound and smell to
60
 *   pass.  
61
 */
62
paper: Material
63
    seeThru = opaque
64
    hearThru = transparent
65
    smellThru = transparent
66
    touchThru = opaque
67
;
68
69
/*
70
 *   Glass is transparent to light, but opaque to touch, sound, and smell.
71
 */
72
glass: Material
73
    seeThru = transparent
74
    hearThru = opaque
75
    smellThru = opaque
76
    touchThru = opaque
77
;
78
79
/*
80
 *   Fine Mesh is transparent to all senses except touch.  
81
 */
82
fineMesh: Material
83
    seeThru = transparent
84
    hearThru = transparent
85
    smellThru = transparent
86
    touchThru = opaque
87
;
88
89
/*
90
 *   Coarse Mesh is transparent to all senses, including touch, but
91
 *   doesn't allow large objects to pass through.  
92
 */
93
coarseMesh: Material
94
    seeThru = transparent
95
    hearThru = transparent
96
    smellThru = transparent
97
    touchThru = transparent
98
;
99
100
/* ------------------------------------------------------------------------ */
101
/*
102
 *   Sense: the basic class for senses.  
103
 */
104
class Sense: object
105
    /*
106
     *   Each sense must define the property thruProp as a property
107
     *   pointer giving the xxxThru property for the sense.  The xxxThru
108
     *   property is the property of a material which determines how the
109
     *   sense passes through that material.  
110
     */
111
    thruProp = nil
112
113
    /*
114
     *   Each sense must define the property sizeProp as a property
115
     *   pointer giving the xxxSize property for the sense.  The xxxSize
116
     *   property is the property of a Thing which determines how "large"
117
     *   the object is with respect to the sense.  For example, sightSize
118
     *   indicates how large the object is visually, while soundSize
119
     *   indicates how loud the object is.
120
     *   
121
     *   The purpose of an object's size in a given sense is to determine
122
     *   how well the object can be sensed through an obscuring medium or
123
     *   at a distance.  
124
     */
125
    sizeProp = nil
126
127
    /*
128
     *   Each sense must define the property presenceProp as a property
129
     *   pointer giving the xxxPresence property for the sense.  The
130
     *   xxxPresence property is the property of a Thing which determines
131
     *   whether or not the object has a "presence" in this sense, which is
132
     *   to say whether or not the object is emitting any detectable
133
     *   sensory data for the sense.  For example, soundPresence indicates
134
     *   whether or not a Thing is making any noise.
135
     *   
136
     *   The sensory presence is used to determine if an object is in
137
     *   scope.  An object with a detectable sensory presence is normally
138
     *   in scope.  Note that sounds and smells emitted by a tangible
139
     *   object are frequently represented as additional intangible
140
     *   objects, and in these cases the intangible object (the sensory
141
     *   emanation) is usually the object with a sensory presence, rather
142
     *   than the tangible object making the noise/odor.  However, it is
143
     *   sometimes obvious that a particular sound or odor is coming from a
144
     *   particular kind of object, so the presence of the sound or odor
145
     *   implies the presence of the source object and thus places the
146
     *   source object in scope.  In such cases, it is desirable for the
147
     *   source object to have a sensory presence of its own, in addition
148
     *   to the sensory presence of the intangible sensory emanation
149
     *   object.
150
     *   
151
     *   Note that the "presence" doesn't have any effect on whether or not
152
     *   an object can be sensed.  Only the sense path matters for that: an
153
     *   object without a presence can still be sensed if there's a
154
     *   non-opaque sense path to the object.  Presence only determines
155
     *   whether or not an object is *actively* calling attention to
156
     *   itself.  
157
     */
158
    presenceProp = nil
159
160
    /*
161
     *   Each sense can define this property to specify a property pointer
162
     *   used to define a Thing's "ambient" energy emissions.  Senses
163
     *   which do not use ambient energy should define this to nil.
164
     *   
165
     *   Some senses work only on directly emitted sensory data; human
166
     *   hearing, for example, has no (at least effectively no) use for
167
     *   reflected sound, and can sense objects only by the sounds they're
168
     *   actually emitting.  Sight, on the other hand, can make use not
169
     *   only of light emitted by an object but of light reflected by the
170
     *   object.  So, sight defines an ambience property, whereas hearing,
171
     *   touch, and smell do not.  
172
     */
173
    ambienceProp = nil
174
175
    /*
176
     *   Determine if, in general, the given object can be sensed under
177
     *   the given conditions.  Returns true if so, nil if not.  By
178
     *   default, if the ambient level is zero, we'll return nil;
179
     *   otherwise, if the transparency level is 'transparent', we'll
180
     *   return true; otherwise, we'll consult the object's size:
181
     *   
182
     *   - Small objects cannot be sensed under less than transparent
183
     *   conditions.
184
     *   
185
     *   - Medium or large objects can be sensed in any conditions other
186
     *   than opaque.
187
     */
188
    canObjBeSensed(obj, trans, ambient)
189
    {
190
        /* 
191
         *   if we use "reflected" energy, and the ambient energy level is
192
         *   zero, we can't sense it 
193
         */
194
        if (ambienceProp != nil && ambient == 0)
195
            return nil;
196
197
        /* check the transparency level */
198
        switch(trans)
199
        {
200
        case transparent:
201
        case attenuated:
202
            /* 
203
             *   we can always sense under transparent or attenuated
204
             *   conditions 
205
             */
206
            return true;
207
208
        case distant:
209
        case obscured:
210
            /* 
211
             *   we can only sense medium and large objects under less
212
             *   than transparent conditions 
213
             */
214
            return obj.(self.sizeProp) != small;
215
216
        default:
217
            /* we can never sense under other conditions */
218
            return nil;
219
        }
220
    }
221
;
222
223
/*
224
 *   The senses.  We define sight, sound, smell, and touch.  We do not
225
 *   define a separate sense for taste, since it would add nothing to our
226
 *   model: you can taste something if and only if you can touch it.
227
 *   
228
 *   To add a new sense, you must do the following:
229
 *   
230
 *   - Define the sense object itself, in parallel to the senses defined
231
 *   below.
232
 *   
233
 *   - Modify class Material to set the default transparency level for
234
 *   this sense by defining the property xxxThru - for most senses, the
235
 *   default transparency level is 'opaque', but you must decide on the
236
 *   appropriate default for your new sense.
237
 *   
238
 *   - Modify class Thing to set the default xxxSize setting, if desired.
239
 *   
240
 *   - Modify class Thing to set the default xxxPresence setting, if
241
 *   desired.
242
 *   
243
 *   - Modify each instance of class 'Material' that should have a
244
 *   non-default transparency for the sense by defining the property
245
 *   xxxThru for the material.
246
 *   
247
 *   - Modify class Actor to add the sense to the default mySenses list;
248
 *   this is only necessary if the sense is one that all actors should
249
 *   have by default.  
250
 */
251
252
sight: Sense
253
    thruProp = &seeThru
254
    sizeProp = &sightSize
255
    presenceProp = &sightPresence
256
    ambienceProp = &brightness
257
;
258
259
sound: Sense
260
    thruProp = &hearThru
261
    sizeProp = &soundSize
262
    presenceProp = &soundPresence
263
;
264
265
smell: Sense
266
    thruProp = &smellThru
267
    sizeProp = &smellSize
268
    presenceProp = &smellPresence
269
;
270
271
touch: Sense
272
    thruProp = &touchThru
273
    sizeProp = &touchSize
274
    presenceProp = &touchPresence
275
276
    /*
277
     *   Override canObjBeSensed for touch.  Unlike other senses, touch
278
     *   requires physical contact with an object, so it cannot operate at
279
     *   a distance, regardless of the size of an object.  
280
     */
281
    canObjBeSensed(obj, trans, ambient)
282
    {
283
        /* if it's distant, we can't sense the object no matter how large */
284
        if (trans == distant)
285
            return nil;
286
287
        /* for other cases, inherit the default handling */
288
        return inherited(obj, trans, ambient);
289
    }
290
;
291
292
293
/* ------------------------------------------------------------------------ */
294
/*
295
 *   "Add" two transparency levels, yielding a new transparency level.
296
 *   This function can be used to determine the result of passing a sense
297
 *   through multiple layers of material.  
298
 */
299
transparencyAdd(a, b)
300
{
301
    /* transparent + x -> x for all x */
302
    if (a == transparent)
303
        return b;
304
    if (b == transparent)
305
        return a;
306
307
    /* opaque + x -> opaque for all x */
308
    if (a == opaque || b == opaque)
309
        return opaque;
310
311
    /* 
312
     *   any other combinations yield opaque - we can't have two levels of
313
     *   attenuation or obscuration without losing all detail and energy
314
     *   transmission 
315
     */
316
    return opaque;
317
}
318
319
/* ------------------------------------------------------------------------ */
320
/*
321
 *   Compare two transparency levels to determine which one is more
322
 *   transparent.  Returns 0 if the two levels are equally transparent, 1
323
 *   if the first one is more transparent, and -1 if the second one is
324
 *   more transparent.  The comparison follows this rule:
325
 *   
326
 *   transparent > attenuated > distant == obscured > opaque 
327
 */
328
transparencyCompare(a, b)
329
{
330
    /*
331
     *   for the purposes of the comparison, consider obscured to be
332
     *   identical to distant
333
     */
334
    if (a == obscured)
335
        a = distant;
336
    if (b == obscured)
337
        b = distant;
338
339
    /* if they're the same, return zero to so indicate */
340
    if (a == b)
341
        return 0;
342
343
    /*
344
     *   We know they're not equal, so if one is transparent, then the
345
     *   other one isn't.  Thus, if either one is transparent, it's the
346
     *   winner.
347
     */
348
    if (a == transparent)
349
        return 1;
350
    if (b == transparent)
351
        return -1;
352
353
    /*
354
     *   We know they're not equal and we know neither is transparent, so
355
     *   if one is attenuated then the other is worse, and the attenuated
356
     *   one is the winner.  
357
     */
358
    if (a == attenuated)
359
        return 1;
360
    if (b == attenuated)
361
        return -1;
362
363
    /*
364
     *   We now know neither one is transparent or attenuated, and we've
365
     *   already transformed obscured into distant, so the only possible
366
     *   values remaining are distant and opaque.  We know also they're
367
     *   not equal, because we would have already returned if that were
368
     *   the case.  So, we can conclude that one must be distant and the
369
     *   other must be opaque.  Hence, the one that's opaque is the less
370
     *   transparent one.  
371
     */
372
    if (a == opaque)
373
        return -1;
374
    else
375
        return 1;
376
}
377
378
/* ------------------------------------------------------------------------ */
379
/*
380
 *   Given a brightness level and a transparency level, compute the
381
 *   brightness as modified by the transparency level. 
382
 */
383
adjustBrightness(br, trans)
384
{
385
    switch(trans)
386
    {
387
    case transparent:
388
    case distant:
389
        /* 
390
         *   Transparent medium or distance - this doesn't modify
391
         *   brightness at all.  (Technically, distance would reduce
392
         *   brightness somewhat, but the typical scale of an IF setting
393
         *   isn't usually large enough that brightness should
394
         *   significantly diminish.)  
395
         */
396
        return br;
397
398
    case attenuated:
399
    case obscured:
400
        /* 
401
         *   Distant, obscured, or attenuated.  We reduce self-illuminating
402
         *   light (level 1) and dim light (level 2) to nothing (level 0),
403
         *   we leave nothing as nothing (obviously), and we reduce all
404
         *   other levels one step.  So, everything below level 3 goes to
405
         *   0, and everything at or above level 3 gets decremented by 1.  
406
         */
407
        return (br >= 3 ? br - 1 : 0);
408
409
    case opaque:
410
        /* opaque medium - nothing makes it through */
411
        return 0;
412
413
    default:
414
        /* shouldn't get to other cases */
415
        return nil;
416
    }
417
}
418
419
/* ------------------------------------------------------------------------ */
420
/*
421
 *   SenseConnector: an object that can pass senses across room
422
 *   boundaries.  This is a mix-in class: add it to the superclass list of
423
 *   the object before Thing (or a Thing subclass).
424
 *   
425
 *   A SenseConnector acts as a sense conduit across all of its locations,
426
 *   so to establish a connection between locations, simply place a
427
 *   SenseConnector in each location.  Since a SenseConnector is useful
428
 *   only when placed placed in multiple locations, SenseConnector is
429
 *   based on MultiLoc.  
430
 */
431
class SenseConnector: MultiLoc
432
    /*
433
     *   A SenseConnector's material generally determines how senses pass
434
     *   through the connection.  
435
     */
436
    connectorMaterial = adventium
437
438
    /*
439
     *   Determine how senses pass through this connection.  By default,
440
     *   we simply use the material's transparency.
441
     */
442
    transSensingThru(sense) { return connectorMaterial.senseThru(sense); }
443
444
    /*
445
     *   Add the direct containment connections for this item to a lookup
446
     *   table. 
447
     *   
448
     *   Since we provide a sense connection among all of our containers,
449
     *   add each of our containers to the list.  
450
     */
451
    addDirectConnections(tab)
452
    {
453
        /* add myself */
454
        tab[self] = true;
455
            
456
        /* add my CollectiveGroup objects */
457
        foreach (local cur in collectiveGroups)
458
            tab[cur] = true;
459
460
        /* add my contents */
461
        foreach (local cur in contents)
462
        {
463
            if (tab[cur] == nil)
464
                cur.addDirectConnections(tab);
465
        }
466
467
        /* add my containers */
468
        foreach (local cur in locationList)
469
        {
470
            if (tab[cur] == nil)
471
                cur.addDirectConnections(tab);
472
        }
473
    }
474
475
    /*
476
     *   Transmit energy from a container onto me.
477
     */
478
    shineFromWithout(fromParent, sense, ambient, fill)
479
    {
480
        /* if this increases my ambient level, accept the new level */
481
        if (ambient > tmpAmbient_)
482
        {
483
            local levelThru;
484
            
485
            /* remember the new level and fill material to this point */
486
            tmpAmbient_ = ambient;
487
            tmpAmbientFill_ = fill;
488
489
            /* transmit to my contents */
490
            shineOnContents(sense, ambient, fill);
491
492
            /*
493
             *   We must transmit this energy to each of our other
494
             *   parents, possibly reduced for traversing our connector.
495
             *   Calculate the new level after traversing our connector. 
496
             */
497
            levelThru = adjustBrightness(ambient, transSensingThru(sense));
498
499
            /* 
500
             *   if there's anything left, transmit it to the other
501
             *   containers 
502
             */
503
            if (levelThru >= 2)
504
            {
505
                /* transmit to each container except the source */
506
                foreach (local cur in locationList)
507
                {
508
                    /* if this isn't the sender, transmit to it */
509
                    if (cur != fromParent)
510
                        cur.shineFromWithin(self, sense, levelThru, fill);
511
                }
512
            }
513
        }
514
    }
515
516
    /*
517
     *   Build a sense path from a container to me
518
     */
519
    sensePathFromWithout(fromParent, sense, trans, obs, fill)
520
    {
521
        /* 
522
         *   if there's better transparency along this path than along any
523
         *   previous path we've used to visit this item, take this path 
524
         */
525
        if (transparencyCompare(trans, tmpTrans_) > 0)
526
        {
527
            local transThru;
528
            
529
            /* remember the new path to this point */
530
            tmpTrans_ = trans;
531
            tmpObstructor_ = obs;
532
533
            /* we're coming to this object from outside */
534
            tmpPathIsIn_ = true;
535
536
            /* transmit to my contents */
537
            sensePathToContents(sense, trans, obs, fill);
538
539
            /*
540
             *   We must transmit this energy to each of our other
541
             *   parents, possibly reduced for traversing our connector.
542
             *   Calculate the new level after traversing our connector. 
543
             */
544
            transThru = transparencyAdd(trans, transSensingThru(sense));
545
546
            /* if we changed the transparency, we're the obstructor */
547
            if (transThru != trans)
548
                obs = self;
549
550
            /* 
551
             *   if there's anything left, transmit it to the other
552
             *   containers 
553
             */
554
            if (transThru != opaque)
555
            {
556
                /* transmit to each container except the source */
557
                foreach (local cur in locationList)
558
                {
559
                    /* if this isn't the sender, transmit to it */
560
                    if (cur != fromParent)
561
                        cur.sensePathFromWithin(self, sense,
562
                                                transThru, obs, fill);
563
                }
564
            }
565
        }
566
    }
567
568
    /* 
569
     *   Call a function on each connected container.  Since we provide a
570
     *   sense path connection among our containers, we must iterate over
571
     *   each of our containers.  
572
     */
573
    forEachConnectedContainer(func, [args])
574
    {
575
        forEachContainer(func, args...);
576
    }
577
578
    /* 
579
     *   Return a list of my connected containers.  We connect to all of
580
     *   our containers, so simply return my location list. 
581
     */
582
    getConnectedContainers = (locationList)
583
584
    /* 
585
     *   Check moving an object through me.  This is called when we try to
586
     *   move an object from one of our containers to another of our
587
     *   containers through me.  By default, we don't allow it.  
588
     */
589
    checkMoveThrough(obj, dest)
590
    {
591
        /* return an error - cannot move through <self> */
592
        return new CheckStatusFailure(&cannotMoveThroughMsg, obj, self);
593
    }
594
595
    /*
596
     *   Check touching an object through me.  This is called when an
597
     *   actor tries to reach from one of my containers through me into
598
     *   another of my containers.  By default, we don't allow it. 
599
     */
600
    checkTouchThrough(obj, dest)
601
    {
602
        /* return an error - cannot reach through <self> */
603
        return new CheckStatusFailure(&cannotReachThroughMsg, dest, self);
604
    }
605
606
    /* check for moving via a path */
607
    checkMoveViaPath(obj, dest, op)
608
    {
609
        /* if moving through us, don't allow it */
610
        if (op == PathThrough)
611
            return checkMoveThrough(obj, dest);
612
613
        /* if we can inherit, do so */
614
        if (canInherit())
615
            return inherited(obj, dest, op);
616
617
        /* return success by default */
618
        return checkStatusSuccess;
619
    }
620
621
    /* check for touching via a path */
622
    checkTouchViaPath(obj, dest, op)
623
    {
624
        /* if moving through us, don't allow it */
625
        if (op == PathThrough)
626
            return checkTouchThrough(obj, dest);
627
628
        /* if we can inherit, do so */
629
        if (canInherit())
630
            return inherited(obj, dest, op);
631
632
        /* return success by default */
633
        return checkStatusSuccess;
634
    }
635
;
636
637
/*
638
 *   Occluder: this is a mix-in class that can be used with multiple
639
 *   inheritance to combine with other classes (such as SenseConnector, or
640
 *   Thing subclasses), to create an "occluded view."  This lets you
641
 *   exclude certain objects from view, and you can make the exclusion vary
642
 *   according to the point of view.
643
 *   
644
 *   This class is useful for situations where the view from one location
645
 *   to another is partially obstructed.  For example, suppose we have two
646
 *   rooms, connected by a window between them.  The window is the sense
647
 *   connector that connects the two top-level locations, and it makes
648
 *   objects in one room visible from the point of view of the other room.
649
 *   Suppose that one room contains a bookcase, with its back to the
650
 *   window.  From the point of view of the other room, we can't see
651
 *   anything inside the bookcase.  This class allows for such special
652
 *   situations.
653
 *   
654
 *   Note that occlusion rules are applied "globally" within a room - that
655
 *   is, anything that an Occluder occludes will be removed from view, even
656
 *   if it's visible from another, non-occluding connector.  Hence,
657
 *   occlusion always takes precedence over "inclusion" - if an object is
658
 *   occluded just once, then it won't be in view, no matter how many times
659
 *   it's added back into view by other connectors.  This comes from the
660
 *   order in which the occlusion rules are considered.  Occlusion rules
661
 *   are always run last, and they can't distinguish the connector that
662
 *   added an object to view.  So, we first run around and collect up
663
 *   everything that can be seen, by considering all of the different paths
664
 *   to seeing those things.  Then, we go through all of the occlusion
665
 *   rules that apply to the room, and we remove from view everything that
666
 *   the occluding connectors want to occlude.  
667
 */
668
class Occluder: object
669
    /*
670
     *   Do we occlude the given object, in the given sense and from the
671
     *   given point of view?  This returns true if the object is occluded,
672
     *   nil if not.  By default, we simply ask the object whether it's
673
     *   occluded by this occluder from the given POV.  
674
     */
675
    occludeObj(obj, sense, pov)
676
    {
677
        /* by default, simply ask the object what it thinks */
678
        return obj.isOccludedBy(self, sense, pov);
679
    }
680
681
    /*
682
     *   When we initialize for the sense path calculation, register to
683
     *   receive notification after we've finished building the sense
684
     *   table.  We'll use the notification to remove any occluded objects
685
     *   from the sense table.  
686
     */
687
    clearSenseInfo()
688
    {
689
        /* do the normal work */
690
        inherited();
691
692
        /* register for notification after we've built the table */
693
        senseTmp.notifyList.append(self);
694
    }
695
696
    /*
697
     *   Receive notification that the sense path calculation is now
698
     *   finished.  'objs' is a LookupTable containing all of the objects
699
     *   involved in the sense path calculation (the objects are the keys
700
     *   in the table).  Each object in the table now has its tmpXxx_
701
     *   properties set to the sense path data we've calculated for that
702
     *   object - tmpTrans_ is the transparency to the object, tmpAmbient_
703
     *   is the ambient light level at the object, and so on.
704
     *   
705
     *   Since our job is to occlude certain objects from view, we'll run
706
     *   through the table and test each object using our occlusion rule.
707
     *   If we find that we do occlude an object, we'll set its
708
     *   transparency to 'opaque' to indicate that it cannot be seen.  
709
     */
710
    finishSensePath(objs, sense)
711
    {
712
        /* get the point of view of the calculation */
713
        local pov = senseTmp.pointOfView;
714
            
715
        /* run through the table, and apply our rule to each object */
716
        objs.forEachAssoc(new function(key, val)
717
        {
718
            /* if this object is occluded, set its path to opaque */
719
            if (occludeObj(key, sense, pov))
720
            {
721
                /* set this object to opaque */
722
                key.tmpTrans_ = key.tmpTransWithin_ = opaque;
723
724
                /* we're the obstructor for the object */
725
                key.tmpObstructor_ = key.tmpObstructorWithin_ = self;
726
            }
727
        });
728
    }
729
;
730
731
/*
732
 *   DistanceConnector: a special type of SenseConnector that connects its
733
 *   locations with distance.  This can be used for things like divided
734
 *   rooms, where a single physical location is modeled with two or more
735
 *   Room objects - the north and south end of a large cave, for example.
736
 *   This is also useful for cases where two rooms are separate but open to
737
 *   one another, such as a balcony overlooking a courtyard.
738
 *   
739
 *   Note that this inherits from both SenseConnector and Intangible.
740
 *   Intangible is included as a base class because each instance will need
741
 *   to derive from Thing, so that it fits into the normal sense model, but
742
 *   will virtually never need any other physical presence in the game
743
 *   world; Intangible fills both of these needs.  
744
 */
745
class DistanceConnector: SenseConnector, Intangible
746
    /* all senses are connected through us, but at a distance */
747
    transSensingThru(sense) { return distant; }
748
749
    /* 
750
     *   When checking for reaching through this connector, specialize the
751
     *   failure message to indicate that distance is the specific problem.
752
     *   (Without this specialization, we'd get a generic message when
753
     *   trying to reach through the connector, such as "you can't reach
754
     *   that through <self>."  
755
     */
756
    checkTouchThrough(obj, dest)
757
    {
758
        /* we can't touch through this connector due to the distance */
759
        return new CheckStatusFailure(&tooDistantMsg, dest);
760
    }
761
762
    /* 
763
     *   Do allow moving an object through a distance connector.  This
764
     *   should generally only be involved at all when we're moving an
765
     *   object programmatically, in which case we should already have
766
     *   decided that the movement is allowable.  Any command that tries to
767
     *   move an object through a distance connector will almost certainly
768
     *   have a suitable set of preconditions that checks for reachability,
769
     *   which will in most cases disallow the action anyway before we get
770
     *   to the point of wanting to move anything.  
771
     */
772
    checkMoveThrough(obj, dest) { return checkStatusSuccess; }
773
774
    /* 
775
     *   If we're responsible for blocking a thrown object's flight, we
776
     *   need to provide a custom message.  The default says that we
777
     *   deflected the object as though we were a physical barrier.
778
     *   Instead, in our case, the problem isn't deflection but simply
779
     *   range - so we need to explain that the projectile fell short of
780
     *   the target.
781
     *   
782
     *   By default, we assume that this connector interposes such a great
783
     *   distance that a character can't throw something all the way to the
784
     *   other side, which is why we provide this special message.  If you
785
     *   do want to allow actors to throw things all the way through the
786
     *   distance connector to the connected location, you can override
787
     *   checkMoveThrough like this:
788
     *   
789
     *   checkMoveThrough(obj, dest) { return checkStatusSuccess; } 
790
     */
791
    throwTargetHitWith(projectile, path)
792
    {
793
        /* 
794
         *   figure out where we fall to when we hit this object, then send
795
         *   the object being thrown to that location 
796
         */
797
        getHitFallDestination(projectile, path)
798
            .receiveDrop(projectile, new DropTypeShortThrow(self, path));
799
    }
800
;
801
802
/*
803
 *   A drop-type descriptor for a "short throw," which occurs when the
804
 *   target is too far away to reach with our throw (i.e., the thrown
805
 *   object falls short of the target).  
806
 */
807
class DropTypeShortThrow: DropTypeThrow
808
    construct(target, path)
809
    {
810
        /* inherit the default handling */
811
        inherited(target, path);
812
813
        /* we care about the *intended* target, not the distance connector */
814
        target_ = path[path.length()];
815
    }
816
817
    standardReport(obj, dest)
818
    {
819
        /* show the short-throw report */
820
        mainReport(&throwFallShortMsg, obj, target_,
821
                   dest.getNominalDropDestination());
822
    }
823
824
    getReportPrefix(obj, dest)
825
    {
826
        /* return the short-throw prefix */
827
        return gActor.getActionMessageObj().throwShortMsg(obj, target_);
828
    }
829
;
830