Nothing but FOO can start a magic-word sequence,
[open-adventure.git] / actions.c
index 3d03d830e5f0da9b71e04c80395fbc47492ab8bf..936e4d80978d9aeebfc135a6576e2be9c109d7f2 100644 (file)
--- a/actions.c
+++ b/actions.c
@@ -1,12 +1,21 @@
+/*
+ * Actions for the dungeon-running code.
+ *
+ * Copyright (c) 1977, 2005 by Will Crowther and Don Woods
+ * Copyright (c) 2017 by Eric S. Raymond
+ * SPDX-License-Identifier: BSD-2-clause
+ */
+
 #include <stdlib.h>
 #include <stdbool.h>
 #include <string.h>
 #include "advent.h"
 #include "dungeon.h"
+#include <inttypes.h>
 
-static int fill(verb_t, obj_t);
+static phase_codes_t fill(verb_t, obj_t);
 
-static int attack(command_t command)
+static phase_codes_t attack(command_t command)
 /*  Attack.  Assume target if unambiguous.  "Throw" also links here.
  *  Attackable objects fall into two categories: enemies (snake,
  *  dwarf, etc.)  and others (bird, clam, machine).  Ambiguous if 2
@@ -75,6 +84,7 @@ static int attack(command_t command)
     if (obj == VEND) {
         state_change(VEND,
                      game.prop[VEND] == VEND_BLOCKS ? VEND_UNBLOCKS : VEND_BLOCKS);
+
         return GO_CLEAROBJ;
     }
 
@@ -101,7 +111,7 @@ static int attack(command_t command)
          *  fixed), move rug there (not fixed), and move him there,
          *  too.  Then do a null motion to get new description. */
         rspeak(BARE_HANDS_QUERY);
-        if (!silent_yes()) {
+        if (!silent_yes_or_no()) {
             speak(arbitrary_messages[NASTY_DRAGON]);
             return GO_MOVE;
         }
@@ -176,16 +186,24 @@ static int attack(command_t command)
     return GO_CLEAROBJ;
 }
 
-static int bigwords(vocab_t id)
-/*  FEE FIE FOE FOO (AND FUM).  Advance to next state if given in proper order.
- *  Look up foo in special section of vocab to determine which word we've got.
- *  Last word zips the eggs back to the giant room (unless already there). */
+static phase_codes_t bigwords(vocab_t id)
+/* Only called on FEE FIE FOE FOO (AND FUM).  Advance to next state if given
+ * in proper order. Look up foo in special section of vocab to determine which
+ * word we've got. Last word zips the eggs back to the giant room (unless
+ * already there). */
 {
-    if ((game.foobar == WORD_EMPTY && id == FEE) ||
-        (game.foobar == FEE && id == FIE) ||
-        (game.foobar == FIE && id == FOE) ||
-        (game.foobar == FOE && id == FOO) ||
-        (game.foobar == FOE && id == FUM)) {
+    int foobar = abs(game.foobar);
+
+    if ((foobar == WORD_EMPTY) && (id == FIE || id == FOE || id == FOO || id == FUM)) {
+       rspeak(NOTHING_HAPPENS);
+       return GO_CLEAROBJ;
+    }
+    
+    if ((foobar == WORD_EMPTY && id == FEE) ||
+        (foobar == FEE && id == FIE) ||
+        (foobar == FIE && id == FOE) ||
+        (foobar == FOE && id == FOO) ||
+        (foobar == FOE && id == FUM)) {
         game.foobar = id;
         if ((id != FOO) && (id != FUM)) {
             rspeak(OK_MAN);
@@ -196,29 +214,26 @@ static int bigwords(vocab_t id)
             (TOTING(EGGS) && game.loc == objects[EGGS].plac)) {
             rspeak(NOTHING_HAPPENS);
             return GO_CLEAROBJ;
+        } else if (id == FUM) {
+            goto fum;
         } else {
             /*  Bring back troll if we steal the eggs back from him before
              *  crossing. */
             if (game.place[EGGS] == LOC_NOWHERE && game.place[TROLL] == LOC_NOWHERE && game.prop[TROLL] == TROLL_UNPAID)
                 game.prop[TROLL] = TROLL_PAIDONCE;
             if (HERE(EGGS))
-                pspeak(EGGS, look, EGGS_VANISHED, true);
+                pspeak(EGGS, look, true, EGGS_VANISHED);
             else if (game.loc == objects[EGGS].plac)
-                pspeak(EGGS, look, EGGS_HERE, true);
+                pspeak(EGGS, look, true, EGGS_HERE);
             else
-                pspeak(EGGS, look, EGGS_DONE, true);
+                pspeak(EGGS, look, true, EGGS_DONE);
             move(EGGS, objects[EGGS].plac);
 
             return GO_CLEAROBJ;
         }
     } else {
-        if (game.loc == LOC_GIANTROOM) {
-            rspeak(START_OVER);
-        } else {
-            /* This is new begavior in Open Adventure - sounds better when
-             * player isn't in the Giant Room. */
-            rspeak(WELL_POINTLESS);
-        }
+fum:
+       rspeak(START_OVER);
         game.foobar = WORD_EMPTY;
         return GO_CLEAROBJ;
     }
@@ -245,7 +260,7 @@ static void blast(void)
     }
 }
 
-static int vbreak(verb_t verb, obj_t obj)
+static phase_codes_t vbreak(verb_t verb, obj_t obj)
 /*  Break.  Only works for mirror in repository and, of course, the vase. */
 {
     switch (obj) {
@@ -265,14 +280,14 @@ static int vbreak(verb_t verb, obj_t obj)
             game.fixed[VASE] = IS_FIXED;
             break;
         }
-       /* FALLTHRU */
+    /* FALLTHRU */
     default:
         speak(actions[verb].message);
     }
     return (GO_CLEAROBJ);
 }
 
-static int brief(void)
+static phase_codes_t brief(void)
 /*  Brief.  Intransitive only.  Suppress full descriptions after first time. */
 {
     game.abbnum = 10000;
@@ -281,7 +296,7 @@ static int brief(void)
     return GO_CLEAROBJ;
 }
 
-static int vcarry(verb_t verb, obj_t obj)
+static phase_codes_t vcarry(verb_t verb, obj_t obj)
 /*  Carry an object.  Special cases for bird and cage (if bird in cage, can't
  *  take one without the other).  Liquids also special, since they depend on
  *  status of bottle.  Also various side effects, etc. */
@@ -450,7 +465,7 @@ static int chain(verb_t verb)
     return GO_CLEAROBJ;
 }
 
-static int discard(verb_t verb, obj_t obj)
+static phase_codes_t discard(verb_t verb, obj_t obj)
 /*  Discard object.  "Throw" also comes here for most objects.  Special cases for
  *  bird (might attack snake or dragon) and cage (might contain bird) and vase.
  *  Drop coins at vending machine for extra batteries. */
@@ -491,7 +506,7 @@ static int discard(verb_t verb, obj_t obj)
     if (obj == COINS && HERE(VEND)) {
         DESTROY(COINS);
         drop(BATTERY, game.loc);
-        pspeak(BATTERY, look, FRESH_BATTERIES, true);
+        pspeak(BATTERY, look, true, FRESH_BATTERIES);
         return GO_CLEAROBJ;
     }
 
@@ -554,7 +569,7 @@ static int discard(verb_t verb, obj_t obj)
     return GO_CLEAROBJ;
 }
 
-static int drink(verb_t verb, obj_t obj)
+static phase_codes_t drink(verb_t verb, obj_t obj)
 /*  Drink.  If no object, assume water and look for it here.  If water is in
  *  the bottle, drink that, else must be at a water loc, so drink stream. */
 {
@@ -584,7 +599,7 @@ static int drink(verb_t verb, obj_t obj)
     return GO_CLEAROBJ;
 }
 
-static int eat(verb_t verb, obj_t obj)
+static phase_codes_t eat(verb_t verb, obj_t obj)
 /*  Eat.  Intransitive: assume food if present, else ask what.  Transitive: food
  *  ok, some things lose appetite, rest are ridiculous. */
 {
@@ -592,7 +607,7 @@ static int eat(verb_t verb, obj_t obj)
     case INTRANSITIVE:
         if (!HERE(FOOD))
             return GO_UNKNOWN;
-       /* FALLTHRU */
+    /* FALLTHRU */
     case FOOD:
         DESTROY(FOOD);
         rspeak(THANKS_DELICIOUS);
@@ -614,7 +629,7 @@ static int eat(verb_t verb, obj_t obj)
     return GO_CLEAROBJ;
 }
 
-static int extinguish(verb_t verb, obj_t obj)
+static phase_codes_t extinguish(verb_t verb, obj_t obj)
 /* Extinguish.  Lamp, urn, dragon/volcano (nice try). */
 {
     if (obj == INTRANSITIVE) {
@@ -631,7 +646,7 @@ static int extinguish(verb_t verb, obj_t obj)
         if (game.prop[URN] != URN_EMPTY) {
             state_change(URN, URN_DARK);
         } else {
-            pspeak(URN, change, URN_DARK, true);
+            pspeak(URN, change, true, URN_DARK);
         }
         break;
     case LAMP:
@@ -650,7 +665,7 @@ static int extinguish(verb_t verb, obj_t obj)
     return GO_CLEAROBJ;
 }
 
-static int feed(verb_t verb, obj_t obj)
+static phase_codes_t feed(verb_t verb, obj_t obj)
 /*  Feed.  If bird, no seed.  Snake, dragon, troll: quip.  If dwarf, make him
  *  mad.  Bear, special. */
 {
@@ -710,7 +725,7 @@ static int feed(verb_t verb, obj_t obj)
     return GO_CLEAROBJ;
 }
 
-int fill(verb_t verb, obj_t obj)
+phase_codes_t fill(verb_t verb, obj_t obj)
 /*  Fill.  Bottle or urn must be empty, and liquid available.  (Vase
  *  is nasty.) */
 {
@@ -786,7 +801,7 @@ int fill(verb_t verb, obj_t obj)
     return GO_CLEAROBJ;
 }
 
-static int find(verb_t verb, obj_t obj)
+static phase_codes_t find(verb_t verb, obj_t obj)
 /* Find.  Might be carrying it, or it might be here.  Else give caveat. */
 {
     if (TOTING(obj)) {
@@ -812,7 +827,7 @@ static int find(verb_t verb, obj_t obj)
     return GO_CLEAROBJ;
 }
 
-static int fly(verb_t verb, obj_t obj)
+static phase_codes_t fly(verb_t verb, obj_t obj)
 /* Fly.  Snide remarks unless hovering rug is here. */
 {
     if (obj == INTRANSITIVE) {
@@ -835,20 +850,27 @@ static int fly(verb_t verb, obj_t obj)
         rspeak(RUG_NOTHING1);
         return GO_CLEAROBJ;
     }
-    game.oldlc2 = game.oldloc;
-    game.oldloc = game.loc;
 
-    if (game.prop[SAPPH] == STATE_NOTFOUND) {
-        game.newloc = game.place[SAPPH];
+    if (game.loc == LOC_CLIFF) {
+        game.oldlc2 = game.oldloc;
+        game.oldloc = game.loc;
+        game.newloc = LOC_LEDGE;
         rspeak(RUG_GOES);
-    } else {
+    } else if (game.loc == LOC_LEDGE) {
+        game.oldlc2 = game.oldloc;
+        game.oldloc = game.loc;
         game.newloc = LOC_CLIFF;
         rspeak(RUG_RETURNS);
+    } else {
+// LCOV_EXCL_START
+        /* should never happen */
+        rspeak(NOTHING_HAPPENS);
+// LCOV_EXCL_STOP
     }
     return GO_TERMINATE;
 }
 
-static int inven(void)
+static phase_codes_t inven(void)
 /* Inventory. If object, treat same as find.  Else report on current burden. */
 {
     bool empty = true;
@@ -860,7 +882,7 @@ static int inven(void)
             rspeak(NOW_HOLDING);
             empty = false;
         }
-        pspeak(i, touch, -1, false);
+        pspeak(i, touch, false, -1);
     }
     if (TOTING(BEAR))
         rspeak(TAME_BEAR);
@@ -869,7 +891,7 @@ static int inven(void)
     return GO_CLEAROBJ;
 }
 
-static int light(verb_t verb, obj_t obj)
+static phase_codes_t light(verb_t verb, obj_t obj)
 /*  Light.  Applicable only to lamp and urn. */
 {
     if (obj == INTRANSITIVE) {
@@ -907,15 +929,16 @@ static int light(verb_t verb, obj_t obj)
     return GO_CLEAROBJ;
 }
 
-static int listen(void)
-/*  Listen.  Intransitive only.  Print stuff based on object sound proprties. */
+static phase_codes_t listen(void)
+/*  Listen.  Intransitive only.  Print stuff based on object sound properties. */
 {
+    bool soundlatch = false;
     vocab_t sound = locations[game.loc].sound;
     if (sound != SILENT) {
         rspeak(sound);
         if (!locations[game.loc].loud)
             rspeak(NO_MESSAGE);
-        return GO_CLEAROBJ;
+        soundlatch = true;
     }
     for (obj_t i = 1; i <= NOBJECTS; i++) {
         if (!HERE(i) ||
@@ -930,17 +953,18 @@ static int listen(void)
          * depending on whether player has drunk dragon's blood. */
         if (i == BIRD)
             mi += 3 * game.blooded;
-        pspeak(i, hear, mi, true, game.zzword);
+        pspeak(i, hear, true, mi, game.zzword);
         rspeak(NO_MESSAGE);
         if (i == BIRD && mi == BIRD_ENDSTATE)
             DESTROY(BIRD);
-        return GO_CLEAROBJ;
+        soundlatch = true;
     }
-    rspeak(ALL_SILENT);
+    if (!soundlatch)
+        rspeak(ALL_SILENT);
     return GO_CLEAROBJ;
 }
 
-static int lock(verb_t verb, obj_t obj)
+static phase_codes_t lock(verb_t verb, obj_t obj)
 /* Lock, unlock, no object given.  Assume various things if present. */
 {
     if (obj == INTRANSITIVE) {
@@ -988,6 +1012,8 @@ static int lock(verb_t verb, obj_t obj)
     case CLAM:
         if (verb == LOCK)
             rspeak(HUH_MAN);
+        else if (TOTING(CLAM))
+            rspeak(DROP_CLAM);
         else if (!TOTING(TRIDENT))
             rspeak(CLAM_OPENER);
         else {
@@ -1023,7 +1049,7 @@ static int lock(verb_t verb, obj_t obj)
     return GO_CLEAROBJ;
 }
 
-static int pour(verb_t verb, obj_t obj)
+static phase_codes_t pour(verb_t verb, obj_t obj)
 /*  Pour.  If no object, or object is bottle, assume contents of bottle.
  *  special tests for pouring water or oil on plant or rusty door. */
 {
@@ -1068,15 +1094,15 @@ static int pour(verb_t verb, obj_t obj)
     }
 }
 
-static int quit(void)
+static phase_codes_t quit(void)
 /*  Quit.  Intransitive only.  Verify intent and exit if that's what he wants. */
 {
-    if (yes(arbitrary_messages[REALLY_QUIT], arbitrary_messages[OK_MAN], arbitrary_messages[OK_MAN]))
+    if (yes_or_no(arbitrary_messages[REALLY_QUIT], arbitrary_messages[OK_MAN], arbitrary_messages[OK_MAN]))
         terminate(quitgame);
     return GO_CLEAROBJ;
 }
 
-static int read(command_t command)
+static phase_codes_t read(command_t command)
 /*  Read.  Print stuff based on objtxt.  Oyster (?) is special case. */
 {
     if (command.obj == INTRANSITIVE) {
@@ -1093,17 +1119,23 @@ static int read(command_t command)
 
     if (DARK(game.loc)) {
         sspeak(NO_SEE, command.word[0].raw);
-    } else if (command.obj == OYSTER && !game.clshnt && game.closed) {
-        game.clshnt = yes(arbitrary_messages[CLUE_QUERY], arbitrary_messages[WAYOUT_CLUE], arbitrary_messages[OK_MAN]);
+    } else if (command.obj == OYSTER) {
+        if (!TOTING(OYSTER) || !game.closed) {
+            rspeak(DONT_UNDERSTAND);
+        } else if (!game.clshnt) {
+            game.clshnt = yes_or_no(arbitrary_messages[CLUE_QUERY], arbitrary_messages[WAYOUT_CLUE], arbitrary_messages[OK_MAN]);
+        } else {
+            pspeak(OYSTER, hear, true, 1);     // Not really a sound, but oh well.
+        }
     } else if (objects[command.obj].texts[0] == NULL ||
                game.prop[command.obj] == STATE_NOTFOUND) {
         speak(actions[command.verb].message);
     } else
-        pspeak(command.obj, study, game.prop[command.obj], true);
+        pspeak(command.obj, study, true, game.prop[command.obj]);
     return GO_CLEAROBJ;
 }
 
-static int reservoir(void)
+static phase_codes_t reservoir(void)
 /*  Z'ZZZ (word gets recomputed at startup; different each game). */
 {
     if (!AT(RESER) && game.loc != LOC_RESBOTTOM) {
@@ -1123,7 +1155,7 @@ static int reservoir(void)
     }
 }
 
-static int rub(verb_t verb, obj_t obj)
+static phase_codes_t rub(verb_t verb, obj_t obj)
 /* Rub.  Yields various snide remarks except for lit urn. */
 {
     if (obj == URN && game.prop[URN] == URN_LIT) {
@@ -1141,39 +1173,39 @@ static int rub(verb_t verb, obj_t obj)
     return GO_CLEAROBJ;
 }
 
-static int say(command_t command)
+static phase_codes_t say(command_t command)
 /* Say.  Echo WD2. Magic words override. */
 {
     if (command.word[1].type == MOTION &&
-       (command.word[1].id == XYZZY ||
-        command.word[1].id == PLUGH ||
-       command.word[1].id == PLOVER)) {
-           return GO_WORD2;
+        (command.word[1].id == XYZZY ||
+         command.word[1].id == PLUGH ||
+         command.word[1].id == PLOVER)) {
+        return GO_WORD2;
     }
     if (command.word[1].type == ACTION && command.word[1].id == PART)
         return reservoir();
-    
+
     if (command.word[1].type == ACTION &&
         (command.word[1].id == FEE ||
-        command.word[1].id == FIE ||
-        command.word[1].id == FOE ||
-        command.word[1].id == FOO ||
-        command.word[1].id == FUM ||
-        command.word[1].id == PART)) {
+         command.word[1].id == FIE ||
+         command.word[1].id == FOE ||
+         command.word[1].id == FOO ||
+         command.word[1].id == FUM ||
+         command.word[1].id == PART)) {
         return bigwords(command.word[1].id);
     }
     sspeak(OKEY_DOKEY, command.word[1].raw);
     return GO_CLEAROBJ;
 }
 
-static int throw_support(vocab_t spk)
+static phase_codes_t throw_support(vocab_t spk)
 {
     rspeak(spk);
     drop(AXE, game.loc);
     return GO_MOVE;
 }
 
-static int throw (command_t command)
+static phase_codes_t throwit(command_t command)
 /*  Throw.  Same as discard unless axe.  Then same as attack except
  *  ignore bird, and if dwarf is present then one might be killed.
  *  (Only way to do so!)  Axe also special for dragon, bear, and
@@ -1234,7 +1266,7 @@ static int throw (command_t command)
     }
 }
 
-static int wake(verb_t verb, obj_t obj)
+static phase_codes_t wake(verb_t verb, obj_t obj)
 /* Wake.  Only use is to disturb the dwarves. */
 {
     if (obj != DWARF ||
@@ -1247,17 +1279,17 @@ static int wake(verb_t verb, obj_t obj)
     }
 }
 
-static int seed(verb_t verb, const char *arg)
+static phase_codes_t seed(verb_t verb, const char *arg)
 /* Set seed */
 {
-    int seed = atoi(arg);
+    int32_t seed = strtol(arg, NULL, 10);
     speak(actions[verb].message, seed);
     set_seed(seed);
     --game.turns;
     return GO_TOP;
 }
 
-static int waste(verb_t verb, turn_t turns)
+static phase_codes_t waste(verb_t verb, turn_t turns)
 /* Burn turns */
 {
     game.limit -= turns;
@@ -1265,7 +1297,7 @@ static int waste(verb_t verb, turn_t turns)
     return GO_TOP;
 }
 
-static int wave(verb_t verb, obj_t obj)
+static phase_codes_t wave(verb_t verb, obj_t obj)
 /* Wave.  No effect unless waving rod at fissure or at bird. */
 {
     if (obj != ROD ||
@@ -1311,19 +1343,19 @@ static int wave(verb_t verb, obj_t obj)
     }
 }
 
-int action(command_t command)
+phase_codes_t action(command_t command)
 /*  Analyse a verb.  Remember what it was, go back for object if second word
  *  unless verb is "say", which snarfs arbitrary second word.
  */
 {
-    /* Previously, actions that result in a message, but don't do anything 
-     * further were called "specials". Now they're handled here as normal 
+    /* Previously, actions that result in a message, but don't do anything
+     * further were called "specials". Now they're handled here as normal
      * actions. If noaction is true, then we spit out the message and return */
     if (actions[command.verb].noaction) {
         speak(actions[command.verb].message);
         return GO_CLEAROBJ;
     }
-    
+
     if (command.part == unknown) {
         /*  Analyse an object word.  See if the thing is here, whether
          *  we've got a verb yet, and so on.  Object must be here
@@ -1336,8 +1368,8 @@ int action(command_t command)
             /* FALL THROUGH */;
         else if (command.obj == DWARF && atdwrf(game.loc) > 0)
             /* FALL THROUGH */;
-        else if ((LIQUID() == command.obj && HERE(BOTTLE)) ||
-                 command.obj == LIQLOC(game.loc))
+        else if (!game.closed && ((LIQUID() == command.obj && HERE(BOTTLE)) ||
+                                  command.obj == LIQLOC(game.loc)))
             /* FALL THROUGH */;
         else if (command.obj == OIL && HERE(URN) && game.prop[URN] != URN_EMPTY) {
             command.obj = URN;
@@ -1360,8 +1392,6 @@ int action(command_t command)
             return GO_CLEAROBJ;
         }
 
-        if (command.word[1].id != WORD_EMPTY && command.word[1].id != WORD_NOT_FOUND)
-            return GO_WORD2;
         if (command.verb != 0)
             command.part = transitive;
     }
@@ -1371,9 +1401,9 @@ int action(command_t command)
         if (command.word[1].raw[0] != '\0' && command.verb != SAY)
             return GO_WORD2;
         if (command.verb == SAY)
-           /* KEYS is not special, anything not NO_OBJECT or INTRANSITIVE
-            * will do here. We're preventing interpretation as an intransitive
-            * verb when the word is unknown. */
+            /* KEYS is not special, anything not NO_OBJECT or INTRANSITIVE
+             * will do here. We're preventing interpretation as an intransitive
+             * verb when the word is unknown. */
             command.obj = command.word[1].raw[0] != '\0' ? KEYS : NO_OBJECT;
         if (command.obj == NO_OBJECT ||
             command.obj == INTRANSITIVE) {
@@ -1510,7 +1540,7 @@ int action(command_t command)
         case RUB:
             return rub(command.verb, command.obj);
         case THROW:
-            return throw(command);
+            return throwit(command);
         case QUIT: {
             speak(actions[command.verb].message);
             return GO_CLEAROBJ;
@@ -1562,11 +1592,11 @@ int action(command_t command)
             speak(actions[command.verb].message);
             return GO_CLEAROBJ;
         }
-       // LCOV_EXCL_START
-       // This case should never happen - here only as placeholder
+        // LCOV_EXCL_START
+        // This case should never happen - here only as placeholder
         case PART:
             return reservoir();
-       // LCOV_EXCL_STOP
+        // LCOV_EXCL_STOP
         case SEED:
             return seed(command.verb, command.word[1].raw);
         case WASTE: