Abolish a silly layer of macros.
[open-adventure.git] / actions.c
1 #include <stdlib.h>
2 #include <stdbool.h>
3 #include "advent.h"
4 #include "database.h"
5
6 #define VRSION  25      /* bump on save format change */
7
8 /*
9  * Action handlers.  Eventually we'll do lookup through a method table
10  * that calls these.  Absolutely nothing like the original FORTRAN.
11  */
12
13 static int fill(long);
14
15 static int attack(FILE *input, long verb, long obj)
16 /*  Attack.  Assume target if unambiguous.  "Throw" also links here.
17  *  Attackable objects fall into two categories: enemies (snake,
18  *  dwarf, etc.)  and others (bird, clam, machine).  Ambiguous if 2
19  *  enemies, or no enemies but 2 others. */
20 {
21     int i =ATDWRF(game.loc);
22     if (obj == 0) {
23         if (i > 0)
24             obj=DWARF;
25         if (HERE(SNAKE))obj=obj*NOBJECTS+SNAKE;
26         if (AT(DRAGON) && game.prop[DRAGON] == 0)obj=obj*NOBJECTS+DRAGON;
27         if (AT(TROLL))obj=obj*NOBJECTS+TROLL;
28         if (AT(OGRE))obj=obj*NOBJECTS+OGRE;
29         if (HERE(BEAR) && game.prop[BEAR] == 0)obj=obj*NOBJECTS+BEAR;
30         if (obj > NOBJECTS) return(8000);
31         if (obj == 0) {
32             /* Can't attack bird or machine by throwing axe. */
33             if (HERE(BIRD) && verb != THROW)obj=BIRD;
34             if (HERE(VEND) && verb != THROW)obj=obj*NOBJECTS+VEND;
35             /* Clam and oyster both treated as clam for intransitive case;
36              * no harm done. */
37             if (HERE(CLAM) || HERE(OYSTER))obj=NOBJECTS*obj+CLAM;
38             if (obj > NOBJECTS) return(8000);
39         }
40     }
41     if (obj == BIRD) {
42         SPK=137;
43         if (game.closed) return(2011);
44         DSTROY(BIRD);
45         game.prop[BIRD]=0;
46         SPK=45;
47     }
48     if (obj == VEND) {
49         PSPEAK(VEND,game.prop[VEND]+2);
50         game.prop[VEND]=3-game.prop[VEND];
51         return(2012);
52     }
53
54     if (obj == 0)SPK=44;
55     if (obj == CLAM || obj == OYSTER)SPK=150;
56     if (obj == SNAKE)SPK=46;
57     if (obj == DWARF)SPK=49;
58     if (obj == DWARF && game.closed) return(19000);
59     if (obj == DRAGON)SPK=167;
60     if (obj == TROLL)SPK=157;
61     if (obj == OGRE)SPK=203;
62     if (obj == OGRE && i > 0) {
63         RSPEAK(SPK);
64         RSPEAK(6);
65         DSTROY(OGRE);
66         int k=0;
67         for (i=1; i < PIRATE; i++) {
68             if (game.dloc[i] == game.loc) {
69                 ++k;
70                 game.dloc[i]=61;
71                 game.dseen[i]=false;
72             }
73         }
74         SPK=SPK+1+1/k;
75         return(2011);
76     }
77     if (obj == BEAR)SPK=165+(game.prop[BEAR]+1)/2;
78     if (obj != DRAGON || game.prop[DRAGON] != 0) return(2011);
79     /*  Fun stuff for dragon.  If he insists on attacking it, win!
80      *  Set game.prop to dead, move dragon to central loc (still
81      *  fixed), move rug there (not fixed), and move him there,
82      *  too.  Then do a null motion to get new description. */
83     RSPEAK(49);
84     GETIN(input,&WD1,&WD1X,&WD2,&WD2X);
85     if (WD1 != MAKEWD(25) && WD1 != MAKEWD(250519))
86         return(2607);
87     PSPEAK(DRAGON,3);
88     game.prop[DRAGON]=1;
89     game.prop[RUG]=0;
90     int k=(PLAC[DRAGON]+FIXD[DRAGON])/2;
91     MOVE(DRAGON+NOBJECTS,-1);
92     MOVE(RUG+NOBJECTS,0);
93     MOVE(DRAGON,k);
94     MOVE(RUG,k);
95     DROP(BLOOD,k);
96     for (obj=1; obj<=NOBJECTS; obj++) {
97         if (game.place[obj] == PLAC[DRAGON] || game.place[obj] == FIXD[DRAGON])
98             MOVE(obj,k);
99     }
100     game.loc=k;
101     K=NUL;      /* FIXME: error if removed */
102     return(8);
103 }
104
105 static int bigwords(long foo)
106 /*  FEE FIE FOE FOO (AND FUM).  Advance to next state if given in proper order.
107  *  Look up WD1 in section 3 of vocab to determine which word we've got.  Last
108  *  word zips the eggs back to the giant room (unless already there). */
109 {
110     int k=VOCAB(foo,3);
111     SPK=42;
112     if (game.foobar != 1-k) {
113         if (game.foobar != 0)SPK=151;
114         return(2011);
115     } else {
116         game.foobar=k;
117         if (k != 4) return(2009);
118         game.foobar=0;
119         if (game.place[EGGS] == PLAC[EGGS] || (TOTING(EGGS) && game.loc == PLAC[EGGS])) 
120             return(2011);
121         /*  Bring back troll if we steal the eggs back from him before
122          *  crossing. */
123         if (game.place[EGGS]==0 && game.place[TROLL]==0 && game.prop[TROLL]==0)
124             game.prop[TROLL]=1;
125         k=2;
126         if (HERE(EGGS))k=1;
127         if (game.loc == PLAC[EGGS])k=0;
128         MOVE(EGGS,PLAC[EGGS]);
129         PSPEAK(EGGS,k);
130         return(2012);
131     }
132 }
133
134 static int bivalve(token_t verb, token_t obj)
135 /* Clam/oyster actions */
136 {
137     int k=0;
138     if (obj == OYSTER)k=1;
139     SPK=124+k;
140     if (TOTING(obj))SPK=120+k;
141     if (!TOTING(TRIDNT))SPK=122+k;
142     if (verb == LOCK)SPK=61;
143     if (SPK != 124)
144         return(2011);
145     DSTROY(CLAM);
146     DROP(OYSTER,game.loc);
147     DROP(PEARL,105);
148     return(2011);
149 }
150
151 static int blast(void)
152 /*  Blast.  No effect unless you've got dynamite, which is a neat trick! */
153 {
154     if (game.prop[ROD2] < 0 || !game.closed) return(2011);
155     game.bonus=133;
156     if (game.loc == 115)game.bonus=134;
157     if (HERE(ROD2))game.bonus=135;
158     RSPEAK(game.bonus);
159     score(0);
160 }
161
162 static int vbreak(token_t obj)
163 /*  Break.  Only works for mirror in repository and, of course, the vase. */
164 {
165     if (obj == MIRROR)SPK=148;
166     if (obj == VASE && game.prop[VASE] == 0) {
167         SPK=198;
168         if (TOTING(VASE))DROP(VASE,game.loc);
169         game.prop[VASE]=2;
170         game.fixed[VASE]= -1;
171         return(2011);
172     } else {
173         if (obj != MIRROR || !game.closed) return(2011);
174         SPK=197;
175         return(18999);
176     }
177 }
178
179 static int brief(void)
180 /*  Brief.  Intransitive only.  Suppress long descriptions after first time. */
181 {
182     SPK=156;
183     game.abbnum=10000;
184     game.detail=3;
185     return(2011);
186 }
187
188 static int carry(long obj)
189 /*  Carry an object.  Special cases for bird and cage (if bird in cage, can't
190  *  take one without the other).  Liquids also special, since they depend on
191  *  status of bottle.  Also various side effects, etc. */
192 {
193     if (obj == INTRANSITIVE) {
194         /*  Carry, no object given yet.  OK if only one object present. */
195         if(game.atloc[game.loc] == 0 ||
196            game.link[game.atloc[game.loc]] != 0 ||
197            ATDWRF(game.loc) > 0)
198             return(8000);
199         obj=game.atloc[game.loc];
200     }
201
202     if (TOTING(obj)) return(2011);
203     SPK=25;
204     if (obj == PLANT && game.prop[PLANT] <= 0)SPK=115;
205     if (obj == BEAR && game.prop[BEAR] == 1)SPK=169;
206     if (obj == CHAIN && game.prop[BEAR] != 0)SPK=170;
207     if (obj == URN)SPK=215;
208     if (obj == CAVITY)SPK=217;
209     if (obj == BLOOD)SPK=239;
210     if (obj == RUG && game.prop[RUG] == 2)SPK=222;
211     if (obj == SIGN)SPK=196;
212     if (obj == MESSAG) {
213         SPK=190;
214         DSTROY(MESSAG);
215     }
216     if (game.fixed[obj] != 0)
217         return(2011);
218     if (obj == WATER || obj == OIL) {
219         if (!HERE(BOTTLE) || LIQ(0) != obj) {
220             if (TOTING(BOTTLE) && game.prop[BOTTLE] == 1)
221                 return(fill(BOTTLE));
222             if (game.prop[BOTTLE] != 1)SPK=105;
223             if (!TOTING(BOTTLE))SPK=104;
224             return(2011);
225         }
226         obj = BOTTLE;
227     }
228
229     SPK=92;
230     if (game.holdng >= INVLIMIT)
231         return(2011);
232     if (obj == BIRD && game.prop[BIRD] != 1 && -1-game.prop[BIRD] != 1) {
233         if (game.prop[BIRD] == 2) {
234             SPK=238;
235             DSTROY(BIRD);
236             return(2011);
237         }
238         if (!TOTING(CAGE))SPK=27;
239         if (TOTING(ROD))SPK=26;
240         if (SPK/2 == 13) return(2011);
241         game.prop[BIRD]=1;
242     }
243     if ((obj==BIRD || obj==CAGE) && (game.prop[BIRD]==1 || -1-game.prop[BIRD]==1))
244         CARRY(BIRD+CAGE-obj,game.loc);
245     CARRY(obj,game.loc);
246     if (obj == BOTTLE && LIQ(0) != 0)
247         game.place[LIQ(0)] = -1;
248     if (!GSTONE(obj) || game.prop[obj] == 0)
249         return(2009);
250     game.prop[obj]=0;
251     game.prop[CAVITY]=1;
252     return(2009);
253 }
254
255 static int chain(token_t verb)
256 /* Do something to the bear's chain */
257 {
258     if (verb != LOCK) {
259         SPK=171;
260         if (game.prop[BEAR] == 0)SPK=41;
261         if (game.prop[CHAIN] == 0)SPK=37;
262         if (SPK != 171) return(2011);
263         game.prop[CHAIN]=0;
264         game.fixed[CHAIN]=0;
265         if (game.prop[BEAR] != 3)game.prop[BEAR]=2;
266         game.fixed[BEAR]=2-game.prop[BEAR];
267         return(2011);
268     } else {
269         SPK=172;
270         if (game.prop[CHAIN] != 0)SPK=34;
271         if (game.loc != PLAC[CHAIN])SPK=173;
272         if (SPK != 172) return(2011);
273         game.prop[CHAIN]=2;
274         if (TOTING(CHAIN))DROP(CHAIN,game.loc);
275         game.fixed[CHAIN]= -1;
276         return(2011);
277     }
278 }
279
280 static int discard(long obj, bool just_do_it)
281 /*  Discard object.  "Throw" also comes here for most objects.  Special cases for
282  *  bird (might attack snake or dragon) and cage (might contain bird) and vase.
283  *  Drop coins at vending machine for extra batteries. */
284 {
285     if (!just_do_it) {
286         if (TOTING(ROD2) && obj == ROD && !TOTING(ROD))obj=ROD2;
287         if (!TOTING(obj)) return(2011);
288         if (obj == BIRD && HERE(SNAKE)) {
289             RSPEAK(30);
290             if (game.closed) return(19000);
291             DSTROY(SNAKE);
292             /* Set game.prop for use by travel options */
293             game.prop[SNAKE]=1;
294
295         } else if ((GSTONE(obj) && AT(CAVITY) && game.prop[CAVITY] != 0)) {
296             RSPEAK(218);
297             game.prop[obj]=1;
298             game.prop[CAVITY]=0;
299             if (HERE(RUG) && ((obj == EMRALD && game.prop[RUG] != 2) || (obj == RUBY &&
300                                                                          game.prop[RUG] == 2))) {
301                 SPK=219;
302                 if (TOTING(RUG))SPK=220;
303                 if (obj == RUBY)SPK=221;
304                 RSPEAK(SPK);
305                 if (SPK != 220) {
306                     K=2-game.prop[RUG];
307                     game.prop[RUG]=K;
308                     if (K == 2)K=PLAC[SAPPH];
309                     MOVE(RUG+NOBJECTS,K);
310                 }
311             }
312         } else if (obj == COINS && HERE(VEND)) {
313             DSTROY(COINS);
314             DROP(BATTER,game.loc);
315             PSPEAK(BATTER,0);
316             return(2012);
317         } else if (obj == BIRD && AT(DRAGON) && game.prop[DRAGON] == 0) {
318             RSPEAK(154);
319             DSTROY(BIRD);
320             game.prop[BIRD]=0;
321             return(2012);
322         } else if (obj == BEAR && AT(TROLL)) {
323             RSPEAK(163);
324             MOVE(TROLL,0);
325             MOVE(TROLL+NOBJECTS,0);
326             MOVE(TROLL2,PLAC[TROLL]);
327             MOVE(TROLL2+NOBJECTS,FIXD[TROLL]);
328             JUGGLE(CHASM);
329             game.prop[TROLL]=2;
330         } else if (obj != VASE || game.loc == PLAC[PILLOW]) {
331             RSPEAK(54);
332         } else {
333             game.prop[VASE]=2;
334             if (AT(PILLOW))game.prop[VASE]=0;
335             PSPEAK(VASE,game.prop[VASE]+1);
336             if (game.prop[VASE] != 0)game.fixed[VASE]= -1;
337         }
338     }
339     K=LIQ(0);
340     if (K == obj)obj=BOTTLE;
341     if (obj == BOTTLE && K != 0)game.place[K]=0;
342     if (obj == CAGE && game.prop[BIRD] == 1)DROP(BIRD,game.loc);
343     DROP(obj,game.loc);
344     if (obj != BIRD) return(2012);
345     game.prop[BIRD]=0;
346     if (FOREST(game.loc))game.prop[BIRD]=2;
347     return(2012);
348 }
349
350 static int drink(token_t obj)
351 /*  Drink.  If no object, assume water and look for it here.  If water is in
352  *  the bottle, drink that, else must be at a water loc, so drink stream. */
353 {
354     if (obj == 0 && LIQLOC(game.loc) != WATER && (LIQ(0) != WATER || !HERE(BOTTLE)))
355         return(8000);
356     if (obj != BLOOD) {
357         if (obj != 0 && obj != WATER)SPK=110;
358         if (SPK == 110 || LIQ(0) != WATER || !HERE(BOTTLE)) return(2011);
359         game.prop[BOTTLE]=1;
360         game.place[WATER]=0;
361         SPK=74;
362         return(2011);
363     } else {
364         DSTROY(BLOOD);
365         game.prop[DRAGON]=2;
366         OBJSND[BIRD]=OBJSND[BIRD]+3;
367         SPK=240;
368         return(2011);
369     }
370 }
371
372 static int eat(token_t obj)
373 /*  Eat.  Intransitive: assume food if present, else ask what.  Transitive: food
374  *  ok, some things lose appetite, rest are ridiculous. */
375 {
376     if (obj == INTRANSITIVE) {
377         if (!HERE(FOOD))
378             return(8000);
379         DSTROY(FOOD);
380         SPK=72;
381         return(2011);
382     } else {
383         if (obj == FOOD) {
384             DSTROY(FOOD);
385             SPK=72;
386             return(2011);
387         }
388         if (obj == BIRD || obj == SNAKE || obj == CLAM || obj == OYSTER || obj ==
389            DWARF || obj == DRAGON || obj == TROLL || obj == BEAR || obj ==
390            OGRE)SPK=71;
391         return(2011);
392     }
393 }
394
395 static int extinguish(int obj)
396 /* Extinguish.  Lamp, urn, dragon/volcano (nice try). */
397 {
398     if (obj == INTRANSITIVE) {
399         if (HERE(LAMP) && game.prop[LAMP] == 1)obj=LAMP;
400         if (HERE(URN) && game.prop[URN] == 2)obj=obj*NOBJECTS+URN;
401         if (obj == 0 || obj > NOBJECTS) return(8000);
402     }
403
404     if (obj == URN) {
405         game.prop[URN]=game.prop[URN]/2;
406         SPK=210;
407         return(2011);
408     }
409     if (obj == LAMP) {
410         game.prop[LAMP]=0;
411         RSPEAK(40);
412         if (DARK(0))
413             RSPEAK(16);
414         return(2012);
415     }
416     if (obj == DRAGON || obj == VOLCAN)
417         SPK=146;
418     return(2011);
419 }
420
421 static int feed(long obj)
422 /*  Feed.  If bird, no seed.  Snake, dragon, troll: quip.  If dwarf, make him
423  *  mad.  Bear, special. */
424 {
425     if (obj == BIRD) {
426         SPK=100;
427         return(2011);
428     }
429
430     if (!(obj != SNAKE && obj != DRAGON && obj != TROLL)) {
431         SPK=102;
432         if (obj == DRAGON && game.prop[DRAGON] != 0)SPK=110;
433         if (obj == TROLL)SPK=182;
434         if (obj != SNAKE || game.closed || !HERE(BIRD))
435             return(2011);
436         SPK=101;
437         DSTROY(BIRD);
438         game.prop[BIRD]=0;
439         return(2011);
440     }
441
442     if (obj == DWARF) {
443         if (!HERE(FOOD))
444             return(2011);
445         SPK=103;
446         game.dflag=game.dflag+2;
447         return(2011);
448     }
449
450     if (obj == BEAR) {
451         if (game.prop[BEAR] == 0)SPK=102;
452         if (game.prop[BEAR] == 3)SPK=110;
453         if (!HERE(FOOD))
454             return(2011);
455         DSTROY(FOOD);
456         game.prop[BEAR]=1;
457         game.fixed[AXE]=0;
458         game.prop[AXE]=0;
459         SPK=168;
460         return(2011);
461     }
462
463     if (obj == OGRE) {
464         if (HERE(FOOD))
465             SPK=202;
466         return(2011);
467     }
468
469     SPK=14;
470     return(2011);
471 }
472
473 int fill(long obj)
474 /*  Fill.  Bottle or urn must be empty, and liquid available.  (Vase
475  *  is nasty.) */
476 {
477     int k;
478     if (obj == VASE) {
479         SPK=29;
480         if (LIQLOC(game.loc) == 0)SPK=144;
481         if (LIQLOC(game.loc) == 0 || !TOTING(VASE))
482             return(2011);
483         RSPEAK(145);
484         game.prop[VASE]=2;
485         game.fixed[VASE]= -1;
486         return(discard(obj, true));
487     }
488
489     if (obj == URN){
490         SPK=213;
491         if (game.prop[URN] != 0) return(2011);
492         SPK=144;
493         k=LIQ(0);
494         if (k == 0 || !HERE(BOTTLE)) return(2011);
495         game.place[k]=0;
496         game.prop[BOTTLE]=1;
497         if (k == OIL)game.prop[URN]=1;
498         SPK=211+game.prop[URN];
499         return(2011);
500     }
501
502     if (obj != 0 && obj != BOTTLE)
503         return(2011);
504     if (obj == 0 && !HERE(BOTTLE))
505         return(8000);
506     SPK=107;
507     if (LIQLOC(game.loc) == 0)
508         SPK=106;
509     if (HERE(URN) && game.prop[URN] != 0)
510         SPK=214;
511     if (LIQ(0) != 0)
512         SPK=105;
513     if (SPK != 107)
514         return(2011);
515     game.prop[BOTTLE]=MOD(COND[game.loc],4)/2*2;
516     k=LIQ(0);
517     if (TOTING(BOTTLE))
518         game.place[k]= -1;
519     if (k == OIL)
520         SPK=108;
521     return(2011);
522 }
523
524 static int find(token_t obj)
525 /* Find.  Might be carrying it, or it might be here.  Else give caveat. */
526 {
527     if (AT(obj) ||
528        (LIQ(0) == obj && AT(BOTTLE)) ||
529        obj == LIQLOC(game.loc) ||
530        (obj == DWARF && ATDWRF(game.loc) > 0))
531         SPK=94;
532     if (game.closed)SPK=138;
533     if (TOTING(obj))SPK=24;
534     return(2011);
535 }
536
537 static int fly(token_t obj)
538 /* Fly.  Snide remarks unless hovering rug is here. */
539 {
540     if (obj == INTRANSITIVE) {
541         if (game.prop[RUG] != 2)SPK=224;
542         if (!HERE(RUG))SPK=225;
543         if (SPK/2 == 112) return(2011);
544         obj=RUG;
545     }
546
547     if (obj != RUG) return(2011);
548     SPK=223;
549     if (game.prop[RUG] != 2) return(2011);
550     game.oldlc2=game.oldloc;
551     game.oldloc=game.loc;
552     game.newloc=game.place[RUG]+game.fixed[RUG]-game.loc;
553     SPK=226;
554     if (game.prop[SAPPH] >= 0)SPK=227;
555     RSPEAK(SPK);
556     return(2);
557 }
558     
559 static int inven(token_t obj)
560 /* Inventory. If object, treat same as find.  Else report on current burden. */
561 {
562     int i;
563     SPK=98;
564     for (i=1; i<=NOBJECTS; i++) {
565         if (i == BEAR || !TOTING(i))
566             continue;
567         if (SPK == 98)RSPEAK(99);
568         game.blklin=false;
569         PSPEAK(i,-1);
570         game.blklin=true;
571         SPK=0;
572     }
573     if (TOTING(BEAR))
574         SPK=141;
575     return(2011);
576 }
577
578 int light(token_t obj)
579 /*  Light.  Applicable only to lamp and urn. */
580 {
581     if (obj == INTRANSITIVE) {
582         if (HERE(LAMP) && game.prop[LAMP] == 0 && game.limit >= 0)obj=LAMP;
583         if (HERE(URN) && game.prop[URN] == 1)obj=obj*NOBJECTS+URN;
584         if (obj == 0 || obj > NOBJECTS) return(8000);
585     }
586
587     if (obj == URN) {
588         SPK=38;
589         if (game.prop[URN] == 0)
590             return(2011);
591         SPK=209;
592         game.prop[URN]=2;
593         return(2011);
594     } else {
595         if (obj != LAMP) return(2011);
596         SPK=184;
597         if (game.limit < 0) return(2011);
598         game.prop[LAMP]=1;
599         RSPEAK(39);
600         if (game.wzdark) return(2000);
601         return(2012);
602     }    
603 }
604
605 static int listen(void)
606 /*  Listen.  Intransitive only.  Print stuff based on objsnd/locsnd. */
607 {
608     int i, k;
609     SPK=228;
610     k=LOCSND[game.loc];
611     if (k != 0) {
612         RSPEAK(labs(k));
613         if (k < 0) return(2012);
614         SPK=0;
615     }
616     SETPRM(1,game.zzword,0);
617     for (i=1; i<=NOBJECTS; i++) {
618         if (!HERE(i) || OBJSND[i] == 0 || game.prop[i] < 0)
619             continue;
620         PSPEAK(i,OBJSND[i]+game.prop[i]);
621         SPK=0;
622         if (i == BIRD && OBJSND[i]+game.prop[i] == 8)
623             DSTROY(BIRD);
624     }
625     return(2011);
626 }
627
628 static int lock(token_t verb, token_t obj)
629 /* Lock, unlock, no object given.  Assume various things if present. */
630 {
631     int k;
632     if (obj == INTRANSITIVE) {
633         SPK=28;
634         if (HERE(CLAM))obj=CLAM;
635         if (HERE(OYSTER))obj=OYSTER;
636         if (AT(DOOR))obj=DOOR;
637         if (AT(GRATE))obj=GRATE;
638         if (obj != 0 && HERE(CHAIN)) return(8000);
639         if (HERE(CHAIN))obj=CHAIN;
640         if (obj == 0) return(2011);
641     }
642         
643     /*  Lock, unlock object.  Special stuff for opening clam/oyster
644      *  and for chain. */
645     if (obj == CLAM || obj == OYSTER)
646         return bivalve(verb, obj);
647     if (obj == DOOR)SPK=111;
648     if (obj == DOOR && game.prop[DOOR] == 1)SPK=54;
649     if (obj == CAGE)SPK=32;
650     if (obj == KEYS)SPK=55;
651     if (obj == GRATE || obj == CHAIN)SPK=31;
652     if (SPK != 31 || !HERE(KEYS)) return(2011);
653     if (obj == CHAIN)
654         return chain(verb);
655     if (game.closng) {
656         SPK=130;
657         if (!game.panic)game.clock2=15;
658         game.panic=true;
659         return(2011);
660     }
661     SPK=34+game.prop[GRATE];
662     game.prop[GRATE]=1;
663     if (verb == LOCK)game.prop[GRATE]=0;
664     SPK=SPK+2*game.prop[GRATE];
665     return(2011);
666 }
667
668 static int pour(token_t obj)
669 /*  Pour.  If no object, or object is bottle, assume contents of bottle.
670  *  special tests for pouring water or oil on plant or rusty door. */
671 {
672     if (obj == BOTTLE || obj == 0)obj=LIQ(0);
673     if (obj == 0) return(8000);
674     if (!TOTING(obj)) return(2011);
675     SPK=78;
676     if (obj != OIL && obj != WATER) return(2011);
677     if (HERE(URN) && game.prop[URN] == 0)
678         return fill(URN);
679     game.prop[BOTTLE]=1;
680     game.place[obj]=0;
681     SPK=77;
682     if (!(AT(PLANT) || AT(DOOR)))
683         return(2011);
684     if (!AT(DOOR)) {
685         SPK=112;
686         if (obj != WATER) return(2011);
687         PSPEAK(PLANT,game.prop[PLANT]+3);
688         game.prop[PLANT]=MOD(game.prop[PLANT]+1,3);
689         game.prop[PLANT2]=game.prop[PLANT];
690         K=NUL;
691         return(8);
692     } else {
693         game.prop[DOOR]=0;
694         if (obj == OIL)game.prop[DOOR]=1;
695         SPK=113+game.prop[DOOR];
696         return(2011);
697     }
698 }
699
700 static int quit(FILE *input)
701 /*  Quit.  Intransitive only.  Verify intent and exit if that's what he wants. */
702 {
703     if (YES(input,22,54,54))
704         score(1);
705     return(2012);
706 }
707
708 static int read(FILE *input, token_t obj)
709 /*  Read.  Print stuff based on objtxt.  Oyster (?) is special case. */
710 {
711     int i;
712     if (obj == INTRANSITIVE) {
713         obj = 0;
714         for (i=1; i<=NOBJECTS; i++) {
715             if (HERE(i) && OBJTXT[i] != 0 && game.prop[i] >= 0)
716                 obj = obj * NOBJECTS + i;
717         }
718         if (obj > NOBJECTS || obj == 0 || DARK(0)) return(8000);
719     }
720         
721     if (DARK(0)) {
722         SETPRM(1,WD1,WD1X);
723         RSPEAK(256);
724         return(2012);
725     }
726     if (OBJTXT[obj] == 0 || game.prop[obj] < 0)
727         return(2011);
728     if (obj == OYSTER && !game.clshnt) {
729         game.clshnt=YES(input,192,193,54);
730         return(2012);
731     }
732     PSPEAK(obj,OBJTXT[obj]+game.prop[obj]);
733     return(2012);
734 }
735
736 static int reservoir(void)
737 /*  Z'ZZZ (word gets recomputed at startup; different each game). */
738 {
739     if (!AT(RESER) && game.loc != game.fixed[RESER]-1) return(2011);
740     PSPEAK(RESER,game.prop[RESER]+1);
741     game.prop[RESER]=1-game.prop[RESER];
742     if (AT(RESER)) return(2012);
743     game.oldlc2=game.loc;
744     game.newloc=0;
745     RSPEAK(241);
746     return(2);
747 }
748
749 static int rub(token_t obj)
750 /* Rub.  Yields various snide remarks except for lit urn. */
751 {
752     if (obj != LAMP)SPK=76;
753     if (obj != URN || game.prop[URN] != 2) return(2011);
754     DSTROY(URN);
755     DROP(AMBER,game.loc);
756     game.prop[AMBER]=1;
757     game.tally=game.tally-1;
758     DROP(CAVITY,game.loc);
759     SPK=216;
760     return(2011);
761 }
762
763 static int say(void)
764 /* SAY.  Echo WD2 (or WD1 if no WD2 (SAY WHAT?, etc.).)  Magic words override. */
765 {
766     /* FIXME: ugly use of globals */
767     SETPRM(1,WD2,WD2X); if (WD2 <= 0)SETPRM(1,WD1,WD1X);
768     if (WD2 > 0)WD1=WD2;
769     int wd=VOCAB(WD1,-1);
770     if (wd == 62 || wd == 65 || wd == 71 || wd == 2025 || wd == 2034) {
771         WD2=0;
772         return(2630);
773     }
774     RSPEAK(258);
775     return(2012);
776
777 }
778
779 static int suspendresume(FILE *input, bool resume)
780 /* Suspend and resume */
781 {
782     int kk;
783     if (!resume) {
784         /*  Suspend.  Offer to save things in a file, but charging
785          *  some points (so can't win by using saved games to retry
786          *  battles or to start over after learning zzword). */
787         SPK=201;
788         RSPEAK(260);
789         if (!YES(input,200,54,54)) return(2012);
790         game.saved=game.saved+5;
791         kk= -1;
792     }
793     else
794     {
795         /*  Resume.  Read a suspended game back from a file. */
796         kk=1;
797         if (game.loc != 1 || game.abbrev[1] != 1) {
798             RSPEAK(268);
799             if (!YES(input,200,54,54)) return(2012);
800         }
801     }
802
803     /*  Suspend vs resume cases are distinguished by the value of kk
804      *  (-1 for suspend, +1 for resume). */
805
806     /* 
807      * FIXME: This is way more complicated than it needs to be in C.
808      * What we ought to do is define a save-block structure that
809      * includes a game state block and then use a single fread/fwrite
810      * for I/O. All the SAV* functions can be scrapped.
811      */
812
813     DATIME(&I,&K);
814     K=I+650*K;
815     SAVWRD(kk,K);
816     K=VRSION;
817     SAVWRD(0,K);
818     if (K != VRSION) {
819         SETPRM(1,K/10,MOD(K,10));
820         SETPRM(3,VRSION/10,MOD(VRSION,10));
821         RSPEAK(269);
822         return(2000);
823     }
824     /* Herewith are all the variables whose values can change during a game,
825      * omitting a few (such as I, J) whose values between turns are
826      * irrelevant and some whose values when a game is
827      * suspended or resumed are guaranteed to match.  If unsure whether a value
828      * needs to be saved, include it.  Overkill can't hurt.  Pad the last savwds
829      * with junk variables to bring it up to 7 values. */
830     SAVWDS(game.abbnum,game.blklin,game.bonus,game.clock1,game.clock2,game.closed,game.closng);
831     SAVWDS(game.detail,game.dflag,game.dkill,game.dtotal,game.foobar,game.holdng,game.iwest);
832     SAVWDS(game.knfloc,game.limit,K,game.lmwarn,game.loc,game.newloc,game.numdie);
833     SAVWDS(K,game.oldlc2,game.oldloc,game.oldobj,game.panic,game.saved,game.setup);
834     SAVWDS(SPK,game.tally,game.thresh,game.trndex,game.trnluz,game.turns,OBJTXT[OYSTER]);
835     SAVWDS(K,WD1,WD1X,WD2,game.wzdark,game.zzword,OBJSND[BIRD]);
836     SAVWDS(OBJTXT[SIGN],game.clshnt,game.novice,K,K,K,K);
837     SAVARR(game.abbrev,LOCSIZ);
838     SAVARR(game.atloc,LOCSIZ);
839     SAVARR(game.dloc,NDWARVES);
840     SAVARR(game.dseen,NDWARVES);
841     SAVARR(game.fixed,NOBJECTS);
842     SAVARR(game.hinted,HNTSIZ);
843     SAVARR(game.hintlc,HNTSIZ);
844     SAVARR(game.link,NOBJECTS*2);
845     SAVARR(game.odloc,NDWARVES);
846     SAVARR(game.place,NOBJECTS);
847     SAVARR(game.prop,NOBJECTS);
848     SAVWRD(kk,K);
849     if (K != 0) {
850         RSPEAK(270);
851         exit(0);
852     }
853     K=NUL;
854     game.zzword=RNDVOC(3,game.zzword);
855     if (kk > 0) return(8);
856     RSPEAK(266);
857     exit(0);
858 }
859
860 static int throw_support(long spk)
861 {
862     RSPEAK(spk);
863     DROP(AXE,game.loc);
864     K=NUL;
865     return(8);
866 }
867
868 static int throw(FILE *cmdin, long verb, long obj)
869 /*  Throw.  Same as discard unless axe.  Then same as attack except
870  *  ignore bird, and if dwarf is present then one might be killed.
871  *  (Only way to do so!)  Axe also special for dragon, bear, and
872  *  troll.  Treasures special for troll. */
873 {
874     if (TOTING(ROD2) && obj == ROD && !TOTING(ROD))obj=ROD2;
875     if (!TOTING(obj))
876         return(2011);
877     if (obj >= 50 && obj <= MAXTRS && AT(TROLL)) {
878         SPK=159;
879         /*  Snarf a treasure for the troll. */
880         DROP(obj,0);
881         MOVE(TROLL,0);
882         MOVE(TROLL+NOBJECTS,0);
883         DROP(TROLL2,PLAC[TROLL]);
884         DROP(TROLL2+NOBJECTS,FIXD[TROLL]);
885         JUGGLE(CHASM);
886         return(2011);
887     }
888     if (obj == FOOD && HERE(BEAR)) {
889     /* But throwing food is another story. */
890         obj=BEAR;
891         return(feed(obj));
892     }
893     if (obj != AXE)
894         return(discard(obj, false));
895     I=ATDWRF(game.loc);
896     if (I <= 0) {
897         if (AT(DRAGON) && game.prop[DRAGON] == 0) {
898             SPK=152;
899             return throw_support(SPK);
900         }
901         if (AT(TROLL)) {
902             SPK=158;
903             return throw_support(SPK);
904         }
905         if (AT(OGRE)) {
906             SPK=203;
907             return throw_support(SPK);
908         }
909         if (HERE(BEAR) && game.prop[BEAR] == 0) {
910             /* This'll teach him to throw the axe at the bear! */
911             SPK=164;
912             DROP(AXE,game.loc);
913             game.fixed[AXE]= -1;
914             game.prop[AXE]=1;
915             JUGGLE(BEAR);
916             return(2011);
917         }
918         return(attack(cmdin, verb, 0));
919     }
920
921     if (randrange(NDWARVES+1) < game.dflag) {
922         SPK=48;
923         return throw_support(SPK);
924     }
925     game.dseen[I]=false;
926     game.dloc[I]=0;
927     SPK=47;
928     game.dkill=game.dkill+1;
929     if (game.dkill == 1)SPK=149;
930
931     return throw_support(SPK);
932 }
933
934 static int vscore(void)
935 /* Score.  Call scoring routine but tell it to return. */
936 {
937     score(-1);
938     return(2012);
939 }
940
941 static int wake(token_t obj)
942 /* Wake.  Only use is to disturb the dwarves. */
943 {
944     if (obj != DWARF || !game.closed) return(2011);
945     SPK=199;
946     return(18999);
947 }
948
949 static int wave(token_t obj)
950 /* Wave.  No effect unless waving rod at fissure or at bird. */
951 {
952     if ((!TOTING(obj)) && (obj != ROD || !TOTING(ROD2)))SPK=29;
953     if (obj != ROD ||
954        !TOTING(obj) ||
955        (!HERE(BIRD) && (game.closng || !AT(FISSUR))))
956         return(2011);
957     if (HERE(BIRD))SPK=206+MOD(game.prop[BIRD],2);
958     if (SPK == 206 && game.loc == game.place[STEPS] && game.prop[JADE] < 0) {
959         DROP(JADE,game.loc);
960         game.prop[JADE]=0;
961         game.tally=game.tally-1;
962         SPK=208;
963         return(2011);
964     } else {
965         if (game.closed) return(18999);
966         if (game.closng || !AT(FISSUR)) return(2011);
967         if (HERE(BIRD))RSPEAK(SPK);
968         game.prop[FISSUR]=1-game.prop[FISSUR];
969         PSPEAK(FISSUR,2-game.prop[FISSUR]);
970         return(2012);
971     }
972 }
973
974 /* We're called with a number that says what label the caller wanted
975  * to "goto", and we return a similar label number for the caller to
976  * "goto".
977  */
978
979 int action(FILE *input, long STARTAT, long verb, long obj)
980 /*  Analyse a verb.  Remember what it was, go back for object if second word
981  *  unless verb is "say", which snarfs arbitrary second word.
982  */
983 {
984     int kk;
985     switch(STARTAT) {
986     case 4000: goto L4000;
987     case 4090: goto L4090;
988     case 5000: goto L5000;
989     }
990     BUG(99);
991
992 L4000:  
993     SPK=ACTSPK[verb];
994     if (WD2 > 0 && verb != SAY) return(2800);
995     if (verb == SAY)obj=WD2;
996     if (obj > 0) goto L4090;
997
998 /*  Analyse an intransitive verb (ie, no object given yet). */
999
1000     switch (verb-1) {
1001     case  0: /* CARRY */ return carry(INTRANSITIVE);
1002     case  1: /* DROP  */ return(8000); 
1003     case  2: /* SAY   */ return(8000); 
1004     case  3: /* UNLOC */ return lock(verb, INTRANSITIVE);    
1005     case  4: /* NOTHI */ return(2009); 
1006     case  5: /* LOCK  */ return lock(verb, INTRANSITIVE);    
1007     case  6: /* LIGHT */ return light(INTRANSITIVE);    
1008     case  7: /* EXTIN */ return extinguish(INTRANSITIVE);    
1009     case  8: /* WAVE  */ return(8000); 
1010     case  9: /* CALM  */ return(8000); 
1011     case 10: /* WALK  */ return(2011); 
1012     case 11: /* ATTAC */ return attack(input, verb, obj);   
1013     case 12: /* POUR  */ return pour(obj);   
1014     case 13: /* EAT   */ return eat(INTRANSITIVE);   
1015     case 14: /* DRINK */ return drink(obj);   
1016     case 15: /* RUB   */ return(8000); 
1017     case 16: /* TOSS  */ return(8000); 
1018     case 17: /* QUIT  */ return quit(input);   
1019     case 18: /* FIND  */ return(8000); 
1020     case 19: /* INVEN */ return inven(obj);   
1021     case 20: /* FEED  */ return(8000); 
1022     case 21: /* FILL  */ return fill(obj);   
1023     case 22: /* BLAST */ return blast();   
1024     case 23: /* SCOR  */ return vscore();   
1025     case 24: /* FOO   */ return bigwords(WD1);   
1026     case 25: /* BRIEF */ return brief();   
1027     case 26: /* READ  */ return read(input, INTRANSITIVE);   
1028     case 27: /* BREAK */ return(8000); 
1029     case 28: /* WAKE  */ return(8000); 
1030     case 29: /* SUSP  */ return suspendresume(input, false);   
1031     case 30: /* RESU  */ return suspendresume(input, true);   
1032     case 31: /* FLY   */ return fly(INTRANSITIVE);   
1033     case 32: /* LISTE */ return listen();   
1034     case 33: /* ZZZZ  */ return reservoir();   
1035     }
1036     BUG(23);
1037
1038 /*  Analyse a transitive verb. */
1039
1040 L4090:  switch (verb-1) {
1041     case  0: /* CARRY */ return carry(obj);    
1042     case  1: /* DROP  */ return discard(obj, false);    
1043     case  2: /* SAY   */ return say();    
1044     case  3: /* UNLOC */ return lock(verb, obj);    
1045     case  4: /* NOTHI */ return(2009); 
1046     case  5: /* LOCK  */ return lock(verb, obj);    
1047     case  6: /* LIGHT */ return light(obj);    
1048     case  7: /* EXTI  */ return extinguish(obj);    
1049     case  8: /* WAVE  */ return wave(obj);    
1050     case  9: /* CALM  */ return(2011); 
1051     case 10: /* WALK  */ return(2011); 
1052     case 11: /* ATTAC */ return attack(input, verb, obj);   
1053     case 12: /* POUR  */ return pour(obj);   
1054     case 13: /* EAT   */ return eat(obj);   
1055     case 14: /* DRINK */ return drink(obj);   
1056     case 15: /* RUB   */ return rub(obj);   
1057     case 16: /* TOSS  */ return throw(input, verb, obj);   
1058     case 17: /* QUIT  */ return(2011); 
1059     case 18: /* FIND  */ return find(obj);   
1060     case 19: /* INVEN */ return find(obj);   
1061     case 20: /* FEED  */ return feed(obj);   
1062     case 21: /* FILL  */ return fill(obj);   
1063     case 22: /* BLAST */ return blast();   
1064     case 23: /* SCOR  */ return(2011); 
1065     case 24: /* FOO   */ return(2011); 
1066     case 25: /* BRIEF */ return(2011); 
1067     case 26: /* READ  */ return read(input, obj);   
1068     case 27: /* BREAK */ return vbreak(obj);   
1069     case 28: /* WAKE  */ return wake(obj);   
1070     case 29: /* SUSP  */ return(2011); 
1071     case 30: /* RESU  */ return(2011); 
1072     case 31: /* FLY   */ return fly(obj);   
1073     case 32: /* LISTE */ return(2011); 
1074     case 33: /* ZZZZ  */ return reservoir();   
1075     }
1076     BUG(24);
1077
1078 /*  Analyse an object word.  See if the thing is here, whether we've got a verb
1079  *  yet, and so on.  Object must be here unless verb is "find" or "invent(ory)"
1080  *  (and no new verb yet to be analysed).  Water and oil are also funny, since
1081  *  they are never actually dropped at any location, but might be here inside
1082  *  the bottle or urn or as a feature of the location. */
1083
1084 L5000:
1085     if (!HERE(obj))
1086         goto L5100;
1087 L5010:
1088     if (WD2 > 0)
1089         return(2800);
1090     if (verb != 0)
1091         goto L4090;
1092     SETPRM(1,WD1,WD1X);
1093     RSPEAK(255);
1094     return(2600);
1095
1096 L5100:
1097     if (obj == GRATE) {
1098         if (game.loc == 1 || game.loc == 4 || game.loc == 7)
1099             obj=DPRSSN;
1100         if (game.loc > 9 && game.loc < 15)
1101             obj=ENTRNC;
1102         if (obj != GRATE)
1103             return(8);
1104     }
1105
1106     if (obj == DWARF && ATDWRF(game.loc) > 0)
1107         goto L5010;
1108     if ((LIQ(0) == obj && HERE(BOTTLE)) || obj == LIQLOC(game.loc))
1109         goto L5010;
1110     if (obj != OIL || !HERE(URN) || game.prop[URN] == 0)
1111         goto L5120;
1112     obj=URN;
1113     goto L5010;
1114 L5120:
1115     if (obj != PLANT || !AT(PLANT2) || game.prop[PLANT2] == 0)
1116         goto L5130;
1117     obj=PLANT2;
1118     goto L5010;
1119 L5130:
1120     if (obj != KNIFE || game.knfloc != game.loc)
1121         goto L5140;
1122     game.knfloc= -1;
1123     SPK=116;
1124     return(2011);
1125 L5140:
1126     if (obj != ROD || !HERE(ROD2))
1127         goto L5190;
1128     obj=ROD2;
1129     goto L5010;
1130 L5190:
1131     if ((verb == FIND || verb == INVENT) && WD2 <= 0)
1132         goto L5010;
1133     SETPRM(1,WD1,WD1X);
1134     RSPEAK(256);
1135     return(2012);
1136 }