1TBS reflow with clang-format.
[open-adventure.git] / actions.c
1 /*
2  * Actions for the dungeon-running code.
3  *
4  * SPDX-FileCopyrightText: (C) 1977, 2005 Will Crowther and Don Woods
5  * SPDX-License-Identifier: BSD-2-Clause
6  */
7
8 #include <inttypes.h>
9 #include <stdbool.h>
10 #include <stdlib.h>
11 #include <string.h>
12
13 #include "advent.h"
14
15 static phase_codes_t fill(verb_t, obj_t);
16
17 static phase_codes_t attack(command_t command) {
18         /*  Attack.  Assume target if unambiguous.  "Throw" also links here.
19          *  Attackable objects fall into two categories: enemies (snake,
20          *  dwarf, etc.)  and others (bird, clam, machine).  Ambiguous if 2
21          *  enemies, or no enemies but 2 others. */
22         verb_t verb = command.verb;
23         obj_t obj = command.obj;
24
25         if (obj == INTRANSITIVE) {
26                 int changes = 0;
27                 if (atdwrf(game.loc) > 0) {
28                         obj = DWARF;
29                         ++changes;
30                 }
31                 if (HERE(SNAKE)) {
32                         obj = SNAKE;
33                         ++changes;
34                 }
35                 if (AT(DRAGON) && game.objects[DRAGON].prop == DRAGON_BARS) {
36                         obj = DRAGON;
37                         ++changes;
38                 }
39                 if (AT(TROLL)) {
40                         obj = TROLL;
41                         ++changes;
42                 }
43                 if (AT(OGRE)) {
44                         obj = OGRE;
45                         ++changes;
46                 }
47                 if (HERE(BEAR) && game.objects[BEAR].prop == UNTAMED_BEAR) {
48                         obj = BEAR;
49                         ++changes;
50                 }
51                 /* check for low-priority targets */
52                 if (obj == INTRANSITIVE) {
53                         /* Can't attack bird or machine by throwing axe. */
54                         if (HERE(BIRD) && verb != THROW) {
55                                 obj = BIRD;
56                                 ++changes;
57                         }
58                         if (HERE(VEND) && verb != THROW) {
59                                 obj = VEND;
60                                 ++changes;
61                         }
62                         /* Clam and oyster both treated as clam for intransitive
63                          * case; no harm done. */
64                         if (HERE(CLAM) || HERE(OYSTER)) {
65                                 obj = CLAM;
66                                 ++changes;
67                         }
68                 }
69                 if (changes >= 2)
70                         return GO_UNKNOWN;
71         }
72
73         if (obj == BIRD) {
74                 if (game.closed) {
75                         rspeak(UNHAPPY_BIRD);
76                 } else {
77                         DESTROY(BIRD);
78                         rspeak(BIRD_DEAD);
79                 }
80                 return GO_CLEAROBJ;
81         }
82         if (obj == VEND) {
83                 state_change(VEND, game.objects[VEND].prop == VEND_BLOCKS
84                                        ? VEND_UNBLOCKS
85                                        : VEND_BLOCKS);
86
87                 return GO_CLEAROBJ;
88         }
89
90         if (obj == BEAR) {
91                 switch (game.objects[BEAR].prop) {
92                 case UNTAMED_BEAR:
93                         rspeak(BEAR_HANDS);
94                         break;
95                 case SITTING_BEAR:
96                         rspeak(BEAR_CONFUSED);
97                         break;
98                 case CONTENTED_BEAR:
99                         rspeak(BEAR_CONFUSED);
100                         break;
101                 case BEAR_DEAD:
102                         rspeak(ALREADY_DEAD);
103                         break;
104                 }
105                 return GO_CLEAROBJ;
106         }
107         if (obj == DRAGON && game.objects[DRAGON].prop == DRAGON_BARS) {
108                 /*  Fun stuff for dragon.  If he insists on attacking it, win!
109                  *  Set game.prop to dead, move dragon to central loc (still
110                  *  fixed), move rug there (not fixed), and move him there,
111                  *  too.  Then do a null motion to get new description. */
112                 rspeak(BARE_HANDS_QUERY);
113                 if (!silent_yes_or_no()) {
114                         speak(arbitrary_messages[NASTY_DRAGON]);
115                         return GO_MOVE;
116                 }
117                 state_change(DRAGON, DRAGON_DEAD);
118                 game.objects[RUG].prop = RUG_FLOOR;
119                 /* Hardcoding LOC_SECRET5 as the dragon's death location is
120                  * ugly. The way it was computed before was worse; it depended
121                  * on the two dragon locations being LOC_SECRET4 and LOC_SECRET6
122                  * and LOC_SECRET5 being right between them.
123                  */
124                 move(DRAGON + NOBJECTS, IS_FIXED);
125                 move(RUG + NOBJECTS, IS_FREE);
126                 move(DRAGON, LOC_SECRET5);
127                 move(RUG, LOC_SECRET5);
128                 drop(BLOOD, LOC_SECRET5);
129                 for (obj_t i = 1; i <= NOBJECTS; i++) {
130                         if (game.objects[i].place == objects[DRAGON].plac ||
131                             game.objects[i].place == objects[DRAGON].fixd)
132                                 move(i, LOC_SECRET5);
133                 }
134                 game.loc = LOC_SECRET5;
135                 return GO_MOVE;
136         }
137
138         if (obj == OGRE) {
139                 rspeak(OGRE_DODGE);
140                 if (atdwrf(game.loc) == 0) {
141                         return GO_CLEAROBJ;
142                 }
143                 rspeak(KNIFE_THROWN);
144                 DESTROY(OGRE);
145                 int dwarves = 0;
146                 for (int i = 1; i < PIRATE; i++) {
147                         if (game.dwarves[i].loc == game.loc) {
148                                 ++dwarves;
149                                 game.dwarves[i].loc = LOC_LONGWEST;
150                                 game.dwarves[i].seen = false;
151                         }
152                 }
153                 rspeak((dwarves > 1) ? OGRE_PANIC1 : OGRE_PANIC2);
154                 return GO_CLEAROBJ;
155         }
156
157         switch (obj) {
158         case INTRANSITIVE:
159                 rspeak(NO_TARGET);
160                 break;
161         case CLAM:
162         case OYSTER:
163                 rspeak(SHELL_IMPERVIOUS);
164                 break;
165         case SNAKE:
166                 rspeak(SNAKE_WARNING);
167                 break;
168         case DWARF:
169                 if (game.closed) {
170                         return GO_DWARFWAKE;
171                 }
172                 rspeak(BARE_HANDS_QUERY);
173                 break;
174         case DRAGON:
175                 rspeak(ALREADY_DEAD);
176                 break;
177         case TROLL:
178                 rspeak(ROCKY_TROLL);
179                 break;
180         default:
181                 speak(actions[verb].message);
182         }
183         return GO_CLEAROBJ;
184 }
185
186 static phase_codes_t bigwords(vocab_t id) {
187         /* Only called on FEE FIE FOE FOO (AND FUM).  Advance to next state if
188          * given in proper order. Look up foo in special section of vocab to
189          * determine which word we've got. Last word zips the eggs back to the
190          * giant room (unless already there). */
191         int foobar = abs(game.foobar);
192
193         /* Only FEE can start a magic-word sequence. */
194         if ((foobar == WORD_EMPTY) &&
195             (id == FIE || id == FOE || id == FOO || id == FUM)) {
196                 rspeak(NOTHING_HAPPENS);
197                 return GO_CLEAROBJ;
198         }
199
200         if ((foobar == WORD_EMPTY && id == FEE) ||
201             (foobar == FEE && id == FIE) || (foobar == FIE && id == FOE) ||
202             (foobar == FOE && id == FOO)) {
203                 game.foobar = id;
204                 if (id != FOO) {
205                         rspeak(OK_MAN);
206                         return GO_CLEAROBJ;
207                 }
208                 game.foobar = WORD_EMPTY;
209                 if (game.objects[EGGS].place == objects[EGGS].plac ||
210                     (TOTING(EGGS) && game.loc == objects[EGGS].plac)) {
211                         rspeak(NOTHING_HAPPENS);
212                         return GO_CLEAROBJ;
213                 } else {
214                         /*  Bring back troll if we steal the eggs back from him
215                          * before crossing. */
216                         if (game.objects[EGGS].place == LOC_NOWHERE &&
217                             game.objects[TROLL].place == LOC_NOWHERE &&
218                             game.objects[TROLL].prop == TROLL_UNPAID)
219                                 game.objects[TROLL].prop = TROLL_PAIDONCE;
220                         if (HERE(EGGS)) {
221                                 pspeak(EGGS, look, true, EGGS_VANISHED);
222                         } else if (game.loc == objects[EGGS].plac) {
223                                 pspeak(EGGS, look, true, EGGS_HERE);
224                         } else {
225                                 pspeak(EGGS, look, true, EGGS_DONE);
226                         }
227                         move(EGGS, objects[EGGS].plac);
228
229                         return GO_CLEAROBJ;
230                 }
231         } else {
232                 /* Magic-word sequence was started but is incorrect */
233                 if (settings.oldstyle || game.seenbigwords) {
234                         rspeak(START_OVER);
235                 } else {
236                         rspeak(WELL_POINTLESS);
237                 }
238                 game.foobar = WORD_EMPTY;
239                 return GO_CLEAROBJ;
240         }
241 }
242
243 static void blast(void) {
244         /*  Blast.  No effect unless you've got dynamite, which is a neat trick!
245          */
246         if (PROP_IS_NOTFOUND(ROD2) || !game.closed)
247                 rspeak(REQUIRES_DYNAMITE);
248         else {
249                 if (HERE(ROD2)) {
250                         game.bonus = splatter;
251                         rspeak(SPLATTER_MESSAGE);
252                 } else if (game.loc == LOC_NE) {
253                         game.bonus = defeat;
254                         rspeak(DEFEAT_MESSAGE);
255                 } else {
256                         game.bonus = victory;
257                         rspeak(VICTORY_MESSAGE);
258                 }
259                 terminate(endgame);
260         }
261 }
262
263 static phase_codes_t vbreak(verb_t verb, obj_t obj) {
264         /*  Break.  Only works for mirror in repository and, of course, the
265          * vase. */
266         switch (obj) {
267         case MIRROR:
268                 if (game.closed) {
269                         state_change(MIRROR, MIRROR_BROKEN);
270                         return GO_DWARFWAKE;
271                 } else {
272                         rspeak(TOO_FAR);
273                         break;
274                 }
275         case VASE:
276                 if (game.objects[VASE].prop == VASE_WHOLE) {
277                         if (TOTING(VASE))
278                                 drop(VASE, game.loc);
279                         state_change(VASE, VASE_BROKEN);
280                         game.objects[VASE].fixed = IS_FIXED;
281                         break;
282                 }
283         /* FALLTHRU */
284         default:
285                 speak(actions[verb].message);
286         }
287         return (GO_CLEAROBJ);
288 }
289
290 static phase_codes_t brief(void) {
291         /*  Brief.  Intransitive only.  Suppress full descriptions after first
292          * time. */
293         game.abbnum = 10000;
294         game.detail = 3;
295         rspeak(BRIEF_CONFIRM);
296         return GO_CLEAROBJ;
297 }
298
299 static phase_codes_t vcarry(verb_t verb, obj_t obj) {
300         /*  Carry an object.  Special cases for bird and cage (if bird in cage,
301          * can't take one without the other).  Liquids also special, since they
302          * depend on status of bottle.  Also various side effects, etc. */
303         if (obj == INTRANSITIVE) {
304                 /*  Carry, no object given yet.  OK if only one object present.
305                  */
306                 if (game.locs[game.loc].atloc == NO_OBJECT ||
307                     game.link[game.locs[game.loc].atloc] != 0 ||
308                     atdwrf(game.loc) > 0) {
309                         return GO_UNKNOWN;
310                 }
311                 obj = game.locs[game.loc].atloc;
312         }
313
314         if (TOTING(obj)) {
315                 speak(actions[verb].message);
316                 return GO_CLEAROBJ;
317         }
318
319         if (obj == MESSAG) {
320                 rspeak(REMOVE_MESSAGE);
321                 DESTROY(MESSAG);
322                 return GO_CLEAROBJ;
323         }
324
325         if (game.objects[obj].fixed != IS_FREE) {
326                 switch (obj) {
327                 case PLANT:
328                         /* Next guard tests whether plant is tiny or stashed */
329                         rspeak(game.objects[PLANT].prop <= PLANT_THIRSTY
330                                    ? DEEP_ROOTS
331                                    : YOU_JOKING);
332                         break;
333                 case BEAR:
334                         rspeak(game.objects[BEAR].prop == SITTING_BEAR
335                                    ? BEAR_CHAINED
336                                    : YOU_JOKING);
337                         break;
338                 case CHAIN:
339                         rspeak(game.objects[BEAR].prop != UNTAMED_BEAR
340                                    ? STILL_LOCKED
341                                    : YOU_JOKING);
342                         break;
343                 case RUG:
344                         rspeak(game.objects[RUG].prop == RUG_HOVER
345                                    ? RUG_HOVERS
346                                    : YOU_JOKING);
347                         break;
348                 case URN:
349                         rspeak(URN_NOBUDGE);
350                         break;
351                 case CAVITY:
352                         rspeak(DOUGHNUT_HOLES);
353                         break;
354                 case BLOOD:
355                         rspeak(FEW_DROPS);
356                         break;
357                 case SIGN:
358                         rspeak(HAND_PASSTHROUGH);
359                         break;
360                 default:
361                         rspeak(YOU_JOKING);
362                 }
363                 return GO_CLEAROBJ;
364         }
365
366         if (obj == WATER || obj == OIL) {
367                 if (!HERE(BOTTLE) || LIQUID() != obj) {
368                         if (!TOTING(BOTTLE)) {
369                                 rspeak(NO_CONTAINER);
370                                 return GO_CLEAROBJ;
371                         }
372                         if (game.objects[BOTTLE].prop == EMPTY_BOTTLE) {
373                                 return (fill(verb, BOTTLE));
374                         } else {
375                                 rspeak(BOTTLE_FULL);
376                         }
377                         return GO_CLEAROBJ;
378                 }
379                 obj = BOTTLE;
380         }
381
382         if (game.holdng >= INVLIMIT) {
383                 rspeak(CARRY_LIMIT);
384                 return GO_CLEAROBJ;
385         }
386
387         if (obj == BIRD && game.objects[BIRD].prop != BIRD_CAGED &&
388             !PROP_IS_STASHED(BIRD)) {
389                 if (game.objects[BIRD].prop == BIRD_FOREST_UNCAGED) {
390                         DESTROY(BIRD);
391                         rspeak(BIRD_CRAP);
392                         return GO_CLEAROBJ;
393                 }
394                 if (!TOTING(CAGE)) {
395                         rspeak(CANNOT_CARRY);
396                         return GO_CLEAROBJ;
397                 }
398                 if (TOTING(ROD)) {
399                         rspeak(BIRD_EVADES);
400                         return GO_CLEAROBJ;
401                 }
402                 game.objects[BIRD].prop = BIRD_CAGED;
403         }
404         if ((obj == BIRD || obj == CAGE) &&
405             (game.objects[BIRD].prop == BIRD_CAGED ||
406              PROP_STASHED(BIRD) == BIRD_CAGED)) {
407                 /* expression maps BIRD to CAGE and CAGE to BIRD */
408                 carry(BIRD + CAGE - obj, game.loc);
409         }
410
411         carry(obj, game.loc);
412
413         if (obj == BOTTLE && LIQUID() != NO_OBJECT) {
414                 game.objects[LIQUID()].place = CARRIED;
415         }
416
417         if (GSTONE(obj) && !PROP_IS_FOUND(obj)) {
418                 PROP_SET_FOUND(obj);
419                 game.objects[CAVITY].prop = CAVITY_EMPTY;
420         }
421         rspeak(OK_MAN);
422         return GO_CLEAROBJ;
423 }
424
425 static int chain(verb_t verb) {
426         /* Do something to the bear's chain */
427         if (verb != LOCK) {
428                 if (game.objects[BEAR].prop == UNTAMED_BEAR) {
429                         rspeak(BEAR_BLOCKS);
430                         return GO_CLEAROBJ;
431                 }
432                 if (game.objects[CHAIN].prop == CHAIN_HEAP) {
433                         rspeak(ALREADY_UNLOCKED);
434                         return GO_CLEAROBJ;
435                 }
436                 game.objects[CHAIN].prop = CHAIN_HEAP;
437                 game.objects[CHAIN].fixed = IS_FREE;
438                 if (game.objects[BEAR].prop != BEAR_DEAD)
439                         game.objects[BEAR].prop = CONTENTED_BEAR;
440
441                 switch (game.objects[BEAR].prop) {
442                 // LCOV_EXCL_START
443                 case BEAR_DEAD:
444                         /* Can't be reached until the bear can die in some way
445                          * other than a bridge collapse. Leave in in case this
446                          * changes, but exclude from coverage testing. */
447                         game.objects[BEAR].fixed = IS_FIXED;
448                         break;
449                 // LCOV_EXCL_STOP
450                 default:
451                         game.objects[BEAR].fixed = IS_FREE;
452                 }
453                 rspeak(CHAIN_UNLOCKED);
454                 return GO_CLEAROBJ;
455         }
456
457         if (game.objects[CHAIN].prop != CHAIN_HEAP) {
458                 rspeak(ALREADY_LOCKED);
459                 return GO_CLEAROBJ;
460         }
461         if (game.loc != objects[CHAIN].plac) {
462                 rspeak(NO_LOCKSITE);
463                 return GO_CLEAROBJ;
464         }
465
466         game.objects[CHAIN].prop = CHAIN_FIXED;
467
468         if (TOTING(CHAIN))
469                 drop(CHAIN, game.loc);
470         game.objects[CHAIN].fixed = IS_FIXED;
471
472         rspeak(CHAIN_LOCKED);
473         return GO_CLEAROBJ;
474 }
475
476 static phase_codes_t discard(verb_t verb, obj_t obj) {
477         /*  Discard object.  "Throw" also comes here for most objects.  Special
478          * cases for bird (might attack snake or dragon) and cage (might contain
479          * bird) and vase. Drop coins at vending machine for extra batteries. */
480         if (obj == ROD && !TOTING(ROD) && TOTING(ROD2)) {
481                 obj = ROD2;
482         }
483
484         if (!TOTING(obj)) {
485                 speak(actions[verb].message);
486                 return GO_CLEAROBJ;
487         }
488
489         if (GSTONE(obj) && AT(CAVITY) &&
490             game.objects[CAVITY].prop != CAVITY_FULL) {
491                 rspeak(GEM_FITS);
492                 game.objects[obj].prop = STATE_IN_CAVITY;
493                 game.objects[CAVITY].prop = CAVITY_FULL;
494                 if (HERE(RUG) &&
495                     ((obj == EMERALD && game.objects[RUG].prop != RUG_HOVER) ||
496                      (obj == RUBY && game.objects[RUG].prop == RUG_HOVER))) {
497                         if (obj == RUBY) {
498                                 rspeak(RUG_SETTLES);
499                         } else if (TOTING(RUG)) {
500                                 rspeak(RUG_WIGGLES);
501                         } else {
502                                 rspeak(RUG_RISES);
503                         }
504                         if (!TOTING(RUG) || obj == RUBY) {
505                                 int k = (game.objects[RUG].prop == RUG_HOVER)
506                                             ? RUG_FLOOR
507                                             : RUG_HOVER;
508                                 game.objects[RUG].prop = k;
509                                 if (k == RUG_HOVER) {
510                                         k = objects[SAPPH].plac;
511                                 }
512                                 move(RUG + NOBJECTS, k);
513                         }
514                 }
515                 drop(obj, game.loc);
516                 return GO_CLEAROBJ;
517         }
518
519         if (obj == COINS && HERE(VEND)) {
520                 DESTROY(COINS);
521                 drop(BATTERY, game.loc);
522                 pspeak(BATTERY, look, true, FRESH_BATTERIES);
523                 return GO_CLEAROBJ;
524         }
525
526         if (LIQUID() == obj) {
527                 obj = BOTTLE;
528         }
529         if (obj == BOTTLE && LIQUID() != NO_OBJECT) {
530                 game.objects[LIQUID()].place = LOC_NOWHERE;
531         }
532
533         if (obj == BEAR && AT(TROLL)) {
534                 state_change(TROLL, TROLL_GONE);
535                 move(TROLL, LOC_NOWHERE);
536                 move(TROLL + NOBJECTS, IS_FREE);
537                 move(TROLL2, objects[TROLL].plac);
538                 move(TROLL2 + NOBJECTS, objects[TROLL].fixd);
539                 juggle(CHASM);
540                 drop(obj, game.loc);
541                 return GO_CLEAROBJ;
542         }
543
544         if (obj == VASE) {
545                 if (game.loc != objects[PILLOW].plac) {
546                         state_change(VASE,
547                                      AT(PILLOW) ? VASE_WHOLE : VASE_DROPPED);
548                         if (game.objects[VASE].prop != VASE_WHOLE) {
549                                 game.objects[VASE].fixed = IS_FIXED;
550                         }
551                         drop(obj, game.loc);
552                         return GO_CLEAROBJ;
553                 }
554         }
555
556         if (obj == CAGE && game.objects[BIRD].prop == BIRD_CAGED) {
557                 drop(BIRD, game.loc);
558         }
559
560         if (obj == BIRD) {
561                 if (AT(DRAGON) && game.objects[DRAGON].prop == DRAGON_BARS) {
562                         rspeak(BIRD_BURNT);
563                         DESTROY(BIRD);
564                         return GO_CLEAROBJ;
565                 }
566                 if (HERE(SNAKE)) {
567                         rspeak(BIRD_ATTACKS);
568                         if (game.closed) {
569                                 return GO_DWARFWAKE;
570                         }
571                         DESTROY(SNAKE);
572                         /* Set game.prop for use by travel options */
573                         game.objects[SNAKE].prop = SNAKE_CHASED;
574                 } else {
575                         rspeak(OK_MAN);
576                 }
577
578                 game.objects[BIRD].prop =
579                     FOREST(game.loc) ? BIRD_FOREST_UNCAGED : BIRD_UNCAGED;
580                 drop(obj, game.loc);
581                 return GO_CLEAROBJ;
582         }
583
584         rspeak(OK_MAN);
585         drop(obj, game.loc);
586         return GO_CLEAROBJ;
587 }
588
589 static phase_codes_t drink(verb_t verb, obj_t obj) {
590         /*  Drink.  If no object, assume water and look for it here.  If water
591          * is in the bottle, drink that, else must be at a water loc, so drink
592          * stream. */
593         if (obj == INTRANSITIVE && LIQLOC(game.loc) != WATER &&
594             (LIQUID() != WATER || !HERE(BOTTLE))) {
595                 return GO_UNKNOWN;
596         }
597
598         if (obj == BLOOD) {
599                 DESTROY(BLOOD);
600                 state_change(DRAGON, DRAGON_BLOODLESS);
601                 game.blooded = true;
602                 return GO_CLEAROBJ;
603         }
604
605         if (obj != INTRANSITIVE && obj != WATER) {
606                 rspeak(RIDICULOUS_ATTEMPT);
607                 return GO_CLEAROBJ;
608         }
609         if (LIQUID() == WATER && HERE(BOTTLE)) {
610                 game.objects[WATER].place = LOC_NOWHERE;
611                 state_change(BOTTLE, EMPTY_BOTTLE);
612                 return GO_CLEAROBJ;
613         }
614
615         speak(actions[verb].message);
616         return GO_CLEAROBJ;
617 }
618
619 static phase_codes_t eat(verb_t verb, obj_t obj) {
620         /*  Eat.  Intransitive: assume food if present, else ask what.
621          * Transitive: food ok, some things lose appetite, rest are ridiculous.
622          */
623         switch (obj) {
624         case INTRANSITIVE:
625                 if (!HERE(FOOD))
626                         return GO_UNKNOWN;
627         /* FALLTHRU */
628         case FOOD:
629                 DESTROY(FOOD);
630                 rspeak(THANKS_DELICIOUS);
631                 break;
632         case BIRD:
633         case SNAKE:
634         case CLAM:
635         case OYSTER:
636         case DWARF:
637         case DRAGON:
638         case TROLL:
639         case BEAR:
640         case OGRE:
641                 rspeak(LOST_APPETITE);
642                 break;
643         default:
644                 speak(actions[verb].message);
645         }
646         return GO_CLEAROBJ;
647 }
648
649 static phase_codes_t extinguish(verb_t verb, obj_t obj) {
650         /* Extinguish.  Lamp, urn, dragon/volcano (nice try). */
651         if (obj == INTRANSITIVE) {
652                 if (HERE(LAMP) && game.objects[LAMP].prop == LAMP_BRIGHT) {
653                         obj = LAMP;
654                 }
655                 if (HERE(URN) && game.objects[URN].prop == URN_LIT) {
656                         obj = URN;
657                 }
658                 if (obj == INTRANSITIVE) {
659                         return GO_UNKNOWN;
660                 }
661         }
662
663         switch (obj) {
664         case URN:
665                 if (game.objects[URN].prop != URN_EMPTY) {
666                         state_change(URN, URN_DARK);
667                 } else {
668                         pspeak(URN, change, true, URN_DARK);
669                 }
670                 break;
671         case LAMP:
672                 state_change(LAMP, LAMP_DARK);
673                 rspeak(DARK(game.loc) ? PITCH_DARK : NO_MESSAGE);
674                 break;
675         case DRAGON:
676         case VOLCANO:
677                 rspeak(BEYOND_POWER);
678                 break;
679         default:
680                 speak(actions[verb].message);
681         }
682         return GO_CLEAROBJ;
683 }
684
685 static phase_codes_t feed(verb_t verb, obj_t obj) {
686         /*  Feed.  If bird, no seed.  Snake, dragon, troll: quip.  If dwarf,
687          * make him mad.  Bear, special. */
688         switch (obj) {
689         case BIRD:
690                 rspeak(BIRD_PINING);
691                 break;
692         case DRAGON:
693                 if (game.objects[DRAGON].prop != DRAGON_BARS) {
694                         rspeak(RIDICULOUS_ATTEMPT);
695                 } else {
696                         rspeak(NOTHING_EDIBLE);
697                 }
698                 break;
699         case SNAKE:
700                 if (!game.closed && HERE(BIRD)) {
701                         DESTROY(BIRD);
702                         rspeak(BIRD_DEVOURED);
703                 } else {
704                         rspeak(NOTHING_EDIBLE);
705                 }
706                 break;
707         case TROLL:
708                 rspeak(TROLL_VICES);
709                 break;
710         case DWARF:
711                 if (HERE(FOOD)) {
712                         game.dflag += 2;
713                         rspeak(REALLY_MAD);
714                 } else {
715                         speak(actions[verb].message);
716                 }
717                 break;
718         case BEAR:
719                 if (game.objects[BEAR].prop == BEAR_DEAD) {
720                         rspeak(RIDICULOUS_ATTEMPT);
721                         break;
722                 }
723                 if (game.objects[BEAR].prop == UNTAMED_BEAR) {
724                         if (HERE(FOOD)) {
725                                 DESTROY(FOOD);
726                                 game.objects[AXE].fixed = IS_FREE;
727                                 game.objects[AXE].prop = AXE_HERE;
728                                 state_change(BEAR, SITTING_BEAR);
729                         } else {
730                                 rspeak(NOTHING_EDIBLE);
731                         }
732                         break;
733                 }
734                 speak(actions[verb].message);
735                 break;
736         case OGRE:
737                 if (HERE(FOOD)) {
738                         rspeak(OGRE_FULL);
739                 } else {
740                         speak(actions[verb].message);
741                 }
742                 break;
743         default:
744                 rspeak(AM_GAME);
745         }
746         return GO_CLEAROBJ;
747 }
748
749 phase_codes_t fill(verb_t verb, obj_t obj) {
750         /*  Fill.  Bottle or urn must be empty, and liquid available.  (Vase
751          *  is nasty.) */
752         if (obj == VASE) {
753                 if (LIQLOC(game.loc) == NO_OBJECT) {
754                         rspeak(FILL_INVALID);
755                         return GO_CLEAROBJ;
756                 }
757                 if (!TOTING(VASE)) {
758                         rspeak(ARENT_CARRYING);
759                         return GO_CLEAROBJ;
760                 }
761                 rspeak(SHATTER_VASE);
762                 game.objects[VASE].prop = VASE_BROKEN;
763                 game.objects[VASE].fixed = IS_FIXED;
764                 drop(VASE, game.loc);
765                 return GO_CLEAROBJ;
766         }
767
768         if (obj == URN) {
769                 if (game.objects[URN].prop != URN_EMPTY) {
770                         rspeak(FULL_URN);
771                         return GO_CLEAROBJ;
772                 }
773                 if (!HERE(BOTTLE)) {
774                         rspeak(FILL_INVALID);
775                         return GO_CLEAROBJ;
776                 }
777                 int k = LIQUID();
778                 switch (k) {
779                 case WATER:
780                         game.objects[BOTTLE].prop = EMPTY_BOTTLE;
781                         rspeak(WATER_URN);
782                         break;
783                 case OIL:
784                         game.objects[URN].prop = URN_DARK;
785                         game.objects[BOTTLE].prop = EMPTY_BOTTLE;
786                         rspeak(OIL_URN);
787                         break;
788                 case NO_OBJECT:
789                 default:
790                         rspeak(FILL_INVALID);
791                         return GO_CLEAROBJ;
792                 }
793                 game.objects[k].place = LOC_NOWHERE;
794                 return GO_CLEAROBJ;
795         }
796         if (obj != INTRANSITIVE && obj != BOTTLE) {
797                 speak(actions[verb].message);
798                 return GO_CLEAROBJ;
799         }
800         if (obj == INTRANSITIVE && !HERE(BOTTLE))
801                 return GO_UNKNOWN;
802
803         if (HERE(URN) && game.objects[URN].prop != URN_EMPTY) {
804                 rspeak(URN_NOPOUR);
805                 return GO_CLEAROBJ;
806         }
807         if (LIQUID() != NO_OBJECT) {
808                 rspeak(BOTTLE_FULL);
809                 return GO_CLEAROBJ;
810         }
811         if (LIQLOC(game.loc) == NO_OBJECT) {
812                 rspeak(NO_LIQUID);
813                 return GO_CLEAROBJ;
814         }
815
816         state_change(BOTTLE,
817                      (LIQLOC(game.loc) == OIL) ? OIL_BOTTLE : WATER_BOTTLE);
818         if (TOTING(BOTTLE)) {
819                 game.objects[LIQUID()].place = CARRIED;
820         }
821         return GO_CLEAROBJ;
822 }
823
824 static phase_codes_t find(verb_t verb, obj_t obj) {
825         /* Find.  Might be carrying it, or it might be here.  Else give caveat.
826          */
827         if (TOTING(obj)) {
828                 rspeak(ALREADY_CARRYING);
829                 return GO_CLEAROBJ;
830         }
831
832         if (game.closed) {
833                 rspeak(NEEDED_NEARBY);
834                 return GO_CLEAROBJ;
835         }
836
837         if (AT(obj) || (LIQUID() == obj && AT(BOTTLE)) ||
838             obj == LIQLOC(game.loc) || (obj == DWARF && atdwrf(game.loc) > 0)) {
839                 rspeak(YOU_HAVEIT);
840                 return GO_CLEAROBJ;
841         }
842
843         speak(actions[verb].message);
844         return GO_CLEAROBJ;
845 }
846
847 static phase_codes_t fly(verb_t verb, obj_t obj) {
848         /* Fly.  Snide remarks unless hovering rug is here. */
849         if (obj == INTRANSITIVE) {
850                 if (!HERE(RUG)) {
851                         rspeak(FLAP_ARMS);
852                         return GO_CLEAROBJ;
853                 }
854                 if (game.objects[RUG].prop != RUG_HOVER) {
855                         rspeak(RUG_NOTHING2);
856                         return GO_CLEAROBJ;
857                 }
858                 obj = RUG;
859         }
860
861         if (obj != RUG) {
862                 speak(actions[verb].message);
863                 return GO_CLEAROBJ;
864         }
865         if (game.objects[RUG].prop != RUG_HOVER) {
866                 rspeak(RUG_NOTHING1);
867                 return GO_CLEAROBJ;
868         }
869
870         if (game.loc == LOC_CLIFF) {
871                 game.oldlc2 = game.oldloc;
872                 game.oldloc = game.loc;
873                 game.newloc = LOC_LEDGE;
874                 rspeak(RUG_GOES);
875         } else if (game.loc == LOC_LEDGE) {
876                 game.oldlc2 = game.oldloc;
877                 game.oldloc = game.loc;
878                 game.newloc = LOC_CLIFF;
879                 rspeak(RUG_RETURNS);
880         } else {
881                 // LCOV_EXCL_START
882                 /* should never happen */
883                 rspeak(NOTHING_HAPPENS);
884                 // LCOV_EXCL_STOP
885         }
886         return GO_TERMINATE;
887 }
888
889 static phase_codes_t inven(void) {
890         /* Inventory. If object, treat same as find.  Else report on current
891          * burden. */
892         bool empty = true;
893         for (obj_t i = 1; i <= NOBJECTS; i++) {
894                 if (i == BEAR || !TOTING(i))
895                         continue;
896                 if (empty) {
897                         rspeak(NOW_HOLDING);
898                         empty = false;
899                 }
900                 pspeak(i, touch, false, -1);
901         }
902         if (TOTING(BEAR))
903                 rspeak(TAME_BEAR);
904         if (empty)
905                 rspeak(NO_CARRY);
906         return GO_CLEAROBJ;
907 }
908
909 static phase_codes_t light(verb_t verb, obj_t obj) {
910         /*  Light.  Applicable only to lamp and urn. */
911         if (obj == INTRANSITIVE) {
912                 int selects = 0;
913                 if (HERE(LAMP) && game.objects[LAMP].prop == LAMP_DARK &&
914                     game.limit >= 0) {
915                         obj = LAMP;
916                         selects++;
917                 }
918                 if (HERE(URN) && game.objects[URN].prop == URN_DARK) {
919                         obj = URN;
920                         selects++;
921                 }
922                 if (selects != 1)
923                         return GO_UNKNOWN;
924         }
925
926         switch (obj) {
927         case URN:
928                 state_change(URN, game.objects[URN].prop == URN_EMPTY
929                                       ? URN_EMPTY
930                                       : URN_LIT);
931                 break;
932         case LAMP:
933                 if (game.limit < 0) {
934                         rspeak(LAMP_OUT);
935                         break;
936                 }
937                 state_change(LAMP, LAMP_BRIGHT);
938                 if (game.wzdark) {
939                         return GO_TOP;
940                 }
941                 break;
942         default:
943                 speak(actions[verb].message);
944         }
945         return GO_CLEAROBJ;
946 }
947
948 static phase_codes_t listen(void) {
949         /*  Listen.  Intransitive only.  Print stuff based on object sound
950          * properties. */
951         bool soundlatch = false;
952         vocab_t sound = locations[game.loc].sound;
953         if (sound != SILENT) {
954                 rspeak(sound);
955                 if (!locations[game.loc].loud) {
956                         rspeak(NO_MESSAGE);
957                 }
958                 soundlatch = true;
959         }
960         for (obj_t i = 1; i <= NOBJECTS; i++) {
961                 if (!HERE(i) || objects[i].sounds[0] == NULL ||
962                     PROP_IS_STASHED_OR_UNSEEN(i)) {
963                         continue;
964                 }
965                 int mi = game.objects[i].prop;
966                 /* (ESR) Some unpleasant magic on object states here. Ideally
967                  * we'd have liked the bird to be a normal object that we can
968                  * use state_change() on; can't do it, because there are
969                  * actually two different series of per-state birdsounds
970                  * depending on whether player has drunk dragon's blood. */
971                 if (i == BIRD) {
972                         mi += 3 * game.blooded;
973                 }
974                 pspeak(i, hear, true, mi, game.zzword);
975                 rspeak(NO_MESSAGE);
976                 if (i == BIRD && mi == BIRD_ENDSTATE) {
977                         DESTROY(BIRD);
978                 }
979                 soundlatch = true;
980         }
981         if (!soundlatch) {
982                 rspeak(ALL_SILENT);
983         }
984         return GO_CLEAROBJ;
985 }
986
987 static phase_codes_t lock(verb_t verb, obj_t obj) {
988         /* Lock, unlock, no object given.  Assume various things if present. */
989         if (obj == INTRANSITIVE) {
990                 if (HERE(CLAM)) {
991                         obj = CLAM;
992                 }
993                 if (HERE(OYSTER)) {
994                         obj = OYSTER;
995                 }
996                 if (AT(DOOR)) {
997                         obj = DOOR;
998                 }
999                 if (AT(GRATE)) {
1000                         obj = GRATE;
1001                 }
1002                 if (HERE(CHAIN)) {
1003                         obj = CHAIN;
1004                 }
1005                 if (obj == INTRANSITIVE) {
1006                         rspeak(NOTHING_LOCKED);
1007                         return GO_CLEAROBJ;
1008                 }
1009         }
1010
1011         /*  Lock, unlock object.  Special stuff for opening clam/oyster
1012          *  and for chain. */
1013
1014         switch (obj) {
1015         case CHAIN:
1016                 if (HERE(KEYS)) {
1017                         return chain(verb);
1018                 } else {
1019                         rspeak(NO_KEYS);
1020                 }
1021                 break;
1022         case GRATE:
1023                 if (HERE(KEYS)) {
1024                         if (game.closng) {
1025                                 rspeak(EXIT_CLOSED);
1026                                 if (!game.panic) {
1027                                         game.clock2 = PANICTIME;
1028                                 }
1029                                 game.panic = true;
1030                         } else {
1031                                 state_change(GRATE, (verb == LOCK)
1032                                                         ? GRATE_CLOSED
1033                                                         : GRATE_OPEN);
1034                         }
1035                 } else {
1036                         rspeak(NO_KEYS);
1037                 }
1038                 break;
1039         case CLAM:
1040                 if (verb == LOCK) {
1041                         rspeak(HUH_MAN);
1042                 } else if (TOTING(CLAM)) {
1043                         rspeak(DROP_CLAM);
1044                 } else if (!TOTING(TRIDENT)) {
1045                         rspeak(CLAM_OPENER);
1046                 } else {
1047                         DESTROY(CLAM);
1048                         drop(OYSTER, game.loc);
1049                         drop(PEARL, LOC_CULDESAC);
1050                         rspeak(PEARL_FALLS);
1051                 }
1052                 break;
1053         case OYSTER:
1054                 if (verb == LOCK) {
1055                         rspeak(HUH_MAN);
1056                 } else if (TOTING(OYSTER)) {
1057                         rspeak(DROP_OYSTER);
1058                 } else if (!TOTING(TRIDENT)) {
1059                         rspeak(OYSTER_OPENER);
1060                 } else {
1061                         rspeak(OYSTER_OPENS);
1062                 }
1063                 break;
1064         case DOOR:
1065                 rspeak((game.objects[DOOR].prop == DOOR_UNRUSTED) ? OK_MAN
1066                                                                   : RUSTY_DOOR);
1067                 break;
1068         case CAGE:
1069                 rspeak(NO_LOCK);
1070                 break;
1071         case KEYS:
1072                 rspeak(CANNOT_UNLOCK);
1073                 break;
1074         default:
1075                 speak(actions[verb].message);
1076         }
1077
1078         return GO_CLEAROBJ;
1079 }
1080
1081 static phase_codes_t pour(verb_t verb, obj_t obj) {
1082         /*  Pour.  If no object, or object is bottle, assume contents of bottle.
1083          *  special tests for pouring water or oil on plant or rusty door. */
1084         if (obj == BOTTLE || obj == INTRANSITIVE) {
1085                 obj = LIQUID();
1086         }
1087         if (obj == NO_OBJECT) {
1088                 return GO_UNKNOWN;
1089         }
1090         if (!TOTING(obj)) {
1091                 speak(actions[verb].message);
1092                 return GO_CLEAROBJ;
1093         }
1094
1095         if (obj != OIL && obj != WATER) {
1096                 rspeak(CANT_POUR);
1097                 return GO_CLEAROBJ;
1098         }
1099         if (HERE(URN) && game.objects[URN].prop == URN_EMPTY) {
1100                 return fill(verb, URN);
1101         }
1102         game.objects[BOTTLE].prop = EMPTY_BOTTLE;
1103         game.objects[obj].place = LOC_NOWHERE;
1104         if (!(AT(PLANT) || AT(DOOR))) {
1105                 rspeak(GROUND_WET);
1106                 return GO_CLEAROBJ;
1107         }
1108         if (!AT(DOOR)) {
1109                 if (obj == WATER) {
1110                         /* cycle through the three plant states */
1111                         state_change(PLANT,
1112                                      MOD(game.objects[PLANT].prop + 1, 3));
1113                         game.objects[PLANT2].prop = game.objects[PLANT].prop;
1114                         return GO_MOVE;
1115                 } else {
1116                         rspeak(SHAKING_LEAVES);
1117                         return GO_CLEAROBJ;
1118                 }
1119         } else {
1120                 state_change(DOOR, (obj == OIL) ? DOOR_UNRUSTED : DOOR_RUSTED);
1121                 return GO_CLEAROBJ;
1122         }
1123 }
1124
1125 static phase_codes_t quit(void) {
1126         /*  Quit.  Intransitive only.  Verify intent and exit if that's what he
1127          * wants. */
1128         if (yes_or_no(arbitrary_messages[REALLY_QUIT],
1129                       arbitrary_messages[OK_MAN], arbitrary_messages[OK_MAN])) {
1130                 terminate(quitgame);
1131         }
1132         return GO_CLEAROBJ;
1133 }
1134
1135 static phase_codes_t read(command_t command)
1136 /*  Read.  Print stuff based on objtxt.  Oyster (?) is special case. */
1137 {
1138         if (command.obj == INTRANSITIVE) {
1139                 command.obj = NO_OBJECT;
1140                 for (int i = 1; i <= NOBJECTS; i++) {
1141                         if (HERE(i) && objects[i].texts[0] != NULL &&
1142                             !PROP_IS_STASHED(i)) {
1143                                 command.obj = command.obj * NOBJECTS + i;
1144                         }
1145                 }
1146                 if (command.obj > NOBJECTS || command.obj == NO_OBJECT ||
1147                     DARK(game.loc)) {
1148                         return GO_UNKNOWN;
1149                 }
1150         }
1151
1152         if (DARK(game.loc)) {
1153                 sspeak(NO_SEE, command.word[0].raw);
1154         } else if (command.obj == OYSTER) {
1155                 if (!TOTING(OYSTER) || !game.closed) {
1156                         rspeak(DONT_UNDERSTAND);
1157                 } else if (!game.clshnt) {
1158                         game.clshnt = yes_or_no(arbitrary_messages[CLUE_QUERY],
1159                                                 arbitrary_messages[WAYOUT_CLUE],
1160                                                 arbitrary_messages[OK_MAN]);
1161                 } else {
1162                         pspeak(OYSTER, hear, true,
1163                                1); // Not really a sound, but oh well.
1164                 }
1165         } else if (objects[command.obj].texts[0] == NULL ||
1166                    PROP_IS_NOTFOUND(command.obj)) {
1167                 speak(actions[command.verb].message);
1168         } else {
1169                 pspeak(command.obj, study, true,
1170                        game.objects[command.obj].prop);
1171         }
1172         return GO_CLEAROBJ;
1173 }
1174
1175 static phase_codes_t reservoir(void) {
1176         /*  Z'ZZZ (word gets recomputed at startup; different each game). */
1177         if (!AT(RESER) && game.loc != LOC_RESBOTTOM) {
1178                 rspeak(NOTHING_HAPPENS);
1179                 return GO_CLEAROBJ;
1180         } else {
1181                 state_change(RESER, game.objects[RESER].prop == WATERS_PARTED
1182                                         ? WATERS_UNPARTED
1183                                         : WATERS_PARTED);
1184                 if (AT(RESER))
1185                         return GO_CLEAROBJ;
1186                 else {
1187                         game.oldlc2 = game.loc;
1188                         game.newloc = LOC_NOWHERE;
1189                         rspeak(NOT_BRIGHT);
1190                         return GO_TERMINATE;
1191                 }
1192         }
1193 }
1194
1195 static phase_codes_t rub(verb_t verb, obj_t obj) {
1196         /* Rub.  Yields various snide remarks except for lit urn. */
1197         if (obj == URN && game.objects[URN].prop == URN_LIT) {
1198                 DESTROY(URN);
1199                 drop(AMBER, game.loc);
1200                 game.objects[AMBER].prop = AMBER_IN_ROCK;
1201                 --game.tally;
1202                 drop(CAVITY, game.loc);
1203                 rspeak(URN_GENIES);
1204         } else if (obj != LAMP) {
1205                 rspeak(PECULIAR_NOTHING);
1206         } else {
1207                 speak(actions[verb].message);
1208         }
1209         return GO_CLEAROBJ;
1210 }
1211
1212 static phase_codes_t say(command_t command) {
1213         /* Say.  Echo WD2. Magic words override. */
1214         if (command.word[1].type == MOTION &&
1215             (command.word[1].id == XYZZY || command.word[1].id == PLUGH ||
1216              command.word[1].id == PLOVER)) {
1217                 return GO_WORD2;
1218         }
1219         if (command.word[1].type == ACTION && command.word[1].id == PART) {
1220                 return reservoir();
1221         }
1222
1223         if (command.word[1].type == ACTION &&
1224             (command.word[1].id == FEE || command.word[1].id == FIE ||
1225              command.word[1].id == FOE || command.word[1].id == FOO ||
1226              command.word[1].id == FUM || command.word[1].id == PART)) {
1227                 return bigwords(command.word[1].id);
1228         }
1229         sspeak(OKEY_DOKEY, command.word[1].raw);
1230         return GO_CLEAROBJ;
1231 }
1232
1233 static phase_codes_t throw_support(vocab_t spk) {
1234         rspeak(spk);
1235         drop(AXE, game.loc);
1236         return GO_MOVE;
1237 }
1238
1239 static phase_codes_t throwit(command_t command) {
1240         /*  Throw.  Same as discard unless axe.  Then same as attack except
1241          *  ignore bird, and if dwarf is present then one might be killed.
1242          *  (Only way to do so!)  Axe also special for dragon, bear, and
1243          *  troll.  Treasures special for troll. */
1244         if (!TOTING(command.obj)) {
1245                 speak(actions[command.verb].message);
1246                 return GO_CLEAROBJ;
1247         }
1248         if (objects[command.obj].is_treasure && AT(TROLL)) {
1249                 /*  Snarf a treasure for the troll. */
1250                 drop(command.obj, LOC_NOWHERE);
1251                 move(TROLL, LOC_NOWHERE);
1252                 move(TROLL + NOBJECTS, IS_FREE);
1253                 drop(TROLL2, objects[TROLL].plac);
1254                 drop(TROLL2 + NOBJECTS, objects[TROLL].fixd);
1255                 juggle(CHASM);
1256                 rspeak(TROLL_SATISFIED);
1257                 return GO_CLEAROBJ;
1258         }
1259         if (command.obj == FOOD && HERE(BEAR)) {
1260                 /* But throwing food is another story. */
1261                 command.obj = BEAR;
1262                 return (feed(command.verb, command.obj));
1263         }
1264         if (command.obj != AXE) {
1265                 return (discard(command.verb, command.obj));
1266         } else {
1267                 if (atdwrf(game.loc) <= 0) {
1268                         if (AT(DRAGON) &&
1269                             game.objects[DRAGON].prop == DRAGON_BARS)
1270                                 return throw_support(DRAGON_SCALES);
1271                         if (AT(TROLL)) {
1272                                 return throw_support(TROLL_RETURNS);
1273                         }
1274                         if (AT(OGRE)) {
1275                                 return throw_support(OGRE_DODGE);
1276                         }
1277                         if (HERE(BEAR) &&
1278                             game.objects[BEAR].prop == UNTAMED_BEAR) {
1279                                 /* This'll teach him to throw the axe at the
1280                                  * bear! */
1281                                 drop(AXE, game.loc);
1282                                 game.objects[AXE].fixed = IS_FIXED;
1283                                 juggle(BEAR);
1284                                 state_change(AXE, AXE_LOST);
1285                                 return GO_CLEAROBJ;
1286                         }
1287                         command.obj = INTRANSITIVE;
1288                         return (attack(command));
1289                 }
1290
1291                 if (randrange(NDWARVES + 1) < game.dflag) {
1292                         return throw_support(DWARF_DODGES);
1293                 } else {
1294                         int i = atdwrf(game.loc);
1295                         game.dwarves[i].seen = false;
1296                         game.dwarves[i].loc = LOC_NOWHERE;
1297                         return throw_support(
1298                             (++game.dkill == 1) ? DWARF_SMOKE : KILLED_DWARF);
1299                 }
1300         }
1301 }
1302
1303 static phase_codes_t wake(verb_t verb, obj_t obj) {
1304         /* Wake.  Only use is to disturb the dwarves. */
1305         if (obj != DWARF || !game.closed) {
1306                 speak(actions[verb].message);
1307                 return GO_CLEAROBJ;
1308         } else {
1309                 rspeak(PROD_DWARF);
1310                 return GO_DWARFWAKE;
1311         }
1312 }
1313
1314 static phase_codes_t seed(verb_t verb, const char *arg) {
1315         /* Set seed */
1316         int32_t seed = strtol(arg, NULL, 10);
1317         speak(actions[verb].message, seed);
1318         set_seed(seed);
1319         --game.turns;
1320         return GO_TOP;
1321 }
1322
1323 static phase_codes_t waste(verb_t verb, turn_t turns) {
1324         /* Burn turns */
1325         game.limit -= turns;
1326         speak(actions[verb].message, (int)game.limit);
1327         return GO_TOP;
1328 }
1329
1330 static phase_codes_t wave(verb_t verb, obj_t obj) {
1331         /* Wave.  No effect unless waving rod at fissure or at bird. */
1332         if (obj != ROD || !TOTING(obj) ||
1333             (!HERE(BIRD) && (game.closng || !AT(FISSURE)))) {
1334                 speak(((!TOTING(obj)) && (obj != ROD || !TOTING(ROD2)))
1335                           ? arbitrary_messages[ARENT_CARRYING]
1336                           : actions[verb].message);
1337                 return GO_CLEAROBJ;
1338         }
1339
1340         if (game.objects[BIRD].prop == BIRD_UNCAGED &&
1341             game.loc == game.objects[STEPS].place && PROP_IS_NOTFOUND(JADE)) {
1342                 drop(JADE, game.loc);
1343                 PROP_SET_FOUND(JADE);
1344                 --game.tally;
1345                 rspeak(NECKLACE_FLY);
1346                 return GO_CLEAROBJ;
1347         } else {
1348                 if (game.closed) {
1349                         rspeak((game.objects[BIRD].prop == BIRD_CAGED)
1350                                    ? CAGE_FLY
1351                                    : FREE_FLY);
1352                         return GO_DWARFWAKE;
1353                 }
1354                 if (game.closng || !AT(FISSURE)) {
1355                         rspeak((game.objects[BIRD].prop == BIRD_CAGED)
1356                                    ? CAGE_FLY
1357                                    : FREE_FLY);
1358                         return GO_CLEAROBJ;
1359                 }
1360                 if (HERE(BIRD))
1361                         rspeak((game.objects[BIRD].prop == BIRD_CAGED)
1362                                    ? CAGE_FLY
1363                                    : FREE_FLY);
1364
1365                 state_change(FISSURE, game.objects[FISSURE].prop == BRIDGED
1366                                           ? UNBRIDGED
1367                                           : BRIDGED);
1368                 return GO_CLEAROBJ;
1369         }
1370 }
1371
1372 phase_codes_t action(command_t command) {
1373         /*  Analyse a verb.  Remember what it was, go back for object if second
1374          * word unless verb is "say", which snarfs arbitrary second word.
1375          */
1376         /* Previously, actions that result in a message, but don't do anything
1377          * further were called "specials". Now they're handled here as normal
1378          * actions. If noaction is true, then we spit out the message and return
1379          */
1380         if (actions[command.verb].noaction) {
1381                 speak(actions[command.verb].message);
1382                 return GO_CLEAROBJ;
1383         }
1384
1385         if (command.part == unknown) {
1386                 /*  Analyse an object word.  See if the thing is here, whether
1387                  *  we've got a verb yet, and so on.  Object must be here
1388                  *  unless verb is "find" or "invent(ory)" (and no new verb
1389                  *  yet to be analysed).  Water and oil are also funny, since
1390                  *  they are never actually dropped at any location, but might
1391                  *  be here inside the bottle or urn or as a feature of the
1392                  *  location. */
1393                 if (HERE(command.obj)) {
1394                         /* FALL THROUGH */;
1395                 } else if (command.obj == DWARF && atdwrf(game.loc) > 0) {
1396                         /* FALL THROUGH */;
1397                 } else if (!game.closed &&
1398                            ((LIQUID() == command.obj && HERE(BOTTLE)) ||
1399                             command.obj == LIQLOC(game.loc))) {
1400                         /* FALL THROUGH */;
1401                 } else if (command.obj == OIL && HERE(URN) &&
1402                            game.objects[URN].prop != URN_EMPTY) {
1403                         command.obj = URN;
1404                         /* FALL THROUGH */;
1405                 } else if (command.obj == PLANT && AT(PLANT2) &&
1406                            game.objects[PLANT2].prop != PLANT_THIRSTY) {
1407                         command.obj = PLANT2;
1408                         /* FALL THROUGH */;
1409                 } else if (command.obj == KNIFE && game.knfloc == game.loc) {
1410                         game.knfloc = -1;
1411                         rspeak(KNIVES_VANISH);
1412                         return GO_CLEAROBJ;
1413                 } else if (command.obj == ROD && HERE(ROD2)) {
1414                         command.obj = ROD2;
1415                         /* FALL THROUGH */;
1416                 } else if ((command.verb == FIND ||
1417                             command.verb == INVENTORY) &&
1418                            (command.word[1].id == WORD_EMPTY ||
1419                             command.word[1].id == WORD_NOT_FOUND)) {
1420                         /* FALL THROUGH */;
1421                 } else {
1422                         sspeak(NO_SEE, command.word[0].raw);
1423                         return GO_CLEAROBJ;
1424                 }
1425
1426                 if (command.verb != 0) {
1427                         command.part = transitive;
1428                 }
1429         }
1430
1431         switch (command.part) {
1432         case intransitive:
1433                 if (command.word[1].raw[0] != '\0' && command.verb != SAY) {
1434                         return GO_WORD2;
1435                 }
1436                 if (command.verb == SAY) {
1437                         /* KEYS is not special, anything not NO_OBJECT or
1438                          * INTRANSITIVE will do here. We're preventing
1439                          * interpretation as an intransitive verb when the word
1440                          * is unknown. */
1441                         command.obj =
1442                             command.word[1].raw[0] != '\0' ? KEYS : NO_OBJECT;
1443                 }
1444                 if (command.obj == NO_OBJECT || command.obj == INTRANSITIVE) {
1445                         /*  Analyse an intransitive verb (ie, no object given
1446                          * yet). */
1447                         switch (command.verb) {
1448                         case CARRY:
1449                                 return vcarry(command.verb, INTRANSITIVE);
1450                         case DROP:
1451                                 return GO_UNKNOWN;
1452                         case SAY:
1453                                 return GO_UNKNOWN;
1454                         case UNLOCK:
1455                                 return lock(command.verb, INTRANSITIVE);
1456                         case NOTHING: {
1457                                 rspeak(OK_MAN);
1458                                 return (GO_CLEAROBJ);
1459                         }
1460                         case LOCK:
1461                                 return lock(command.verb, INTRANSITIVE);
1462                         case LIGHT:
1463                                 return light(command.verb, INTRANSITIVE);
1464                         case EXTINGUISH:
1465                                 return extinguish(command.verb, INTRANSITIVE);
1466                         case WAVE:
1467                                 return GO_UNKNOWN;
1468                         case TAME:
1469                                 return GO_UNKNOWN;
1470                         case GO: {
1471                                 speak(actions[command.verb].message);
1472                                 return GO_CLEAROBJ;
1473                         }
1474                         case ATTACK:
1475                                 command.obj = INTRANSITIVE;
1476                                 return attack(command);
1477                         case POUR:
1478                                 return pour(command.verb, INTRANSITIVE);
1479                         case EAT:
1480                                 return eat(command.verb, INTRANSITIVE);
1481                         case DRINK:
1482                                 return drink(command.verb, INTRANSITIVE);
1483                         case RUB:
1484                                 return GO_UNKNOWN;
1485                         case THROW:
1486                                 return GO_UNKNOWN;
1487                         case QUIT:
1488                                 return quit();
1489                         case FIND:
1490                                 return GO_UNKNOWN;
1491                         case INVENTORY:
1492                                 return inven();
1493                         case FEED:
1494                                 return GO_UNKNOWN;
1495                         case FILL:
1496                                 return fill(command.verb, INTRANSITIVE);
1497                         case BLAST:
1498                                 blast();
1499                                 return GO_CLEAROBJ;
1500                         case SCORE:
1501                                 score(scoregame);
1502                                 return GO_CLEAROBJ;
1503                         case FEE:
1504                         case FIE:
1505                         case FOE:
1506                         case FOO:
1507                         case FUM:
1508                                 return bigwords(command.word[0].id);
1509                         case BRIEF:
1510                                 return brief();
1511                         case READ:
1512                                 command.obj = INTRANSITIVE;
1513                                 return read(command);
1514                         case BREAK:
1515                                 return GO_UNKNOWN;
1516                         case WAKE:
1517                                 return GO_UNKNOWN;
1518                         case SAVE:
1519                                 return suspend();
1520                         case RESUME:
1521                                 return resume();
1522                         case FLY:
1523                                 return fly(command.verb, INTRANSITIVE);
1524                         case LISTEN:
1525                                 return listen();
1526                         case PART:
1527                                 return reservoir();
1528                         case SEED:
1529                         case WASTE:
1530                                 rspeak(NUMERIC_REQUIRED);
1531                                 return GO_TOP;
1532                         default: // LCOV_EXCL_LINE
1533                                 BUG(INTRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE
1534                         }
1535                 }
1536         /* FALLTHRU */
1537         case transitive:
1538                 /*  Analyse a transitive verb. */
1539                 switch (command.verb) {
1540                 case CARRY:
1541                         return vcarry(command.verb, command.obj);
1542                 case DROP:
1543                         return discard(command.verb, command.obj);
1544                 case SAY:
1545                         return say(command);
1546                 case UNLOCK:
1547                         return lock(command.verb, command.obj);
1548                 case NOTHING: {
1549                         rspeak(OK_MAN);
1550                         return (GO_CLEAROBJ);
1551                 }
1552                 case LOCK:
1553                         return lock(command.verb, command.obj);
1554                 case LIGHT:
1555                         return light(command.verb, command.obj);
1556                 case EXTINGUISH:
1557                         return extinguish(command.verb, command.obj);
1558                 case WAVE:
1559                         return wave(command.verb, command.obj);
1560                 case TAME: {
1561                         speak(actions[command.verb].message);
1562                         return GO_CLEAROBJ;
1563                 }
1564                 case GO: {
1565                         speak(actions[command.verb].message);
1566                         return GO_CLEAROBJ;
1567                 }
1568                 case ATTACK:
1569                         return attack(command);
1570                 case POUR:
1571                         return pour(command.verb, command.obj);
1572                 case EAT:
1573                         return eat(command.verb, command.obj);
1574                 case DRINK:
1575                         return drink(command.verb, command.obj);
1576                 case RUB:
1577                         return rub(command.verb, command.obj);
1578                 case THROW:
1579                         return throwit(command);
1580                 case QUIT:
1581                         speak(actions[command.verb].message);
1582                         return GO_CLEAROBJ;
1583                 case FIND:
1584                         return find(command.verb, command.obj);
1585                 case INVENTORY:
1586                         return find(command.verb, command.obj);
1587                 case FEED:
1588                         return feed(command.verb, command.obj);
1589                 case FILL:
1590                         return fill(command.verb, command.obj);
1591                 case BLAST:
1592                         blast();
1593                         return GO_CLEAROBJ;
1594                 case SCORE:
1595                         speak(actions[command.verb].message);
1596                         return GO_CLEAROBJ;
1597                 case FEE:
1598                 case FIE:
1599                 case FOE:
1600                 case FOO:
1601                 case FUM:
1602                         speak(actions[command.verb].message);
1603                         return GO_CLEAROBJ;
1604                 case BRIEF:
1605                         speak(actions[command.verb].message);
1606                         return GO_CLEAROBJ;
1607                 case READ:
1608                         return read(command);
1609                 case BREAK:
1610                         return vbreak(command.verb, command.obj);
1611                 case WAKE:
1612                         return wake(command.verb, command.obj);
1613                 case SAVE:
1614                         speak(actions[command.verb].message);
1615                         return GO_CLEAROBJ;
1616                 case RESUME:
1617                         speak(actions[command.verb].message);
1618                         return GO_CLEAROBJ;
1619                 case FLY:
1620                         return fly(command.verb, command.obj);
1621                 case LISTEN:
1622                         speak(actions[command.verb].message);
1623                         return GO_CLEAROBJ;
1624                 // LCOV_EXCL_START
1625                 // This case should never happen - here only as placeholder
1626                 case PART:
1627                         return reservoir();
1628                 // LCOV_EXCL_STOP
1629                 case SEED:
1630                         return seed(command.verb, command.word[1].raw);
1631                 case WASTE:
1632                         return waste(command.verb,
1633                                      (turn_t)atol(command.word[1].raw));
1634                 default: // LCOV_EXCL_LINE
1635                         BUG(TRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE
1636                 }
1637         case unknown:
1638                 /* Unknown verb, couldn't deduce object - might need hint */
1639                 sspeak(WHAT_DO, command.word[0].raw);
1640                 return GO_CHECKHINT;
1641         default: // LCOV_EXCL_LINE
1642                 BUG(SPEECHPART_NOT_TRANSITIVE_OR_INTRANSITIVE_OR_UNKNOWN); // LCOV_EXCL_LINE
1643         }
1644 }
1645
1646 // end