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

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#charset "us-ascii"
2
3
/* 
4
 *   Copyright (c) 2000, 2006 Michael J. Roberts.  All Rights Reserved. 
5
 *   
6
 *   TADS 3 Library: Pre-Conditions.
7
 *   
8
 *   This module defines the library pre-conditions.  A pre-condition is an
9
 *   abstract object that encapsulates a condition that is required to
10
 *   apply before a command can be executed, and optionally an implied
11
 *   command that can bring the condition into effect.  Pre-conditions can
12
 *   be associated with actions or with the objects of an action.  
13
 */
14
15
#include "adv3.h"
16
17
18
/* ------------------------------------------------------------------------ */
19
/*
20
 *   An action pre-condition object.  Each condition of an action is
21
 *   represented by a subclass of this class.  
22
 */
23
class PreCondition: object
24
    /*
25
     *   Check the condition on the given object (which may be nil, if
26
     *   this condition doesn't apply specifically to one of the objects
27
     *   in the command).  If it is possible to meet the condition with an
28
     *   implicit command, and allowImplicit is true, try to execute the
29
     *   command.  If the condition cannot be met, report a failure and
30
     *   use 'exit' to terminate the command.
31
     *   
32
     *   If allowImplicit is nil, an implicit command may not be
33
     *   attempted.  In this case, if the condition is not met, we must
34
     *   simply report a failure and use 'exit' to terminate the command.
35
     */
36
    checkPreCondition(obj, allowImplicit) { }
37
38
    /*
39
     *   Verify the condition.  This is called during the object
40
     *   verification step so that the pre-condition can add verifications
41
     *   of its own.  This can be used, for example, to add likelihood to
42
     *   objects that already meet the condition.  Note that it is
43
     *   generally not desirable to report illogical for conditions that
44
     *   checkPreCondition() enforces, because doing so will prevent
45
     *   checkPreCondition() from ever being reached and thus will prevent
46
     *   checkPreCondition() from attempting to carry out implicit actions
47
     *   to meet the condition.
48
     *   
49
     *   'obj' is the object being checked.  Note that because this is
50
     *   called during verification, the explicitly passed-in object must
51
     *   be used in the check rather than the current object in the global
52
     *   current action.  
53
     */
54
    verifyPreCondition(obj) { }
55
56
    /*
57
     *   Precondition execution order.  When we execute preconditions for a
58
     *   given action, we'll sort the list of all applicable preconditions
59
     *   in ascending execution order.
60
     *   
61
     *   For the most part, the relative order of two preconditions is
62
     *   arbitrary.  In some unusual cases, though, the order is important,
63
     *   such as when applying one precondition can destroy the conditions
64
     *   that the other would try to create but not vice versa.  When the
65
     *   order doesn't matter, this can be left at the default setting.  
66
     */
67
    preCondOrder = 100
68
;
69
70
/* ------------------------------------------------------------------------ */
71
/*
72
 *   A pre-condition that applies to a specific, pre-determined object,
73
 *   rather than the direct/indirect object of the command.
74
 */
75
class ObjectPreCondition: PreCondition
76
    construct(obj, cond)
77
    {
78
        /* 
79
         *   remember the specific object I act upon, and the underlying
80
         *   precondition to apply to that object 
81
         */
82
        obj_ = obj;
83
        cond_ = cond;
84
    }
85
86
    /* route our check to the pre-condition using our specific object */
87
    checkPreCondition(obj, allowImplicit)
88
    {
89
        /* check the precondition */
90
        return cond_.checkPreCondition(obj_, allowImplicit);
91
    }
92
93
    /* route our verification check to the pre-condition */
94
    verifyPreCondition(obj)
95
    {
96
        cond_.verifyPreCondition(obj_);
97
    }
98
99
    /* use the same order as our underlying condition */
100
    preCondOrder = (cond_.preCondOrder)
101
102
    /* the object we check with the condition */
103
    obj_ = nil
104
105
    /* the pre-condition we check */
106
    cond_ = nil
107
;
108
109
110
/* ------------------------------------------------------------------------ */
111
/*
112
 *   Pre-condition: object must be visible.  This condition doesn't
113
 *   attempt any implied command to make the object visible, but merely
114
 *   enforces visibility before allowing the command.
115
 *   
116
 *   This condition is useful for commands that rely on visibly inspecting
117
 *   the object, such as "examine" or "look in".  It is possible for an
118
 *   object to be in scope without being visible, since an object can be
119
 *   in scope by way of a non-visual sense.
120
 *   
121
 *   We enforce visibility with a verification test, not a precondition
122
 *   check.  
123
 */
124
objVisible: PreCondition
125
    verifyPreCondition(obj)
126
    {
127
        /* if the object isn't visible, disallow the command */
128
        if (obj != nil && !gActor.canSee(obj))
129
        {
130
            /*
131
             *   If the actor is in the dark, that must be the problem.
132
             *   Otherwise, if the object can be heard or smelled but not
133
             *   seen, say so.  In any other case, issue a generic message
134
             *   that we can't see the object.  
135
             */
136
            if (!gActor.isLocationLit())
137
                inaccessible(&tooDarkMsg);
138
            else if (obj.soundPresence && gActor.canHear(obj))
139
                inaccessible(&heardButNotSeenMsg, obj);
140
            else if (obj.smellPresence && gActor.canSmell(obj))
141
                inaccessible(&smelledButNotSeenMsg, obj);
142
            else
143
                inaccessible(&mustBeVisibleMsg, obj);
144
        }
145
    }
146
;
147
148
/* ------------------------------------------------------------------------ */
149
/*
150
 *   Pre-condition: object must be audible; that is, it must be within
151
 *   hearing range of the actor.  This condition doesn't attempt any
152
 *   implied command to make the object audible, but merely enforces
153
 *   audibility before allowing the command.
154
 *   
155
 *   It is possible for an object to be in scope without being audible,
156
 *   since an object can be inside a container that is transparent to
157
 *   light but blocks all sound.
158
 *   
159
 *   We enforce this condition with a verification test.  
160
 */
161
objAudible: PreCondition
162
    verifyPreCondition(obj)
163
    {
164
        /* if the object isn't audible, disallow the command */
165
        if (obj != nil && !gActor.canHear(obj))
166
            inaccessible(&cannotHearMsg, obj);
167
    }
168
;
169
170
/* ------------------------------------------------------------------------ */
171
/*
172
 *   Pre-condition: object must be within smelling range of the actor.
173
 *   This condition doesn't attempt any implied command to make the object
174
 *   smellable, but merely enforces the condition before allowing the
175
 *   command.
176
 *   
177
 *   It is possible for an object to be in scope without being smellable,
178
 *   since an object can be inside a container that is transparent to
179
 *   light but blocks all odors.
180
 *   
181
 *   We enforce this condition with a verification test.  
182
 */
183
objSmellable: PreCondition
184
    verifyPreCondition(obj)
185
    {
186
        /* if the object isn't within sense range, disallow the command */
187
        if (obj != nil && !gActor.canSmell(obj))
188
            inaccessible(&cannotSmellMsg, obj);
189
    }
190
;
191
192
/* ------------------------------------------------------------------------ */
193
/*
194
 *   Pre-condition: actor must be standing.  This is useful for travel
195
 *   commands to ensure that the actor is free of any entanglements from
196
 *   nested rooms prior to travel.  
197
 */
198
actorStanding: PreCondition
199
    checkPreCondition(obj, allowImplicit)
200
    {
201
        /* check to see if the actor is standing - if so, we're done */
202
        if (gActor.posture == standing)
203
            return nil;
204
205
        /* the actor isn't standing - try a "stand up" command */
206
        if (allowImplicit && tryImplicitAction(Stand))
207
        {
208
            /* 
209
             *   make sure that leaves the actor standing - if not,
210
             *   exit silently, since the reason for failure will have
211
             *   been reported by the "stand up" action 
212
             */
213
            if (gActor.posture != standing)
214
                exit;
215
            
216
            /* indicate that we executed an implicit command */
217
            return true;
218
        }
219
        
220
        /* we can't stand up implicitly - report the problem and exit */
221
        reportFailure(&mustBeStandingMsg);
222
        exit;
223
    }
224
;
225
226
/*
227
 *   Pre-condition: actor must be "travel ready."  The exact meaning of
228
 *   "travel ready" is provided by the actor's immediately container.  The
229
 *   'obj' argument is always the travel connector to be traversed.  
230
 */
231
actorTravelReady: PreCondition
232
    checkPreCondition(obj, allowImplicit)
233
    {
234
        local loc = gActor.location;
235
        
236
        /* check to see if the actor is standing - if so, we're done */
237
        if (loc.isActorTravelReady(obj))
238
            return nil;
239
        
240
        /* the actor isn't standing - try a "stand up" command */
241
        if (allowImplicit && gActor.location.tryMakingTravelReady(obj))
242
        {
243
            /* 
244
             *   make sure that the actor really is travel-ready now - if
245
             *   not, exit silently, since the reason for failure will have
246
             *   been reported by the implicit action 
247
             */
248
            if (!loc.isActorTravelReady(obj))
249
                exit;
250
            
251
            /* indicate that we executed an implicit command */
252
            return true;
253
        }
254
        
255
        /* we can't make the actor travel-ready - report failure and exit */
256
        reportFailure(loc.notTravelReadyMsg);
257
        exit;
258
    }
259
;
260
261
/* ------------------------------------------------------------------------ */
262
/*
263
 *   Pre-condition: the traveler is directly in the given room.  This will
264
 *   attempt to remove the traveler from any nested rooms within the given
265
 *   room, but cannot perform travel between rooms not related by
266
 *   containment.
267
 *   
268
 *   Note that the traveler is not necessarily the actor, because the actor
269
 *   could be in a vehicle.
270
 *   
271
 *   This is a class, because it has to be instantiated with more
272
 *   parameters than just a single 'obj' passed by default when evaluating
273
 *   preconditions.  In particular, we need to know the actor performing
274
 *   the travel, the connector being traversed, and the room we need to be
275
 *   directly in.  
276
 */
277
class TravelerDirectlyInRoom: PreCondition
278
    construct(actor, conn, loc)
279
    {
280
        /* remember the actor, connector, and room */
281
        actor_ = actor;
282
        conn_ = conn;
283
        loc_ = loc;
284
    }
285
    
286
    checkPreCondition(obj, allowImplicit)
287
    {
288
        /* ask the traveler to do the work */
289
        return actor_.getTraveler(conn_)
290
            .checkDirectlyInRoom(loc_, allowImplicit);
291
    }
292
293
    /* the actor doing the travel */
294
    actor_ = nil
295
296
    /* the connector being traversed */
297
    conn_ = nil
298
299
    /* the room we need to be directly in  */
300
    loc_ = nil
301
;
302
303
/*
304
 *   Pre-condition: the actor is directly in the given room.  This differs
305
 *   from TravelerDirectlyInRoom in that this operates directly on the
306
 *   actor, regardless of whether the actor is in a vehicle.  
307
 */
308
actorDirectlyInRoom: PreCondition
309
    checkPreCondition(obj, allowImplicit)
310
    {
311
        /* ask the actor to do the work */
312
        return gActor.checkDirectlyInRoom(obj, allowImplicit);
313
    }
314
;
315
316
/* ------------------------------------------------------------------------ */
317
/*
318
 *   Pre-condition: actor is ready to enter a nested location.  This is
319
 *   useful for commands that cause travel within a location, such as "sit
320
 *   on chair": this ensures that the actor is either already in the given
321
 *   nested location, or is in the main location; and that the actor is
322
 *   standing.  We simply call the actor to do the work.  
323
 */
324
actorReadyToEnterNestedRoom: PreCondition
325
    checkPreCondition(obj, allowImplicit)
326
    {
327
        /* ask the actor to make the determination */
328
        return gActor.checkReadyToEnterNestedRoom(obj, allowImplicit);
329
    }
330
;
331
332
/* ------------------------------------------------------------------------ */
333
/*
334
 *   Pre-condition: the target actor must be able to talk to the object.
335
 *   This is useful for actions that require communications, such as ASK
336
 *   ABOUT, TELL ABOUT, and TALK TO.  
337
 */
338
canTalkToObj: PreCondition
339
    checkPreCondition(obj, allowImplicit)
340
    {
341
        /* 
342
         *   if the current actor can't talk to the given object, disallow
343
         *   the command 
344
         */
345
        if (obj != nil && !gActor.canTalkTo(obj))
346
        {
347
            reportFailure(&objCannotHearActorMsg, obj);
348
            exit;
349
        }
350
351
        /* we don't perform any implicit commands */
352
        return nil;
353
    }
354
;
355
356
/* ------------------------------------------------------------------------ */
357
/*
358
 *   Pre-condition: object must be held.  This condition requires that an
359
 *   object of a command must be held by the actor.  If it is not, we will
360
 *   attempt a recursive "take" command on the object.
361
 *   
362
 *   This condition is useful for commands where the object is to be
363
 *   manipulated in some way, or used to manipulate some other object.
364
 *   For example, the key in "unlock door with key" would normally have to
365
 *   be held.  
366
 */
367
objHeld: PreCondition
368
    checkPreCondition(obj, allowImplicit)
369
    {
370
        /* if the object is already held, there's nothing we need to do */
371
        if (obj == nil || obj.meetsObjHeld(gActor))
372
            return nil;
373
        
374
        /* the object isn't being held - try an implicit 'take' command */
375
        if (allowImplicit && obj.tryHolding())
376
        {
377
            /* 
378
             *   we successfully executed the command; check to make sure
379
             *   it worked, and if not, abort the command without further
380
             *   comment (if the command failed, presumably the command
381
             *   showed an explanation as to why) 
382
             */
383
            if (!obj.meetsObjHeld(gActor))
384
                exit;
385
386
            /* tell the caller we executed an implicit command */
387
            return true;
388
        }
389
390
        /* it's not held and we can't take it - fail */
391
        reportFailure(&mustBeHoldingMsg, obj);
392
393
        /* make it the pronoun */
394
        gActor.setPronounObj(obj);
395
396
        /* abort the command */
397
        exit;
398
    }
399
400
    /* lower the likelihood rating for anything not being held */
401
    verifyPreCondition(obj)
402
    {
403
        /* if the object isn't being held, reduce its likelihood rating */
404
        if (obj != nil && !obj.meetsObjHeld(gActor))
405
            logicalRankOrd(80, 'implied take', 150);
406
    }
407
;
408
409
/* ------------------------------------------------------------------------ */
410
/*
411
 *   Pre-condition: a given source object must be able to touch the
412
 *   object.  This requires that the source object (given by our property
413
 *   'sourceObj') has a clear 'touch' path to the target object.
414
 *   
415
 *   This is a base class for arbitrary object-to-object touch conditions.
416
 *   In most cases, you'll want to use the more specific touchObj, which
417
 *   tests that the current actor can touch the current object.  
418
 */
419
class TouchObjCondition: PreCondition
420
    /* construct with a given source object */
421
    construct(src) { sourceObj = src; }
422
423
    /* 
424
     *   the source object - this is the object that is attempting to
425
     *   touch the target object 
426
     */
427
    sourceObj = nil
428
429
    /* check the condition */
430
    checkPreCondition(obj, allowImplicit)
431
    {
432
        local pastObs;
433
        
434
        /* 
435
         *   If we can touch the object, we can proceed with no implicit
436
         *   actions.
437
         */
438
        if (sourceObj.canTouch(obj))
439
            return nil;
440
        
441
        /* we haven't tried removing any obstructors yet */
442
        pastObs = new Vector(8);
443
        
444
        /*
445
         *   Repeatedly look for and attempt to remove obstructions.
446
         *   There could be multiple things in the way, so try to remove
447
         *   each one we find until either we fail to remove an
448
         *   obstruction or we run out of obstructions.  
449
         */
450
        for (;;)
451
        {
452
            local stat;
453
            local path;
454
            local result;
455
            local obs;
456
457
            /* get the path for reaching out and touching the object */
458
            path = sourceObj.getTouchPathTo(obj);
459
460
            /* if we have a path, look for an obstructor */
461
            if (path != nil)
462
            {
463
                /* traverse the path to find what blocks our touch */
464
                stat = sourceObj.traversePath(path, new function(ele, op)
465
                {
466
                    /*
467
                     *   If we can continue the reach via this path element,
468
                     *   simply keep going.  Otherwise, stop the reach here. 
469
                     */
470
                    result = ele.checkTouchViaPath(sourceObj, obj, op);
471
                    if (result.isSuccess)
472
                    {
473
                        /* no objection here - keep going */
474
                        return true;
475
                    }
476
                    else
477
                    {
478
                        /* stop here, noting the obstruction */
479
                        obs = ele;
480
                        return nil;
481
                    }
482
                });
483
484
                /* 
485
                 *   if we now have a clear path, we're done - simply return
486
                 *   true to indicate that we ran one or more implicit
487
                 *   commands 
488
                 */
489
                if (stat)
490
                    return true;
491
            }
492
            else
493
            {
494
                /* 
495
                 *   we have no path, so the object must be in an
496
                 *   unconnected location; we don't know the obstructor in
497
                 *   this case 
498
                 */
499
                obs = nil;
500
            }
501
502
            /*
503
             *   'result' is a CheckStatus object explaining why we can't
504
             *   reach past 'obs', which is the first object that
505
             *   obstructs our reach.
506
             *   
507
             *   If the obstructor is not visible or we couldn't find one,
508
             *   we can't do anything to try to remove it; simply report
509
             *   that we can't reach the target object and give up.  
510
             */
511
            if (obs == nil || !gActor.canSee(obs))
512
            {
513
                reportFailure(&cannotReachObjectMsg, obj);
514
                exit;
515
            }
516
517
            /* 
518
             *   Ask the obstructor to get out of the way if possible.
519
             *   
520
             *   If we've already tried to remove this same obstructor on
521
             *   a past iteration, don't try again, as there's no reason
522
             *   to think an implicit command will work any better this
523
             *   time.
524
             */
525
            if (pastObs.indexOf(obs) != nil
526
                || !allowImplicit
527
                || !obs.tryImplicitRemoveObstructor(touch, obj))
528
            {
529
                /* 
530
                 *   We can't remove the obstruction - either we've tried
531
                 *   an implicit command on this same obstructor and
532
                 *   failed, or we can't try an implicit command at all.
533
                 *   In any case, use the explanation of the problem from
534
                 *   the CheckStatus result object.  
535
                 */
536
                reportFailure(result.msgProp, result.msgParams...);
537
                exit;
538
            }
539
540
            /* 
541
             *   if the implied command failed, simply give up now -
542
             *   there's no need to go on, since the implied command will
543
             *   have already explained why it failed 
544
             */
545
            if (gTranscript.currentActionHasReport({x: x.isFailure}))
546
                exit;
547
548
            /* 
549
             *   We've tried an implied command to remove this obstructor,
550
             *   but that isn't guaranteed to make the target touchable,
551
             *   as there could be further obstrutions, or the implied
552
             *   command could have failed to actually remove the
553
             *   obstruction.  Keep iterating.  To avoid looping forever
554
             *   in the event the implicit command we just tried isn't
555
             *   good enough to remove this obstruction, make a note of
556
             *   the obstruction we just tried to remove; if we find it
557
             *   again on a subsequent iteration, we'll know that we've
558
             *   tried before to remove it and failed, and thus we'll know
559
             *   to give up without making the same doomed attempt again. 
560
             */
561
            pastObs.append(obs);
562
        }
563
    }
564
565
    verifyPreCondition(obj)
566
    {
567
        /*
568
         *   If there's no source object, do nothing at this point.  We can
569
         *   have a nil source object when we're resolving nouns for a
570
         *   two-object action, and we have a cross-object condition (for
571
         *   example, we require that the indirect object can touch the
572
         *   direct object).  In these cases, when we're resolving the
573
         *   first-resolved noun phrase, the second-resolved noun phrase
574
         *   won't be known yet.  The only purpose of verification at times
575
         *   like these is to improve our guess about an ambiguous match,
576
         *   but we have nothing to add at such times, so we can simply
577
         *   return without doing anything.  
578
         */
579
        if (sourceObj == nil)
580
            return;
581
582
        /* if we can't touch the object, make it less likely */
583
        if (!sourceObj.canTouch(obj))
584
        {
585
            /* 
586
             *   If we can't see the object, we must be able to sense it
587
             *   by some means other than sight, so it must have a
588
             *   sufficiently distinctive sound or odor to put it in
589
             *   scope.  Explain this: "you can hear it but you can't see
590
             *   it", or the like.  
591
             */
592
            if (gActor.canSee(obj))
593
            {
594
                local info;
595
                
596
                /* 
597
                 *   It's visible but cannot be reached from here, so it
598
                 *   must be too far away, inside a closed but transparent
599
                 *   container, or something like that.
600
                 *   
601
                 *   If it's at a distance, rule it illogical, since
602
                 *   there's not usually anything automatic we can do to
603
                 *   remove the distance obstruction.
604
                 *   
605
                 *   If it's not distant, don't rule it illogical, but do
606
                 *   reduce the likelihood ranking, so that we'll prefer a
607
                 *   different object that can readily be touched.  Since
608
                 *   we can see where the object is, we might know how to
609
                 *   remove the obstruction to reachability.  
610
                 */
611
                info = gActor.bestVisualInfo(obj);
612
                if (info != nil && info.trans == distant)
613
                {
614
                    /* it's distant - assume we can't fix this */
615
                    inaccessible(&tooDistantMsg, obj);
616
                }
617
                else
618
                {
619
                    /* 
620
                     *   it's not distant; rank it logical (since we might
621
                     *   be able to clear the obstruction with an implied
622
                     *   action), but at reduced likelihood (in case
623
                     *   there's something that doesn't need any prior
624
                     *   implied action to reach) 
625
                     */
626
                    logicalRankOrd(80, 'unreachable but visible', 150);
627
                }
628
            }
629
            else
630
            {
631
                /* 
632
                 *   if it has a sound presence, then "you can hear it but
633
                 *   you can't see it"; if it has a smell presence, then
634
                 *   "you can smell it but you can't see it"; otherwise,
635
                 *   you simply can't see it 
636
                 */
637
                if (obj.soundPresence && gActor.canHear(obj))
638
                {
639
                    /* it can be heard but not seen */
640
                    inaccessible(&heardButNotSeenMsg, obj);
641
                }
642
                else if (obj.smellPresence && gActor.canSmell(obj))
643
                {
644
                    /* it can be smelled but not seen */
645
                    inaccessible(&smelledButNotSeenMsg, obj);
646
                }
647
                else if (!gActor.isLocationLit())
648
                {
649
                    /* it's too dark to see the object */
650
                    inaccessible(&tooDarkMsg);
651
                }
652
                else
653
                {
654
                    /* it simply cannot be seen */
655
                    inaccessible(&mustBeVisibleMsg, obj);
656
                }
657
            }
658
        }
659
    }
660
661
    /*
662
     *   This condition tends to be fragile, in the sense that other
663
     *   preconditions for the same action have the potential to undo any
664
     *   implicit action that we perform to make an object touchable.  This
665
     *   is most likely to happen when we implicitly move the actor (moving
666
     *   in or out of a nested room, for example) to put the actor within
667
     *   reach of the target object.  To reduce the likelihood that this
668
     *   fragility will be visible to a player, try to execute this
669
     *   condition after other conditions.  Most other preconditions tend
670
     *   to be "stickier" - less likely to be undone by subsequent
671
     *   preconditions.  
672
     */
673
    preCondOrder = 200
674
;
675
676
/* ------------------------------------------------------------------------ */
677
/*
678
 *   Pre-condition: actor must be able to touch the object.  This doesn't
679
 *   require that the actor is actually holding the object, but the actor
680
 *   must be able to physically touch the object.  This ensures that the
681
 *   actor and object are not, for example, separated by a transparent
682
 *   barrier.
683
 *   
684
 *   If there is a transparent barrier, we will attempt to remove the
685
 *   barrier by calling the barrier object's tryImplicitRemoveObstructor
686
 *   method.  Objects that can be opened in an obvious fashion will
687
 *   perform an implicit recursive "open" command, and other types of
688
 *   objects can provide customized behavior as appropriate.  
689
 */
690
touchObj: TouchObjCondition
691
    /* we want to test reaching from the current actor to the target object */
692
    sourceObj = (gActor)
693
;
694
695
/*
696
 *   Pre-condition: the indirect object must be able to touch the target
697
 *   object.  This can be used for actions where the direct object is going
698
 *   to be manipulated by an "agent" of the action (i.e., the indirect
699
 *   object), rather than directly by the actor: MOVE X WITH Y, for
700
 *   example.
701
 *   
702
 *   Note that the target object of this condition should be the direct
703
 *   object in most cases, so this condition should usually be used like
704
 *   this:
705
 *   
706
 *   dobjFor(MoveWith) { preCond = [iobjTouchObj] }
707
 *   
708
 *   In other words, this is a precondition that we apply in most cases to
709
 *   the *direct* object.  
710
 */
711
iobjTouchObj: TouchObjCondition
712
    /* the indirect object has to be able to touch the target object */
713
    sourceObj = (gIobj)
714
;
715
716
/*
717
 *   Pre-condition: the direct object can touch the target object.  This
718
 *   is useful for situations where the direct object is being manipulated
719
 *   directly and the indirect object is more of a passive participant in
720
 *   the action, such as PLUG CORD INTO OUTLET.
721
 */
722
dobjTouchObj: TouchObjCondition
723
    /* the direct object has to be able to touch the target object */
724
    sourceObj = (gDobj)
725
;
726
727
/* ------------------------------------------------------------------------ */
728
/*
729
 *   A precondition ensuring that the target object is in the same
730
 *   immediate location as a given object.
731
 */
732
class SameLocationCondition: PreCondition
733
    /* 
734
     *   construct dynamically, setting the other object whose location we
735
     *   must match 
736
     */
737
    construct(obj) { sourceObj = obj; }
738
739
    /* the object whose location we must match */
740
    sourceObj = nil
741
742
    /* check the condition */
743
    checkPreCondition(obj, allowImplicit)
744
    {
745
        local moveObj;
746
        local targetLoc;
747
        
748
        /* if we're in the same container, we're fine */
749
        if (obj.location == sourceObj.location)
750
            return nil;
751
752
        /* 
753
         *   Pick an object to move.  By default, pick the target object;
754
         *   but if the target object is non-portable, then trying to move
755
         *   it will fail, so pick the source object.  
756
         */
757
        if (obj.ofKind(NonPortable))
758
        {
759
            /* 'obj' is unportable, so try moving the source object */
760
            moveObj = sourceObj;
761
            targetLoc = obj.location;
762
        }
763
        else
764
        {
765
            /* 'obj' is portable, so try moving it by default */
766
            moveObj = obj;
767
            targetLoc = sourceObj.location;
768
        }
769
770
        /* try moving the object, and return the result */
771
        if (allowImplicit && targetLoc.tryMovingObjInto(moveObj))
772
        {
773
            /* if it didn't work, abort the action */
774
            if (obj.location != sourceObj.location)
775
                exit;
776
777
            /* tell the caller we executed an implied action */
778
            return true;
779
        }
780
781
        /* we can't move it - report the failure and abort the action */
782
        targetLoc.mustMoveObjInto(moveObj);
783
        exit;
784
    }
785
;
786
787
/* 
788
 *   require that the target object be in the same immediate location as
789
 *   the direct object 
790
 */
791
sameLocationAsDobj: SameLocationCondition
792
    sourceObj = (gDobj)
793
;
794
795
/* 
796
 *   require that the target object be in the same immediate location as
797
 *   the indirect object 
798
 */
799
sameLocationAsIobj: SameLocationCondition
800
    sourceObj = (gIobj)
801
;
802
803
/* ------------------------------------------------------------------------ */
804
/*
805
 *   Pre-condition: actor must have room to hold the object directly (such
806
 *   as in the actor's hands).  We'll let the actor do the work.
807
 */
808
roomToHoldObj: PreCondition
809
    checkPreCondition(obj, allowImplicit)
810
    {
811
        /* let the actor check the precondition */
812
        return gActor.tryMakingRoomToHold(obj, allowImplicit);
813
    }
814
;
815
816
/* ------------------------------------------------------------------------ */
817
/*
818
 *   Pre-condition: the actor must not be wearing the object.  If the
819
 *   actor is currently wearing the object, we'll try asking the actor to
820
 *   doff the object.
821
 *   
822
 *   Note that this pre-condition never needs to be combined with objHeld,
823
 *   because an object being worn is not considered to be held, and
824
 *   Wearable implicitly doffs an article when it must be held.  
825
 */
826
objNotWorn: PreCondition
827
    checkPreCondition(obj, allowImplicit)
828
    {
829
        /* if the object isn't being worn, we have nothing to do */
830
        if (obj == nil || !obj.isWornBy(gActor))
831
            return nil;
832
        
833
        /* try an implicit 'doff' command */
834
        if (allowImplicit && tryImplicitAction(Doff, obj))
835
        {
836
            /* 
837
             *   we executed the command - make sure it worked, and abort
838
             *   if it didn't 
839
             */
840
            if (obj.isWornBy(gActor))
841
                exit;
842
843
            /* tell the caller we executed an implicit command */
844
            return true;
845
        }
846
847
        /* report the problem and terminate the command */
848
        reportFailure(&cannotBeWearingMsg, obj);
849
850
        /* make it the pronoun */
851
        gActor.setPronounObj(obj);
852
853
        /* abort the command */
854
        exit;
855
    }
856
857
    /* lower the likelihood rating for anything being worn */
858
    verifyPreCondition(obj)
859
    {
860
        /* if the object is being worn, reduce its likelihood rating */
861
        if (obj != nil && obj.isWornBy(gActor))
862
            logicalRankOrd(80, 'implied doff', 150);
863
    }
864
;
865
866
/* ------------------------------------------------------------------------ */
867
/*
868
 *   Pre-condition: the object is open.
869
 */
870
class ObjOpenCondition: PreCondition
871
    checkPreCondition(obj, allowImplicit)
872
    {
873
        /* if the object is already open, we're already done */
874
        if (obj == nil || obj.isOpen)
875
            return nil;
876
        
877
        /* try an implicit 'open' command on the object */
878
        if (allowImplicit && tryImplicitAction(Open, obj))
879
        {
880
            /* 
881
             *   we executed the command - make sure it worked, and abort
882
             *   if it didn't 
883
             */
884
            if (!obj.isOpen)
885
                exit;
886
887
            /* tell the caller we executed an implied command */
888
            return true;
889
        }
890
891
        /* can't open it implicitly - report the failure */
892
        conditionFailed(obj);
893
        exit;
894
    }
895
896
    /*
897
     *   The condition failed - report the failure and give up.  We
898
     *   separate this to allow subclasses to report failure differently
899
     *   for specialized types of opening.  
900
     */
901
    conditionFailed(obj)
902
    {
903
        /* can't open it implicitly - report failure and give up */
904
        reportFailure(&mustBeOpenMsg, obj);
905
906
        /* make it the pronoun */
907
        gActor.setPronounObj(obj);
908
    }
909
910
    /* reduce the likelihood rating for anything that isn't already open */
911
    verifyPreCondition(obj)
912
    {
913
        /* if the object is closed, reduce its likelihood rating */
914
        if (obj != nil && !obj.isOpen)
915
            logicalRankOrd(80, 'implied open', 150);
916
    }
917
;
918
919
/*
920
 *   The basic object-open condition 
921
 */
922
objOpen: ObjOpenCondition;
923
924
/*
925
 *   Pre-condition: a door must be open.  This differs from the regular
926
 *   objOpen condition only in that we use a customized version of the
927
 *   failure report. 
928
 */
929
doorOpen: ObjOpenCondition
930
    conditionFailed(obj)
931
    {
932
        /* 
933
         *   We can generate implicit open-door commands as a result of
934
         *   travel, which means that the actor issuing the command might
935
         *   never have explicitly referred to the door.  (This is not the
936
         *   case for most preconditions, which refer to objects directly
937
         *   used in the command and thus within the actor's awareness, at
938
         *   least initially.)  So, if the door isn't visible to the
939
         *   actor, don't tell the actor they have to open the door;
940
         *   instead, just show the standard no-travel message for the
941
         *   door.  
942
         */
943
        if (gActor.canSee(obj))
944
        {
945
            /* they can see the door, so tell them they need to open it */
946
            reportFailure(&mustOpenDoorMsg, obj);
947
948
            /* set this as the pronoun antecedent */
949
            gActor.setPronounObj(obj);
950
        }
951
        else
952
        {
953
            /* 
954
             *   they can't see the door - call the door's routine to
955
             *   indicate that travel is not possible 
956
             */
957
            obj.cannotTravel();
958
        }
959
    }
960
;
961
962
/* ------------------------------------------------------------------------ */
963
/*
964
 *   Pre-condition: the object is closed.
965
 */
966
objClosed: PreCondition
967
    checkPreCondition(obj, allowImplicit)
968
    {
969
        /* if the object is already closed, we're already done */
970
        if (obj == nil || !obj.isOpen)
971
            return nil;
972
        
973
        /* try an implicit 'close' command on the object */
974
        if (allowImplicit && tryImplicitAction(Close, obj))
975
        {
976
            /* 
977
             *   we executed the command - make sure it worked, and abort
978
             *   if it didn't 
979
             */
980
            if (obj.isOpen)
981
                exit;
982
983
            /* tell the caller we executed an implied command */
984
            return true;
985
        }
986
987
        /* can't close it implicitly - report failure and give up */
988
        reportFailure(&mustBeClosedMsg, obj);
989
990
        /* make it the pronoun */
991
        gActor.setPronounObj(obj);
992
993
        /* abort the command */
994
        exit;
995
    }
996
997
    /* reduce the likelihood rating for anything that isn't already closed */
998
    verifyPreCondition(obj)
999
    {
1000
        /* if the object is closed, reduce its likelihood rating */
1001
        if (obj != nil && obj.isOpen)
1002
            logicalRankOrd(80, 'implied close', 150);
1003
    }
1004
;
1005
1006
/* ------------------------------------------------------------------------ */
1007
/*
1008
 *   Pre-condition: the object is unlocked.
1009
 */
1010
objUnlocked: PreCondition
1011
    checkPreCondition(obj, allowImplicit)
1012
    {
1013
        /* if the object is already unlocked, we're already done */
1014
        if (obj == nil || !obj.isLocked)
1015
            return nil;
1016
        
1017
        /* try an implicit 'unlock' command on the object */
1018
        if (allowImplicit && tryImplicitAction(Unlock, obj))
1019
        {
1020
            /* 
1021
             *   we executed the command - make sure it worked, and abort
1022
             *   if it didn't 
1023
             */
1024
            if (obj.isLocked)
1025
                exit;
1026
1027
            /* tell the caller we executed an implied command */
1028
            return true;
1029
        }
1030
1031
        /* can't unlock it implicitly - report failure and give up */
1032
        reportFailure(&mustBeUnlockedMsg, obj);
1033
1034
        /* make it the pronoun */
1035
        gActor.setPronounObj(obj);
1036
1037
        /* abort the command */
1038
        exit;
1039
    }
1040
1041
    /* reduce the likelihood rating for anything that's locked */
1042
    verifyPreCondition(obj)
1043
    {
1044
        /* if the object is locked, reduce its likelihood rating */
1045
        if (obj != nil && obj.isLocked)
1046
            logicalRankOrd(80, 'implied unlock', 150);
1047
    }
1048
;
1049
1050
/* ------------------------------------------------------------------------ */
1051
/*
1052
 *   Pre-condition: destination for "drop" is an outermost room.  If the
1053
 *   drop destination is a nested room, we'll try returning the actor to
1054
 *   the outermost room via an implicit command.  
1055
 */
1056
dropDestinationIsOuterRoom: PreCondition
1057
    checkPreCondition(obj, allowImplicit)
1058
    {
1059
        local dest;
1060
        
1061
        /* 
1062
         *   if the actor's location's drop location is the outermost
1063
         *   room, we don't need to do anything special 
1064
         */
1065
        dest = gActor.getDropDestination(obj, nil);
1066
        if (dest.getOutermostRoom() == dest)
1067
            return nil;
1068
1069
        /* 
1070
         *   the default drop destination is not an outermost room; try an
1071
         *   implicit command to return the actor to an outermost room 
1072
         */
1073
        return actorDirectlyInRoom.checkPreCondition(
1074
            dest.getOutermostRoom(), allowImplicit);
1075
    }
1076
;
1077
1078
/* ------------------------------------------------------------------------ */
1079
/*
1080
 *   Pre-condition: object is burning.  This can be used for matches,
1081
 *   candles, and the like.  If the object's isLit is nil, we'll attempt a
1082
 *   "burn" command on the object.  
1083
 */
1084
objBurning: PreCondition
1085
    checkPreCondition(obj, allowImplicit)
1086
    {
1087
        /* if it's already burning, there's nothing to do */
1088
        if (obj == nil || obj.isLit)
1089
            return nil;
1090
1091
        /* try an implicit 'burn' command */
1092
        if (allowImplicit && tryImplicitAction(Burn, obj))
1093
        {
1094
            /* we executed a 'burn' - give up if it didn't work */
1095
            if (!obj.isLit)
1096
                exit;
1097
1098
            /* tell the caller we executed an implied command */
1099
            return true;
1100
        }
1101
1102
        /* we can't burn it implicitly - report failure and give up */
1103
        reportFailure(&mustBeBurningMsg, obj);
1104
1105
        /* make it the pronoun */
1106
        gActor.setPronounObj(obj);
1107
1108
        /* abort the command */
1109
        exit;
1110
    }
1111
1112
    verifyPreCondition(obj)
1113
    {
1114
        /* if the object is not already burning, reduce its likelihood */
1115
        if (obj != nil && !obj.isLit)
1116
            logicalRankOrd(80, 'implied burn', 150);
1117
    }
1118
;
1119
1120
1121
/* ------------------------------------------------------------------------ */
1122
/*
1123
 *   Pre-condition: the object is empty.  This ensures that the object
1124
 *   does not contain any other objects.
1125
 *   
1126
 *   Note that we unconditionally try to remove all objects.  If a
1127
 *   container needs to have some objects that can be removed and others
1128
 *   that can't (such as components within the container), then the
1129
 *   container will have to be implemented as a ComplexContainer - the
1130
 *   non-removable components should be made contents of the enclosing
1131
 *   ComplexContainer, and the secret inner container should be the one
1132
 *   subject to this precondition.  
1133
 */
1134
objEmpty: PreCondition
1135
    checkPreCondition(obj, allowImplicit)
1136
    {
1137
        local chi;
1138
1139
        /* 
1140
         *   if there's no object, or the object already has no contents,
1141
         *   there's nothing to do 
1142
         */
1143
        if (obj == nil || obj.contents.length() == 0)
1144
            return nil;
1145
1146
        /* 
1147
         *   Try an implicit 'take x' on the object's first child.
1148
         *   
1149
         *   Note that we only try this on the first object, because the
1150
         *   precondition mechanism automatically re-applies all
1151
         *   preconditions after any one of them performs an implied
1152
         *   command.  If we have multiple objects that must be removed,
1153
         *   that basic loop will ensure that we'll come back here as many
1154
         *   times as necessary.  
1155
         */
1156
        chi = obj.contents[1];
1157
        if (allowImplicit && tryImplicitAction(TakeFrom, chi, obj))
1158
        {
1159
            /* make sure it worked */
1160
            if (chi.isIn(obj))
1161
                exit;
1162
            
1163
            /* tell the caller we tried an implied command */
1164
            return true;
1165
        }
1166
            
1167
        /* we can't remove the objects implicitly, so give up */
1168
        reportFailure(&mustBeEmptyMsg, obj);
1169
1170
        /* make it the pronoun */
1171
        gActor.setPronounObj(obj);
1172
1173
        /* abort the command */
1174
        exit;
1175
    }
1176
;