X-Git-Url: https://jxself.org/git/?p=open-adventure.git;a=blobdiff_plain;f=actions.c;h=5460848f95a93d7c10fa3f80fe82d8032eae251c;hp=07c55e07e95d28867344ec0eac4fa369bf4b681c;hb=e7dc3eab5d7eb798199e59081e846582af7205a7;hpb=665103410a30960695432d09d9300dba5c0b5609 diff --git a/actions.c b/actions.c index 07c55e0..5460848 100644 --- a/actions.c +++ b/actions.c @@ -1,28 +1,30 @@ +/* + * Actions for the duneon-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 #include #include #include "advent.h" #include "dungeon.h" +#include -static int fill(token_t, token_t); +static phase_codes_t fill(verb_t, obj_t); -static void state_change(long obj, long state) -{ - game.prop[obj] = state; - pspeak(obj, change, state, true); -} - -static int attack(struct 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 * enemies, or no enemies but 2 others. */ { - vocab_t verb = command->verb; - vocab_t obj = command->obj; + verb_t verb = command.verb; + obj_t obj = command.obj; - if (obj == NO_OBJECT || - obj == INTRANSITIVE) { + if (obj == INTRANSITIVE) { int changes = 0; if (atdwrf(game.loc) > 0) { obj = DWARF; @@ -49,7 +51,7 @@ static int attack(struct command_t *command) ++changes; } /* check for low-priority targets */ - if (obj == NO_OBJECT) { + if (obj == INTRANSITIVE) { /* Can't attack bird or machine by throwing axe. */ if (HERE(BIRD) && verb != THROW) { obj = BIRD; @@ -82,6 +84,7 @@ static int attack(struct command_t *command) if (obj == VEND) { state_change(VEND, game.prop[VEND] == VEND_BLOCKS ? VEND_UNBLOCKS : VEND_BLOCKS); + return GO_CLEAROBJ; } @@ -108,29 +111,28 @@ static int attack(struct 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()) { - // FIXME: setting wd1 is a workaround for broken logic - command->wd1 = token_to_packed("Y"); - } else { - // FIXME: setting wd1 is a workaround for broken logic - command->wd1 = token_to_packed("N"); - return GO_CHECKFOO; + if (!silent_yes()) { + speak(arbitrary_messages[NASTY_DRAGON]); + return GO_MOVE; } state_change(DRAGON, DRAGON_DEAD); game.prop[RUG] = RUG_FLOOR; - /* FIXME: Arithmetic on location values */ - int k = (objects[DRAGON].plac + objects[DRAGON].fixd) / 2; - move(DRAGON + NOBJECTS, -1); - move(RUG + NOBJECTS, 0); - move(DRAGON, k); - move(RUG, k); - drop(BLOOD, k); - for (obj = 1; obj <= NOBJECTS; obj++) { - if (game.place[obj] == objects[DRAGON].plac || - game.place[obj] == objects[DRAGON].fixd) - move(obj, k); - } - game.loc = k; + /* Hardcoding LOC_SECRET5 as the dragon's death location is ugly. + * The way it was computed before was worse; it depended on the + * two dragon locations being LOC_SECRET4 and LOC_SECRET6 and + * LOC_SECRET5 being right between them. + */ + move(DRAGON + NOBJECTS, IS_FIXED); + move(RUG + NOBJECTS, IS_FREE); + move(DRAGON, LOC_SECRET5); + move(RUG, LOC_SECRET5); + drop(BLOOD, LOC_SECRET5); + for (obj_t i = 1; i <= NOBJECTS; i++) { + if (game.place[i] == objects[DRAGON].plac || + game.place[i] == objects[DRAGON].fixd) + move(i, LOC_SECRET5); + } + game.loc = LOC_SECRET5; return GO_MOVE; } @@ -156,7 +158,7 @@ static int attack(struct command_t *command) } switch (obj) { - case NO_OBJECT: + case INTRANSITIVE: rspeak(NO_TARGET); break; case CLAM: @@ -179,33 +181,27 @@ static int attack(struct command_t *command) rspeak(ROCKY_TROLL); break; default: - rspeak(actions[verb].message); + speak(actions[verb].message); } return GO_CLEAROBJ; } -static int bigwords(token_t foo) +static phase_codes_t 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). */ { - char word[TOKLEN + 1]; - packed_to_token(foo, word); - int k = (int) get_special_vocab_id(word); - if (game.foobar != 1 - k) { - if (game.foobar != 0 && game.loc == LOC_GIANTROOM) { - rspeak( START_OVER); - } else { - rspeak(NOTHING_HAPPENS); - } - return GO_CLEAROBJ; - } else { - game.foobar = k; - if (k != 4) { + 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)) { + game.foobar = id; + if ((id != FOO) && (id != FUM)) { rspeak(OK_MAN); return GO_CLEAROBJ; } - game.foobar = 0; + game.foobar = WORD_EMPTY; if (game.place[EGGS] == objects[EGGS].plac || (TOTING(EGGS) && game.loc == objects[EGGS].plac)) { rspeak(NOTHING_HAPPENS); @@ -215,60 +211,79 @@ static int bigwords(token_t foo) * crossing. */ if (game.place[EGGS] == LOC_NOWHERE && game.place[TROLL] == LOC_NOWHERE && game.prop[TROLL] == TROLL_UNPAID) game.prop[TROLL] = TROLL_PAIDONCE; - k = EGGS_DONE; if (HERE(EGGS)) - k = EGGS_VANISHED; - if (game.loc == objects[EGGS].plac) - k = EGGS_HERE; + pspeak(EGGS, look, true, EGGS_VANISHED); + else if (game.loc == objects[EGGS].plac) + pspeak(EGGS, look, true, EGGS_HERE); + else + pspeak(EGGS, look, true, EGGS_DONE); move(EGGS, objects[EGGS].plac); - pspeak(EGGS, look, k, true); + 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); + } + game.foobar = WORD_EMPTY; + return GO_CLEAROBJ; } } static void blast(void) /* Blast. No effect unless you've got dynamite, which is a neat trick! */ { - if (game.prop[ROD2] < 0 || + if (game.prop[ROD2] == STATE_NOTFOUND || !game.closed) rspeak(REQUIRES_DYNAMITE); else { - game.bonus = VICTORY_MESSAGE; - if (game.loc == LOC_NE) - game.bonus = DEFEAT_MESSAGE; - if (HERE(ROD2)) - game.bonus = SPLATTER_MESSAGE; - rspeak(game.bonus); + if (HERE(ROD2)) { + game.bonus = splatter; + rspeak(SPLATTER_MESSAGE); + } else if (game.loc == LOC_NE) { + game.bonus = defeat; + rspeak(DEFEAT_MESSAGE); + } else { + game.bonus = victory; + rspeak(VICTORY_MESSAGE); + } terminate(endgame); } } -static int vbreak(token_t verb, token_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. */ { - if (obj == MIRROR) { + switch (obj) { + case MIRROR: if (game.closed) { - rspeak(BREAK_MIRROR); + state_change(MIRROR, MIRROR_BROKEN); return GO_DWARFWAKE; } else { rspeak(TOO_FAR); - return GO_CLEAROBJ; + break; } + case VASE: + if (game.prop[VASE] == VASE_WHOLE) { + if (TOTING(VASE)) + drop(VASE, game.loc); + state_change(VASE, VASE_BROKEN); + game.fixed[VASE] = IS_FIXED; + break; + } + /* FALLTHRU */ + default: + speak(actions[verb].message); } - if (obj == VASE && game.prop[VASE] == VASE_WHOLE) { - if (TOTING(VASE)) - drop(VASE, game.loc); - state_change(VASE, VASE_BROKEN); - game.fixed[VASE] = -1; - return GO_CLEAROBJ; - } - rspeak(actions[verb].message); return (GO_CLEAROBJ); } -static int brief(void) -/* Brief. Intransitive only. Suppress long descriptions after first time. */ +static phase_codes_t brief(void) +/* Brief. Intransitive only. Suppress full descriptions after first time. */ { game.abbnum = 10000; game.detail = 3; @@ -276,15 +291,14 @@ static int brief(void) return GO_CLEAROBJ; } -static int vcarry(token_t verb, token_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. */ { - int spk; if (obj == INTRANSITIVE) { /* Carry, no object given yet. OK if only one object present. */ - if (game.atloc[game.loc] == 0 || + if (game.atloc[game.loc] == NO_OBJECT || game.link[game.atloc[game.loc]] != 0 || atdwrf(game.loc) > 0) return GO_UNKNOWN; @@ -292,92 +306,109 @@ static int vcarry(token_t verb, token_t obj) } if (TOTING(obj)) { - rspeak(ALREADY_CARRYING); + speak(actions[verb].message); return GO_CLEAROBJ; } - spk = YOU_JOKING; - if (obj == PLANT && game.prop[PLANT] <= 0) - spk = DEEP_ROOTS; - if (obj == BEAR && game.prop[BEAR] == SITTING_BEAR) - spk = BEAR_CHAINED; - if (obj == CHAIN && game.prop[BEAR] != UNTAMED_BEAR) - spk = STILL_LOCKED; - if (obj == URN) - spk = URN_NOBUDGE; - if (obj == CAVITY) - spk = DOUGHNUT_HOLES; - if (obj == BLOOD) - spk = FEW_DROPS; - if (obj == RUG && game.prop[RUG] == RUG_HOVER) - spk = RUG_HOVERS; - if (obj == SIGN) - spk = HAND_PASSTHROUGH; + if (obj == MESSAG) { rspeak(REMOVE_MESSAGE); DESTROY(MESSAG); return GO_CLEAROBJ; } - if (game.fixed[obj] != 0) { - rspeak(spk); + + if (game.fixed[obj] != IS_FREE) { + switch (obj) { + case PLANT: + /* Next guard tests whether plant is tiny or stashed */ + rspeak(game.prop[PLANT] <= PLANT_THIRSTY ? DEEP_ROOTS : YOU_JOKING); + break; + case BEAR: + rspeak( game.prop[BEAR] == SITTING_BEAR ? BEAR_CHAINED : YOU_JOKING); + break; + case CHAIN: + rspeak( game.prop[BEAR] != UNTAMED_BEAR ? STILL_LOCKED : YOU_JOKING); + break; + case RUG: + rspeak(game.prop[RUG] == RUG_HOVER ? RUG_HOVERS : YOU_JOKING); + break; + case URN: + rspeak(URN_NOBUDGE); + break; + case CAVITY: + rspeak(DOUGHNUT_HOLES); + break; + case BLOOD: + rspeak(FEW_DROPS); + break; + case SIGN: + rspeak(HAND_PASSTHROUGH); + break; + default: + rspeak(YOU_JOKING); + } return GO_CLEAROBJ; } + if (obj == WATER || obj == OIL) { if (!HERE(BOTTLE) || LIQUID() != obj) { - if (TOTING(BOTTLE) && game.prop[BOTTLE] == EMPTY_BOTTLE) - return (fill(verb, BOTTLE)); - else { - if (game.prop[BOTTLE] != EMPTY_BOTTLE) - spk = BOTTLE_FULL; - if (!TOTING(BOTTLE)) - spk = NO_CONTAINER; - rspeak(spk); + if (!TOTING(BOTTLE)) { + rspeak(NO_CONTAINER); return GO_CLEAROBJ; } + if (game.prop[BOTTLE] == EMPTY_BOTTLE) { + return (fill(verb, BOTTLE)); + } else + rspeak(BOTTLE_FULL); + return GO_CLEAROBJ; } obj = BOTTLE; } - spk = CARRY_LIMIT; if (game.holdng >= INVLIMIT) { - rspeak(spk); + rspeak(CARRY_LIMIT); return GO_CLEAROBJ; - } else if (obj == BIRD && game.prop[BIRD] != BIRD_CAGED && -1 - game.prop[BIRD] != BIRD_CAGED) { + + } + + if (obj == BIRD && game.prop[BIRD] != BIRD_CAGED && STASHED(BIRD) != BIRD_CAGED) { if (game.prop[BIRD] == BIRD_FOREST_UNCAGED) { DESTROY(BIRD); rspeak(BIRD_CRAP); return GO_CLEAROBJ; } - if (!TOTING(CAGE)) - spk = CANNOT_CARRY; - if (TOTING(ROD)) - spk = BIRD_EVADES; - if (spk == CANNOT_CARRY || - spk == BIRD_EVADES) { - rspeak(spk); + if (!TOTING(CAGE)) { + rspeak(CANNOT_CARRY); + return GO_CLEAROBJ; + } + if (TOTING(ROD)) { + rspeak(BIRD_EVADES); return GO_CLEAROBJ; } game.prop[BIRD] = BIRD_CAGED; } - /* FIXME: Arithmetic on state numbers */ if ((obj == BIRD || obj == CAGE) && - (game.prop[BIRD] == BIRD_CAGED || - -1 - game.prop[BIRD] == 1)) + (game.prop[BIRD] == BIRD_CAGED || STASHED(BIRD) == BIRD_CAGED)) { + /* expression maps BIRD to CAGE and CAGE to BIRD */ carry(BIRD + CAGE - obj, game.loc); + } + carry(obj, game.loc); - if (obj == BOTTLE && LIQUID() != 0) + + if (obj == BOTTLE && LIQUID() != NO_OBJECT) game.place[LIQUID()] = CARRIED; - if (GSTONE(obj) && game.prop[obj] != 0) { - game.prop[obj] = STATE_GROUND; + + if (GSTONE(obj) && game.prop[obj] != STATE_FOUND) { + game.prop[obj] = STATE_FOUND; game.prop[CAVITY] = CAVITY_EMPTY; } rspeak(OK_MAN); return GO_CLEAROBJ; } -static int chain(token_t verb) +static int chain(verb_t verb) /* Do something to the bear's chain */ { if (verb != LOCK) { @@ -390,16 +421,21 @@ static int chain(token_t verb) return GO_CLEAROBJ; } game.prop[CHAIN] = CHAIN_HEAP; - game.fixed[CHAIN] = CHAIN_HEAP; + game.fixed[CHAIN] = IS_FREE; if (game.prop[BEAR] != BEAR_DEAD) game.prop[BEAR] = CONTENTED_BEAR; switch (game.prop[BEAR]) { + // LCOV_EXCL_START case BEAR_DEAD: - game.fixed[BEAR] = -1; + /* Can't be reached until the bear can die in some way other + * than a bridge collapse. Leave in in case this changes, but + * exclude from coverage testing. */ + game.fixed[BEAR] = IS_FIXED; break; + // LCOV_EXCL_STOP default: - game.fixed[BEAR] = 0; + game.fixed[BEAR] = IS_FREE; } rspeak(CHAIN_UNLOCKED); return GO_CLEAROBJ; @@ -418,103 +454,121 @@ static int chain(token_t verb) if (TOTING(CHAIN)) drop(CHAIN, game.loc); - game.fixed[CHAIN] = -1; + game.fixed[CHAIN] = IS_FIXED; rspeak(CHAIN_LOCKED); return GO_CLEAROBJ; } -static int discard(token_t verb, token_t obj, bool just_do_it) +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. */ { - int spk = actions[verb].message; - if (!just_do_it) { - if (TOTING(ROD2) && obj == ROD && !TOTING(ROD)) - obj = ROD2; - if (!TOTING(obj)) { - rspeak(spk); + if (obj == ROD && !TOTING(ROD) && TOTING(ROD2)) { + obj = ROD2; + } + + if (!TOTING(obj)) { + speak(actions[verb].message); + return GO_CLEAROBJ; + } + + if (GSTONE(obj) && AT(CAVITY) && game.prop[CAVITY] != CAVITY_FULL) { + rspeak(GEM_FITS); + game.prop[obj] = STATE_IN_CAVITY; + game.prop[CAVITY] = CAVITY_FULL; + if (HERE(RUG) && ((obj == EMERALD && game.prop[RUG] != RUG_HOVER) || + (obj == RUBY && game.prop[RUG] == RUG_HOVER))) { + if (obj == RUBY) + rspeak(RUG_SETTLES); + else if (TOTING(RUG)) + rspeak(RUG_WIGGLES); + else + rspeak(RUG_RISES); + if (!TOTING(RUG) || obj == RUBY) { + int k = (game.prop[RUG] == RUG_HOVER) ? RUG_FLOOR : RUG_HOVER; + game.prop[RUG] = k; + if (k == RUG_HOVER) + k = objects[SAPPH].plac; + move(RUG + NOBJECTS, k); + } + } + drop(obj, game.loc); + return GO_CLEAROBJ; + } + + if (obj == COINS && HERE(VEND)) { + DESTROY(COINS); + drop(BATTERY, game.loc); + pspeak(BATTERY, look, true, FRESH_BATTERIES); + return GO_CLEAROBJ; + } + + if (LIQUID() == obj) + obj = BOTTLE; + if (obj == BOTTLE && LIQUID() != NO_OBJECT) { + game.place[LIQUID()] = LOC_NOWHERE; + } + + if (obj == BEAR && AT(TROLL)) { + state_change(TROLL, TROLL_GONE); + move(TROLL, LOC_NOWHERE); + move(TROLL + NOBJECTS, IS_FREE); + move(TROLL2, objects[TROLL].plac); + move(TROLL2 + NOBJECTS, objects[TROLL].fixd); + juggle(CHASM); + drop(obj, game.loc); + return GO_CLEAROBJ; + } + + if (obj == VASE) { + if (game.loc != objects[PILLOW].plac) { + state_change(VASE, AT(PILLOW) + ? VASE_WHOLE + : VASE_DROPPED); + if (game.prop[VASE] != VASE_WHOLE) + game.fixed[VASE] = IS_FIXED; + drop(obj, game.loc); + return GO_CLEAROBJ; + } + } + + if (obj == CAGE && game.prop[BIRD] == BIRD_CAGED) { + drop(BIRD, game.loc); + } + + if (obj == BIRD) { + if (AT(DRAGON) && game.prop[DRAGON] == DRAGON_BARS) { + rspeak(BIRD_BURNT); + DESTROY(BIRD); return GO_CLEAROBJ; } - if (obj == BIRD && HERE(SNAKE)) { + if (HERE(SNAKE)) { rspeak(BIRD_ATTACKS); if (game.closed) return GO_DWARFWAKE; DESTROY(SNAKE); /* Set game.prop for use by travel options */ game.prop[SNAKE] = SNAKE_CHASED; - - } else if ((GSTONE(obj) && AT(CAVITY) && game.prop[CAVITY] != CAVITY_FULL)) { - rspeak(GEM_FITS); - game.prop[obj] = 1; - game.prop[CAVITY] = CAVITY_FULL; - if (HERE(RUG) && ((obj == EMERALD && game.prop[RUG] != RUG_HOVER) || - (obj == RUBY && game.prop[RUG] == RUG_HOVER))) { - spk = RUG_RISES; - if (TOTING(RUG)) - spk = RUG_WIGGLES; - if (obj == RUBY) - spk = RUG_SETTLES; - rspeak(spk); - if (spk != RUG_WIGGLES) { - /* FIXME: Arithmetic on state numbers */ - int k = 2 - game.prop[RUG]; - game.prop[RUG] = k; - if (k == 2) - k = objects[SAPPH].plac; - move(RUG + NOBJECTS, k); - } - } - } else if (obj == COINS && HERE(VEND)) { - DESTROY(COINS); - drop(BATTERY, game.loc); - pspeak(BATTERY, look, FRESH_BATTERIES, true); - return GO_CLEAROBJ; - } else if (obj == BIRD && AT(DRAGON) && game.prop[DRAGON] == DRAGON_BARS) { - rspeak(BIRD_BURNT); - DESTROY(BIRD); - return GO_CLEAROBJ; - } else if (obj == BEAR && AT(TROLL)) { - state_change(TROLL, TROLL_GONE); - move(TROLL, LOC_NOWHERE); - move(TROLL + NOBJECTS, LOC_NOWHERE); - move(TROLL2, objects[TROLL].plac); - move(TROLL2 + NOBJECTS, objects[TROLL].fixd); - juggle(CHASM); - } else if (obj != VASE || - game.loc == objects[PILLOW].plac) { + } else rspeak(OK_MAN); - } else { - game.prop[VASE] = VASE_BROKEN; - if (AT(PILLOW)) - game.prop[VASE] = VASE_WHOLE; - pspeak(VASE, look, game.prop[VASE] + 1, true); - if (game.prop[VASE] != VASE_WHOLE) - game.fixed[VASE] = -1; - } + + game.prop[BIRD] = FOREST(game.loc) ? BIRD_FOREST_UNCAGED : BIRD_UNCAGED; + drop(obj, game.loc); + return GO_CLEAROBJ; } - int k = LIQUID(); - if (k == obj) - obj = BOTTLE; - if (obj == BOTTLE && k != 0) - game.place[k] = LOC_NOWHERE; - if (obj == CAGE && game.prop[BIRD] == BIRD_CAGED) - drop(BIRD, game.loc); + + rspeak(OK_MAN); drop(obj, game.loc); - if (obj != BIRD) - return GO_CLEAROBJ; - game.prop[BIRD] = BIRD_UNCAGED; - if (FOREST(game.loc)) - game.prop[BIRD] = BIRD_FOREST_UNCAGED; return GO_CLEAROBJ; } -static int drink(token_t verb, token_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. */ { - if (obj == NO_OBJECT && LIQLOC(game.loc) != WATER && + if (obj == INTRANSITIVE && LIQLOC(game.loc) != WATER && (LIQUID() != WATER || !HERE(BOTTLE))) { return GO_UNKNOWN; } @@ -526,54 +580,51 @@ static int drink(token_t verb, token_t obj) return GO_CLEAROBJ; } - if (obj != NO_OBJECT && obj != WATER) { + if (obj != INTRANSITIVE && obj != WATER) { rspeak(RIDICULOUS_ATTEMPT); return GO_CLEAROBJ; } if (LIQUID() == WATER && HERE(BOTTLE)) { - game.prop[BOTTLE] = EMPTY_BOTTLE; game.place[WATER] = LOC_NOWHERE; - rspeak(BOTTLE_EMPTY); + state_change(BOTTLE, EMPTY_BOTTLE); return GO_CLEAROBJ; } - rspeak(actions[verb].message); + speak(actions[verb].message); return GO_CLEAROBJ; } -static int eat(token_t verb, token_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. */ { - if (obj == INTRANSITIVE) { + switch (obj) { + case INTRANSITIVE: if (!HERE(FOOD)) return GO_UNKNOWN; + /* FALLTHRU */ + case FOOD: DESTROY(FOOD); rspeak(THANKS_DELICIOUS); - return GO_CLEAROBJ; - } - if (obj == FOOD) { - DESTROY(FOOD); - rspeak(THANKS_DELICIOUS); - return GO_CLEAROBJ; - } - if (obj == BIRD || - obj == SNAKE || - obj == CLAM || - obj == OYSTER || - obj == DWARF || - obj == DRAGON || - obj == TROLL || - obj == BEAR || - obj == OGRE) { + break; + case BIRD: + case SNAKE: + case CLAM: + case OYSTER: + case DWARF: + case DRAGON: + case TROLL: + case BEAR: + case OGRE: rspeak(LOST_APPETITE); - return GO_CLEAROBJ; + break; + default: + speak(actions[verb].message); } - rspeak(actions[verb].message); return GO_CLEAROBJ; } -static int extinguish(token_t verb, int obj) +static phase_codes_t extinguish(verb_t verb, obj_t obj) /* Extinguish. Lamp, urn, dragon/volcano (nice try). */ { if (obj == INTRANSITIVE) { @@ -585,81 +636,91 @@ static int extinguish(token_t verb, int obj) return GO_UNKNOWN; } - if (obj == URN) { + switch (obj) { + case URN: if (game.prop[URN] != URN_EMPTY) { state_change(URN, URN_DARK); } else { - pspeak(URN, change, URN_DARK, true); + pspeak(URN, change, true, URN_DARK); } - return GO_CLEAROBJ; - } - - if (obj == LAMP) { + break; + case LAMP: state_change(LAMP, LAMP_DARK); rspeak(DARK(game.loc) ? PITCH_DARK : NO_MESSAGE); - return GO_CLEAROBJ; - } - - if (obj == DRAGON || - obj == VOLCANO) { + break; + case DRAGON: + case VOLCANO: rspeak(BEYOND_POWER); - return GO_CLEAROBJ; + break; + default: + speak(actions[verb].message); } - - rspeak(actions[verb].message); return GO_CLEAROBJ; } -static int feed(token_t verb, token_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. */ { - int spk = actions[verb].message; - if (obj == BIRD) { + switch (obj) { + case BIRD: rspeak(BIRD_PINING); - return GO_CLEAROBJ; - } else if (obj == SNAKE || - obj == DRAGON || - obj == TROLL) { - spk = NOTHING_EDIBLE; - if (obj == DRAGON && game.prop[DRAGON] != DRAGON_BARS) - spk = RIDICULOUS_ATTEMPT; - if (obj == TROLL) - spk = TROLL_VICES; - if (obj == SNAKE && !game.closed && HERE(BIRD)) { + break; + case DRAGON: + if (game.prop[DRAGON] != DRAGON_BARS) + rspeak(RIDICULOUS_ATTEMPT); + else + rspeak(NOTHING_EDIBLE); + break; + case SNAKE: + if (!game.closed && HERE(BIRD)) { DESTROY(BIRD); - spk = BIRD_DEVOURED; - } - } else if (obj == DWARF) { + rspeak(BIRD_DEVOURED); + } else + rspeak(NOTHING_EDIBLE); + break; + case TROLL: + rspeak(TROLL_VICES); + break; + case DWARF: if (HERE(FOOD)) { game.dflag += 2; - spk = REALLY_MAD; + rspeak(REALLY_MAD); + } else + speak(actions[verb].message); + break; + case BEAR: + if (game.prop[BEAR] == BEAR_DEAD) { + rspeak(RIDICULOUS_ATTEMPT); + break; } - } else if (obj == BEAR) { - if (game.prop[BEAR] == UNTAMED_BEAR) - spk = NOTHING_EDIBLE; - if (game.prop[BEAR] == BEAR_DEAD) - spk = RIDICULOUS_ATTEMPT; - if (HERE(FOOD)) { - DESTROY(FOOD); - game.prop[BEAR] = SITTING_BEAR; - game.fixed[AXE] = 0; - game.prop[AXE] = 0; - spk = BEAR_TAMED; + if (game.prop[BEAR] == UNTAMED_BEAR) { + if (HERE(FOOD)) { + DESTROY(FOOD); + game.fixed[AXE] = IS_FREE; + game.prop[AXE] = AXE_HERE; + state_change(BEAR, SITTING_BEAR); + } else + rspeak(NOTHING_EDIBLE); + break; } - } else if (obj == OGRE) { + speak(actions[verb].message); + break; + case OGRE: if (HERE(FOOD)) - spk = OGRE_FULL; - } else { - spk = AM_GAME; + rspeak(OGRE_FULL); + else + speak(actions[verb].message); + break; + default: + rspeak(AM_GAME); } - rspeak(spk); return GO_CLEAROBJ; } -int fill(token_t verb, token_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.) */ { @@ -674,8 +735,9 @@ int fill(token_t verb, token_t obj) } rspeak(SHATTER_VASE); game.prop[VASE] = VASE_BROKEN; - game.fixed[VASE] = -1; - return (discard(verb, obj, true)); + game.fixed[VASE] = IS_FIXED; + drop(VASE, game.loc); + return GO_CLEAROBJ; } if (obj == URN) { @@ -706,11 +768,11 @@ int fill(token_t verb, token_t obj) game.place[k] = LOC_NOWHERE; return GO_CLEAROBJ; } - if (obj != NO_OBJECT && obj != BOTTLE) { - rspeak(actions[verb].message); + if (obj != INTRANSITIVE && obj != BOTTLE) { + speak(actions[verb].message); return GO_CLEAROBJ; } - if (obj == NO_OBJECT && !HERE(BOTTLE)) + if (obj == INTRANSITIVE && !HERE(BOTTLE)) return GO_UNKNOWN; if (HERE(URN) && game.prop[URN] != URN_EMPTY) { @@ -726,17 +788,15 @@ int fill(token_t verb, token_t obj) return GO_CLEAROBJ; } - game.prop[BOTTLE] = (LIQLOC(game.loc) == OIL) ? OIL_BOTTLE : WATER_BOTTLE; + state_change(BOTTLE, (LIQLOC(game.loc) == OIL) + ? OIL_BOTTLE + : WATER_BOTTLE); if (TOTING(BOTTLE)) game.place[LIQUID()] = CARRIED; - if (LIQUID() == OIL) - rspeak(BOTTLED_OIL); - else - rspeak(BOTTLED_WATER); return GO_CLEAROBJ; } -static int find(token_t verb, token_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)) { @@ -758,11 +818,11 @@ static int find(token_t verb, token_t obj) } - rspeak(actions[verb].message); + speak(actions[verb].message); return GO_CLEAROBJ; } -static int fly(token_t verb, token_t obj) +static phase_codes_t fly(verb_t verb, obj_t obj) /* Fly. Snide remarks unless hovering rug is here. */ { if (obj == INTRANSITIVE) { @@ -778,7 +838,7 @@ static int fly(token_t verb, token_t obj) } if (obj != RUG) { - rspeak(actions[verb].message); + speak(actions[verb].message); return GO_CLEAROBJ; } if (game.prop[RUG] != RUG_HOVER) { @@ -787,22 +847,22 @@ static int fly(token_t verb, token_t obj) } game.oldlc2 = game.oldloc; game.oldloc = game.loc; - /* FIXME: Arithmetic on location values */ - game.newloc = game.place[RUG] + game.fixed[RUG] - game.loc; - if (game.prop[SAPPH] >= 0) { - rspeak(RUG_RETURNS); - } else { + if (game.prop[SAPPH] == STATE_NOTFOUND) { + game.newloc = game.place[SAPPH]; rspeak(RUG_GOES); + } else { + game.newloc = LOC_CLIFF; + rspeak(RUG_RETURNS); } 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; - for (int i = 1; i <= NOBJECTS; i++) { + for (obj_t i = 1; i <= NOBJECTS; i++) { if (i == BEAR || !TOTING(i)) continue; @@ -810,7 +870,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); @@ -819,65 +879,70 @@ static int inven(void) return GO_CLEAROBJ; } -static int light(token_t verb, token_t obj) +static phase_codes_t light(verb_t verb, obj_t obj) /* Light. Applicable only to lamp and urn. */ { if (obj == INTRANSITIVE) { - if (HERE(LAMP) && game.prop[LAMP] == LAMP_DARK && game.limit >= 0) + int selects = 0; + if (HERE(LAMP) && game.prop[LAMP] == LAMP_DARK && game.limit >= 0) { obj = LAMP; - if (HERE(URN) && game.prop[URN] == URN_DARK) + selects++; + } + if (HERE(URN) && game.prop[URN] == URN_DARK) { obj = URN; - if (obj == INTRANSITIVE || - (HERE(LAMP) && game.prop[LAMP] == LAMP_DARK && game.limit >= 0 && - HERE(URN) && game.prop[URN] == URN_DARK)) + selects++; + } + if (selects != 1) return GO_UNKNOWN; } - if (obj == URN) { + switch (obj) { + case URN: state_change(URN, game.prop[URN] == URN_EMPTY ? URN_EMPTY : URN_LIT); - return GO_CLEAROBJ; - } else { - if (obj != LAMP) { - rspeak(actions[verb].message); - return GO_CLEAROBJ; - } + break; + case LAMP: if (game.limit < 0) { rspeak(LAMP_OUT); - return GO_CLEAROBJ; + break; } state_change(LAMP, LAMP_BRIGHT); if (game.wzdark) return GO_TOP; - else - return GO_CLEAROBJ; + break; + default: + speak(actions[verb].message); } + return GO_CLEAROBJ; } -static int listen(void) -/* Listen. Intransitive only. Print stuff based on objsnd/locsnd. */ +static phase_codes_t listen(void) +/* Listen. Intransitive only. Print stuff based on object sound proprties. */ { - long sound = locations[game.loc].sound; + vocab_t sound = locations[game.loc].sound; if (sound != SILENT) { rspeak(sound); if (!locations[game.loc].loud) rspeak(NO_MESSAGE); return GO_CLEAROBJ; } - for (int i = 1; i <= NOBJECTS; i++) { + for (obj_t i = 1; i <= NOBJECTS; i++) { if (!HERE(i) || objects[i].sounds[0] == NULL || game.prop[i] < 0) continue; int mi = game.prop[i]; + /* (ESR) Some unpleasant magic on object states here. Ideally + * we'd have liked the bird to be a normal object that we can + * use state_change() on; can't do it, because there are + * actually two different series of per-state birdsounds + * depending on whether player has drunk dragon's blood. */ if (i == BIRD) mi += 3 * game.blooded; - long packed_zzword = token_to_packed(game.zzword); - pspeak(i, hear, mi, true, packed_zzword); + pspeak(i, hear, true, mi, game.zzword); rspeak(NO_MESSAGE); - /* FIXME: Magic number, sensitive to bird state logic */ - if (i == BIRD && game.prop[i] == 5) + if (i == BIRD && mi == BIRD_ENDSTATE) DESTROY(BIRD); return GO_CLEAROBJ; } @@ -885,7 +950,7 @@ static int listen(void) return GO_CLEAROBJ; } -static int lock(token_t verb, token_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) { @@ -907,48 +972,51 @@ static int lock(token_t verb, token_t obj) /* Lock, unlock object. Special stuff for opening clam/oyster * and for chain. */ - if (obj == GRATE || - obj == CHAIN) { + + switch (obj) { + case CHAIN: + if (HERE(KEYS)) { + return chain(verb); + } else + rspeak(NO_KEYS); + break; + case GRATE: if (HERE(KEYS)) { - if (obj == CHAIN) - return chain(verb); if (game.closng) { rspeak(EXIT_CLOSED); if (!game.panic) game.clock2 = PANICTIME; game.panic = true; - return GO_CLEAROBJ ; } else { state_change(GRATE, (verb == LOCK) ? GRATE_CLOSED : GRATE_OPEN); - return GO_CLEAROBJ; } - } - rspeak(NO_KEYS); - return GO_CLEAROBJ; - } - - switch (obj) { + } else + rspeak(NO_KEYS); + break; case CLAM: if (verb == LOCK) rspeak(HUH_MAN); else if (!TOTING(TRIDENT)) - rspeak(OYSTER_OPENER); + rspeak(CLAM_OPENER); else { DESTROY(CLAM); drop(OYSTER, game.loc); drop(PEARL, LOC_CULDESAC); rspeak(PEARL_FALLS); } - return GO_CLEAROBJ; + break; case OYSTER: if (verb == LOCK) rspeak(HUH_MAN); - else + else if (TOTING(OYSTER)) + rspeak(DROP_OYSTER); + else if (!TOTING(TRIDENT)) rspeak(OYSTER_OPENER); - - return GO_CLEAROBJ; + else + rspeak(OYSTER_OPENS); + break; case DOOR: rspeak((game.prop[DOOR] == DOOR_UNRUSTED) ? OK_MAN : RUSTY_DOOR); break; @@ -959,23 +1027,23 @@ static int lock(token_t verb, token_t obj) rspeak(CANNOT_UNLOCK); break; default: - rspeak(actions[verb].message); + speak(actions[verb].message); } return GO_CLEAROBJ; } -static int pour(token_t verb, token_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. */ { if (obj == BOTTLE || - obj == NO_OBJECT) + obj == INTRANSITIVE) obj = LIQUID(); if (obj == NO_OBJECT) return GO_UNKNOWN; if (!TOTING(obj)) { - rspeak(actions[verb].message); + speak(actions[verb].message); return GO_CLEAROBJ; } @@ -1010,7 +1078,7 @@ static int pour(token_t verb, token_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])) @@ -1018,38 +1086,37 @@ static int quit(void) return GO_CLEAROBJ; } -static int read(struct 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) { - command.obj = 0; + command.obj = NO_OBJECT; for (int i = 1; i <= NOBJECTS; i++) { if (HERE(i) && objects[i].texts[0] != NULL && game.prop[i] >= 0) command.obj = command.obj * NOBJECTS + i; } if (command.obj > NOBJECTS || - command.obj == 0 || + command.obj == NO_OBJECT || DARK(game.loc)) return GO_UNKNOWN; } if (DARK(game.loc)) { - sspeak(NO_SEE, command.raw1); + 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 (objects[command.obj].texts[0] == NULL || - game.prop[command.obj] < 0) { - rspeak(actions[command.verb].message); + 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). */ { - /* FIXME: Arithmetic on state numbers */ - if (!AT(RESER) && game.loc != game.fixed[RESER] - 1) { + if (!AT(RESER) && game.loc != LOC_RESBOTTOM) { rspeak(NOTHING_HAPPENS); return GO_CLEAROBJ; } else { @@ -1066,7 +1133,7 @@ static int reservoir(void) } } -static int rub(token_t verb, token_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) { @@ -1079,96 +1146,95 @@ static int rub(token_t verb, token_t obj) } else if (obj != LAMP) { rspeak(PECULIAR_NOTHING); } else { - rspeak(actions[verb].message); + speak(actions[verb].message); } return GO_CLEAROBJ; } -static int say(struct command_t *command) -/* Say. Echo WD2 (or WD1 if no WD2 (SAY WHAT?, etc.).) Magic words override. */ +static phase_codes_t say(command_t command) +/* Say. Echo WD2. Magic words override. */ { - if (command->wd2 > 0) { - command->wd1 = command->wd2; - strcpy(command->raw1, command->raw2); - } - char word1[TOKLEN + 1]; - packed_to_token(command->wd1, word1); - int wd = (int) get_vocab_id(word1); - /* FIXME: magic numbers */ - if (wd == MOTION_WORD(XYZZY) || - wd == MOTION_WORD(PLUGH) || - wd == MOTION_WORD(PLOVER) || - wd == ACTION_WORD(GIANTWORDS) || - wd == ACTION_WORD(PART)) { - /* FIXME: scribbles on the interpreter's command block */ - wordclear(&command->wd2); - return GO_LOOKUP; - } - sspeak(OKEY_DOKEY, command->raw1); + if (command.word[1].type == MOTION && + (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)) { + return bigwords(command.word[1].id); + } + sspeak(OKEY_DOKEY, command.word[1].raw); return GO_CLEAROBJ; } -static int throw_support(long spk) +static phase_codes_t throw_support(vocab_t spk) { rspeak(spk); drop(AXE, game.loc); return GO_MOVE; } -static int throw (struct command_t *command) +static phase_codes_t throw (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 * troll. Treasures special for troll. */ { - if (TOTING(ROD2) && command->obj == ROD && !TOTING(ROD)) - command->obj = ROD2; - if (!TOTING(command->obj)) { - rspeak(actions[command->verb].message); + if (!TOTING(command.obj)) { + speak(actions[command.verb].message); return GO_CLEAROBJ; } - if (objects[command->obj].is_treasure && AT(TROLL)) { + if (objects[command.obj].is_treasure && AT(TROLL)) { /* Snarf a treasure for the troll. */ - drop(command->obj, LOC_NOWHERE); + drop(command.obj, LOC_NOWHERE); move(TROLL, LOC_NOWHERE); - move(TROLL + NOBJECTS, LOC_NOWHERE); + move(TROLL + NOBJECTS, IS_FREE); drop(TROLL2, objects[TROLL].plac); drop(TROLL2 + NOBJECTS, objects[TROLL].fixd); juggle(CHASM); rspeak(TROLL_SATISFIED); return GO_CLEAROBJ; } - if (command->obj == FOOD && HERE(BEAR)) { + if (command.obj == FOOD && HERE(BEAR)) { /* But throwing food is another story. */ - command->obj = BEAR; - return (feed(command->verb, command->obj)); + command.obj = BEAR; + return (feed(command.verb, command.obj)); } - if (command->obj != AXE) - return (discard(command->verb, command->obj, false)); + if (command.obj != AXE) + return (discard(command.verb, command.obj)); else { if (atdwrf(game.loc) <= 0) { if (AT(DRAGON) && game.prop[DRAGON] == DRAGON_BARS) return throw_support(DRAGON_SCALES); if (AT(TROLL)) return throw_support(TROLL_RETURNS); - else if (AT(OGRE)) + if (AT(OGRE)) return throw_support(OGRE_DODGE); - else if (HERE(BEAR) && game.prop[BEAR] == UNTAMED_BEAR) { + if (HERE(BEAR) && game.prop[BEAR] == UNTAMED_BEAR) { /* This'll teach him to throw the axe at the bear! */ drop(AXE, game.loc); - game.fixed[AXE] = -1; + game.fixed[AXE] = IS_FIXED; juggle(BEAR); state_change(AXE, AXE_LOST); return GO_CLEAROBJ; } - command->obj = NO_OBJECT; + command.obj = INTRANSITIVE; return (attack(command)); } if (randrange(NDWARVES + 1) < game.dflag) { return throw_support(DWARF_DODGES); } else { - long i = atdwrf(game.loc); + int i = atdwrf(game.loc); game.dseen[i] = false; game.dloc[i] = LOC_NOWHERE; return throw_support((++game.dkill == 1) ? @@ -1178,12 +1244,12 @@ static int throw (struct command_t *command) } } -static int wake(token_t verb, token_t obj) +static phase_codes_t wake(verb_t verb, obj_t obj) /* Wake. Only use is to disturb the dwarves. */ { if (obj != DWARF || !game.closed) { - rspeak(actions[verb].message); + speak(actions[verb].message); return GO_CLEAROBJ; } else { rspeak(PROD_DWARF); @@ -1191,7 +1257,25 @@ static int wake(token_t verb, token_t obj) } } -static int wave(token_t verb, token_t obj) +static phase_codes_t seed(verb_t verb, const char *arg) +/* Set seed */ +{ + int32_t seed = strtol(arg, NULL, 10); + speak(actions[verb].message, seed); + set_seed(seed); + --game.turns; + return GO_TOP; +} + +static phase_codes_t waste(verb_t verb, turn_t turns) +/* Burn turns */ +{ + game.limit -= turns; + speak(actions[verb].message, (int)game.limit); + return GO_TOP; +} + +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 || @@ -1199,16 +1283,16 @@ static int wave(token_t verb, token_t obj) (!HERE(BIRD) && (game.closng || !AT(FISSURE)))) { - rspeak(((!TOTING(obj)) && (obj != ROD || - !TOTING(ROD2))) ? - ARENT_CARRYING : - actions[verb].message); + speak(((!TOTING(obj)) && (obj != ROD || + !TOTING(ROD2))) ? + arbitrary_messages[ARENT_CARRYING] : + actions[verb].message); return GO_CLEAROBJ; } - if (game.prop[BIRD] == BIRD_UNCAGED && game.loc == game.place[STEPS] && game.prop[JADE] < 0) { + if (game.prop[BIRD] == BIRD_UNCAGED && game.loc == game.place[STEPS] && game.prop[JADE] == STATE_NOTFOUND) { drop(JADE, game.loc); - game.prop[JADE] = 0; + game.prop[JADE] = STATE_FOUND; --game.tally; rspeak(NECKLACE_FLY); return GO_CLEAROBJ; @@ -1237,12 +1321,20 @@ static int wave(token_t verb, token_t obj) } } -int action(struct 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. */ { - if (command->part == unknown) { + /* 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 * unless verb is "find" or "invent(ory)" (and no new verb @@ -1250,97 +1342,86 @@ int action(struct command_t *command) * they are never actually dropped at any location, but might * be here inside the bottle or urn or as a feature of the * location. */ - if (HERE(command->obj)) + if (HERE(command.obj)) /* FALL THROUGH */; - else if (command->obj == GRATE) { - if (game.loc == LOC_START || - game.loc == LOC_VALLEY || - game.loc == LOC_SLIT) { - command->obj = DPRSSN; - } - if (game.loc == LOC_COBBLE || - game.loc == LOC_DEBRIS || - game.loc == LOC_AWKWARD || - game.loc == LOC_BIRD || - game.loc == LOC_PITTOP) { - command->obj = ENTRNC; - } - } else if (command->obj == DWARF && atdwrf(game.loc) > 0) + 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 ((LIQUID() == command.obj && HERE(BOTTLE)) || + command.obj == LIQLOC(game.loc)) /* FALL THROUGH */; - else if (command->obj == OIL && HERE(URN) && game.prop[URN] != 0) { - command->obj = URN; + else if (command.obj == OIL && HERE(URN) && game.prop[URN] != URN_EMPTY) { + command.obj = URN; /* FALL THROUGH */; - } else if (command->obj == PLANT && AT(PLANT2) && game.prop[PLANT2] != 0) { - command->obj = PLANT2; + } else if (command.obj == PLANT && AT(PLANT2) && game.prop[PLANT2] != PLANT_THIRSTY) { + command.obj = PLANT2; /* FALL THROUGH */; - } else if (command->obj == KNIFE && game.knfloc == game.loc) { + } else if (command.obj == KNIFE && game.knfloc == game.loc) { game.knfloc = -1; rspeak(KNIVES_VANISH); return GO_CLEAROBJ; - } else if (command->obj == ROD && HERE(ROD2)) { - command->obj = ROD2; + } else if (command.obj == ROD && HERE(ROD2)) { + command.obj = ROD2; /* FALL THROUGH */; - } else if ((command->verb == FIND || - command->verb == INVENTORY) && command->wd2 <= 0) + } else if ((command.verb == FIND || + command.verb == INVENTORY) && (command.word[1].id == WORD_EMPTY || command.word[1].id == WORD_NOT_FOUND)) /* FALL THROUGH */; else { - sspeak(NO_SEE, command->raw1); + sspeak(NO_SEE, command.word[0].raw); return GO_CLEAROBJ; } - if (command->wd2 > 0) - return GO_WORD2; - if (command->verb != 0) - command->part = transitive; + if (command.verb != 0) + command.part = transitive; } - switch (command->part) { + switch (command.part) { case intransitive: - if (command->wd2 > 0 && command->verb != SAY) + if (command.word[1].raw[0] != '\0' && command.verb != SAY) return GO_WORD2; - if (command->verb == SAY) - command->obj = command->wd2; - if (command->obj == NO_OBJECT || - command->obj == INTRANSITIVE) { + 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. */ + command.obj = command.word[1].raw[0] != '\0' ? KEYS : NO_OBJECT; + if (command.obj == NO_OBJECT || + command.obj == INTRANSITIVE) { /* Analyse an intransitive verb (ie, no object given yet). */ - switch (command->verb) { + switch (command.verb) { case CARRY: - return vcarry(command->verb, INTRANSITIVE); + return vcarry(command.verb, INTRANSITIVE); case DROP: return GO_UNKNOWN; case SAY: return GO_UNKNOWN; case UNLOCK: - return lock(command->verb, INTRANSITIVE); + return lock(command.verb, INTRANSITIVE); case NOTHING: { rspeak(OK_MAN); return (GO_CLEAROBJ); } case LOCK: - return lock(command->verb, INTRANSITIVE); + return lock(command.verb, INTRANSITIVE); case LIGHT: - return light(command->verb, INTRANSITIVE); + return light(command.verb, INTRANSITIVE); case EXTINGUISH: - return extinguish(command->verb, INTRANSITIVE); + return extinguish(command.verb, INTRANSITIVE); case WAVE: return GO_UNKNOWN; case TAME: return GO_UNKNOWN; case GO: { - rspeak(actions[command->verb].message); + speak(actions[command.verb].message); return GO_CLEAROBJ; } case ATTACK: + command.obj = INTRANSITIVE; return attack(command); case POUR: - return pour(command->verb, command->obj); + return pour(command.verb, INTRANSITIVE); case EAT: - return eat(command->verb, INTRANSITIVE); + return eat(command.verb, INTRANSITIVE); case DRINK: - return drink(command->verb, command->obj); + return drink(command.verb, INTRANSITIVE); case RUB: return GO_UNKNOWN; case THROW: @@ -1354,20 +1435,24 @@ int action(struct command_t *command) case FEED: return GO_UNKNOWN; case FILL: - return fill(command->verb, command->obj); + return fill(command.verb, INTRANSITIVE); case BLAST: blast(); return GO_CLEAROBJ; case SCORE: score(scoregame); return GO_CLEAROBJ; - case GIANTWORDS: - return bigwords(command->wd1); + case FEE: + case FIE: + case FOE: + case FOO: + case FUM: + return bigwords(command.word[0].id); case BRIEF: return brief(); case READ: - command->obj = INTRANSITIVE; - return read(*command); + command.obj = INTRANSITIVE; + return read(command); case BREAK: return GO_UNKNOWN; case WAKE: @@ -1377,116 +1462,131 @@ int action(struct command_t *command) case RESUME: return resume(); case FLY: - return fly(command->verb, INTRANSITIVE); + return fly(command.verb, INTRANSITIVE); case LISTEN: return listen(); case PART: return reservoir(); - default: + case SEED: + case WASTE: + rspeak(NUMERIC_REQUIRED); + return GO_TOP; + default: // LCOV_EXCL_LINE BUG(INTRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE } } /* FALLTHRU */ case transitive: /* Analyse a transitive verb. */ - switch (command->verb) { + switch (command.verb) { case CARRY: - return vcarry(command->verb, command->obj); + return vcarry(command.verb, command.obj); case DROP: - return discard(command->verb, command->obj, false); + return discard(command.verb, command.obj); case SAY: return say(command); case UNLOCK: - return lock(command->verb, command->obj); + return lock(command.verb, command.obj); case NOTHING: { rspeak(OK_MAN); return (GO_CLEAROBJ); } case LOCK: - return lock(command->verb, command->obj); + return lock(command.verb, command.obj); case LIGHT: - return light(command->verb, command->obj); + return light(command.verb, command.obj); case EXTINGUISH: - return extinguish(command->verb, command->obj); + return extinguish(command.verb, command.obj); case WAVE: - return wave(command->verb, command->obj); + return wave(command.verb, command.obj); case TAME: { - rspeak(actions[command->verb].message); + speak(actions[command.verb].message); return GO_CLEAROBJ; } case GO: { - rspeak(actions[command->verb].message); + speak(actions[command.verb].message); return GO_CLEAROBJ; } case ATTACK: return attack(command); case POUR: - return pour(command->verb, command->obj); + return pour(command.verb, command.obj); case EAT: - return eat(command->verb, command->obj); + return eat(command.verb, command.obj); case DRINK: - return drink(command->verb, command->obj); + return drink(command.verb, command.obj); case RUB: - return rub(command->verb, command->obj); + return rub(command.verb, command.obj); case THROW: return throw (command); case QUIT: { - rspeak(actions[command->verb].message); + speak(actions[command.verb].message); return GO_CLEAROBJ; } case FIND: - return find(command->verb, command->obj); + return find(command.verb, command.obj); case INVENTORY: - return find(command->verb, command->obj); + return find(command.verb, command.obj); case FEED: - return feed(command->verb, command->obj); + return feed(command.verb, command.obj); case FILL: - return fill(command->verb, command->obj); + return fill(command.verb, command.obj); case BLAST: blast(); return GO_CLEAROBJ; case SCORE: { - rspeak(actions[command->verb].message); + speak(actions[command.verb].message); return GO_CLEAROBJ; } - case GIANTWORDS: { - rspeak(actions[command->verb].message); + case FEE: + case FIE: + case FOE: + case FOO: + case FUM: { + speak(actions[command.verb].message); return GO_CLEAROBJ; } case BRIEF: { - rspeak(actions[command->verb].message); + speak(actions[command.verb].message); return GO_CLEAROBJ; } case READ: - return read(*command); + return read(command); case BREAK: - return vbreak(command->verb, command->obj); + return vbreak(command.verb, command.obj); case WAKE: - return wake(command->verb, command->obj); + return wake(command.verb, command.obj); case SAVE: { - rspeak(actions[command->verb].message); + speak(actions[command.verb].message); return GO_CLEAROBJ; } case RESUME: { - rspeak(actions[command->verb].message); + speak(actions[command.verb].message); return GO_CLEAROBJ; } case FLY: - return fly(command->verb, command->obj); + return fly(command.verb, command.obj); case LISTEN: { - rspeak(actions[command->verb].message); + speak(actions[command.verb].message); return GO_CLEAROBJ; } + // LCOV_EXCL_START + // This case should never happen - here only as placeholder case PART: return reservoir(); - default: + // LCOV_EXCL_STOP + case SEED: + return seed(command.verb, command.word[1].raw); + case WASTE: + return waste(command.verb, (turn_t)atol(command.word[1].raw)); + default: // LCOV_EXCL_LINE BUG(TRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE } case unknown: /* Unknown verb, couldn't deduce object - might need hint */ - sspeak(WHAT_DO, command->raw1); + sspeak(WHAT_DO, command.word[0].raw); return GO_CHECKHINT; - default: + default: // LCOV_EXCL_LINE BUG(SPEECHPART_NOT_TRANSITIVE_OR_INTRANSITIVE_OR_UNKNOWN); // LCOV_EXCL_LINE } }