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