X-Git-Url: https://jxself.org/git/?p=open-adventure.git;a=blobdiff_plain;f=actions.c;h=eeec3eaf40ada6209676beec58a5f748b2275067;hp=b079bd352f863da9e3dab42a30da543b34efa5a9;hb=609159ad6d62af90cdbf97cbb09fffabde87a026;hpb=393010ec41c3c6c25fab05284a76c51ac3135a7b diff --git a/actions.c b/actions.c index b079bd3..eeec3ea 100644 --- a/actions.c +++ b/actions.c @@ -4,13 +4,7 @@ #include "advent.h" #include "dungeon.h" -static int fill(token_t, token_t); - -static void state_change(long obj, long state) -{ - game.prop[obj] = state; - pspeak(obj, change, state, true); -} +static int fill(verb_t, obj_t); static int attack(struct command_t *command) /* Attack. Assume target if unambiguous. "Throw" also links here. @@ -18,12 +12,10 @@ static int attack(struct command_t *command) * 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; - long spk = actions[verb].message; - if (obj == NO_OBJECT || - obj == INTRANSITIVE) { + if (obj == INTRANSITIVE) { int changes = 0; if (atdwrf(game.loc) > 0) { obj = DWARF; @@ -50,7 +42,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; @@ -69,215 +61,214 @@ static int attack(struct command_t *command) } if (changes >= 2) return GO_UNKNOWN; - } + if (obj == BIRD) { if (game.closed) { rspeak(UNHAPPY_BIRD); - return GO_CLEAROBJ; + } else { + DESTROY(BIRD); + rspeak(BIRD_DEAD); } - DESTROY(BIRD); - spk = BIRD_DEAD; - } else if (obj == VEND) { + return GO_CLEAROBJ; + } + if (obj == VEND) { state_change(VEND, game.prop[VEND] == VEND_BLOCKS ? VEND_UNBLOCKS : VEND_BLOCKS); return GO_CLEAROBJ; } - if (obj == NO_OBJECT) - spk = NO_TARGET; - if (obj == CLAM || - obj == OYSTER) - spk = SHELL_IMPERVIOUS; - if (obj == SNAKE) - spk = SNAKE_WARNING; - if (obj == DWARF) - spk = BARE_HANDS_QUERY; - if (obj == DWARF && game.closed) - return GO_DWARFWAKE; - if (obj == DRAGON) - spk = ALREADY_DEAD; - if (obj == TROLL) - spk = ROCKY_TROLL; - if (obj == OGRE) - spk = OGRE_DODGE; - if (obj == OGRE && atdwrf(game.loc) > 0) { - rspeak(spk); - rspeak(KNIFE_THROWN); - DESTROY(OGRE); - int dwarves = 0; - for (int i = 1; i < PIRATE; i++) { - if (game.dloc[i] == game.loc) { - ++dwarves; - game.dloc[i] = LOC_LONGWEST; - game.dseen[i] = false; - } - } - spk = (dwarves > 1) ? - OGRE_PANIC1 : - OGRE_PANIC2; - } else if (obj == BEAR) { + if (obj == BEAR) { switch (game.prop[BEAR]) { case UNTAMED_BEAR: - spk = BEAR_HANDS; + rspeak(BEAR_HANDS); break; case SITTING_BEAR: - spk = BEAR_CONFUSED; + rspeak(BEAR_CONFUSED); break; case CONTENTED_BEAR: - spk = BEAR_CONFUSED; + rspeak(BEAR_CONFUSED); break; case BEAR_DEAD: - spk = ALREADY_DEAD; + rspeak(ALREADY_DEAD); break; } - } else if (obj == DRAGON && game.prop[DRAGON] == DRAGON_BARS) { + return GO_CLEAROBJ; + } + if (obj == DRAGON && game.prop[DRAGON] == DRAGON_BARS) { /* Fun stuff for dragon. If he insists on attacking it, win! * Set game.prop to dead, move dragon to central loc (still * 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; } - rspeak(spk); + if (obj == OGRE) { + rspeak(OGRE_DODGE); + if (atdwrf(game.loc) == 0) + return GO_CLEAROBJ; + + rspeak(KNIFE_THROWN); + DESTROY(OGRE); + int dwarves = 0; + for (int i = 1; i < PIRATE; i++) { + if (game.dloc[i] == game.loc) { + ++dwarves; + game.dloc[i] = LOC_LONGWEST; + game.dseen[i] = false; + } + } + rspeak((dwarves > 1) ? + OGRE_PANIC1 : + OGRE_PANIC2); + return GO_CLEAROBJ; + } + + switch (obj) { + case INTRANSITIVE: + rspeak(NO_TARGET); + break; + case CLAM: + case OYSTER: + rspeak(SHELL_IMPERVIOUS); + break; + case SNAKE: + rspeak(SNAKE_WARNING); + break; + case DWARF: + if (game.closed) { + return GO_DWARFWAKE; + } + rspeak(BARE_HANDS_QUERY); + break; + case DRAGON: + rspeak(ALREADY_DEAD); + break; + case TROLL: + rspeak(ROCKY_TROLL); + break; + default: + speak(actions[verb].message); + } return GO_CLEAROBJ; } -static int bigwords(token_t foo) +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). */ { - char word[TOKLEN+1]; - packed_to_token(foo, word); - int k = (int) get_special_vocab_id(word); - int spk = NOTHING_HAPPENS; - if (game.foobar != 1 - k) { - if (game.foobar != 0 && game.loc == LOC_GIANTROOM) - spk = START_OVER; - rspeak(spk); - 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(spk); + rspeak(NOTHING_HAPPENS); return GO_CLEAROBJ; } 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; - k = EGGS_DONE; if (HERE(EGGS)) - k = EGGS_VANISHED; - if (game.loc == objects[EGGS].plac) - k = EGGS_HERE; + pspeak(EGGS, look, EGGS_VANISHED, true); + else if (game.loc == objects[EGGS].plac) + pspeak(EGGS, look, EGGS_HERE, true); + else + pspeak(EGGS, look, EGGS_DONE, true); move(EGGS, objects[EGGS].plac); - pspeak(EGGS, look, k, true); + return GO_CLEAROBJ; } - } -} - -static int bivalve(token_t verb, token_t obj) -/* Clam/oyster actions */ -{ - bool is_oyster = (obj == OYSTER); - if (verb == LOCK) { - rspeak(HUH_MAN); - return GO_CLEAROBJ; - } - if (!TOTING(TRIDENT)) { - rspeak(is_oyster ? - OYSTER_OPENER : - CLAM_OPENER); - return GO_CLEAROBJ; - } - if (TOTING(obj)) { - rspeak( is_oyster ? - DROP_OYSTER : - DROP_CLAM); + } 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; } - - if (!is_oyster) { - DESTROY(CLAM); - drop(OYSTER, game.loc); - drop(PEARL, LOC_CULDESAC); - } - rspeak(is_oyster ? - OYSTER_OPENS : - PEARL_FALLS); - 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 int 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); } @@ -290,15 +281,14 @@ static int brief(void) return GO_CLEAROBJ; } -static int vcarry(token_t verb, token_t obj) +static int 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; @@ -306,275 +296,325 @@ 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 */ { - int spk; if (verb != LOCK) { - spk = CHAIN_UNLOCKED; - if (game.prop[BEAR] == UNTAMED_BEAR) - spk = BEAR_BLOCKS; - if (game.prop[CHAIN] == CHAIN_HEAP) - spk = ALREADY_UNLOCKED; - if (spk != CHAIN_UNLOCKED) { - rspeak(spk); + if (game.prop[BEAR] == UNTAMED_BEAR) { + rspeak(BEAR_BLOCKS); + return GO_CLEAROBJ; + } + if (game.prop[CHAIN] == CHAIN_HEAP) { + rspeak(ALREADY_UNLOCKED); 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; - /* FIXME: Arithmetic on state numbers */ - game.fixed[BEAR] = 2 - game.prop[BEAR]; - } else { - spk = CHAIN_LOCKED; - if (game.prop[CHAIN] != CHAIN_HEAP) - spk = ALREADY_LOCKED; - if (game.loc != objects[CHAIN].plac) - spk = NO_LOCKSITE; - if (spk != CHAIN_LOCKED) { - rspeak(spk); - return GO_CLEAROBJ; + + switch (game.prop[BEAR]) { + // LCOV_EXCL_START + case BEAR_DEAD: + /* Can't be reached as long as the only way for the bear to die + * is from 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] = IS_FREE; } - game.prop[CHAIN] = CHAIN_FIXED; - if (TOTING(CHAIN)) - drop(CHAIN, game.loc); - game.fixed[CHAIN] = -1; + rspeak(CHAIN_UNLOCKED); + return GO_CLEAROBJ; } - rspeak(spk); + + if (game.prop[CHAIN] != CHAIN_HEAP) { + rspeak(ALREADY_LOCKED); + return GO_CLEAROBJ; + } + if (game.loc != objects[CHAIN].plac) { + rspeak(NO_LOCKSITE); + return GO_CLEAROBJ; + } + + game.prop[CHAIN] = CHAIN_FIXED; + + if (TOTING(CHAIN)) + drop(CHAIN, game.loc); + 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 int 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, FRESH_BATTERIES, true); + 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 == BIRD && HERE(SNAKE)) { + } + + 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 (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)) { - rspeak(TROLL_SCAMPERS); - move(TROLL, LOC_NOWHERE); - move(TROLL + NOBJECTS, LOC_NOWHERE); - move(TROLL2, objects[TROLL].plac); - move(TROLL2 + NOBJECTS, objects[TROLL].fixd); - juggle(CHASM); - game.prop[TROLL] = TROLL_GONE; - } 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 int 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 && (LIQUID() != WATER || - !HERE(BOTTLE))) + if (obj == INTRANSITIVE && LIQLOC(game.loc) != WATER && + (LIQUID() != WATER || !HERE(BOTTLE))) { return GO_UNKNOWN; - if (obj != BLOOD) { - if (obj != NO_OBJECT && obj != WATER) { - rspeak(RIDICULOUS_ATTEMPT); - } else if (LIQUID() == WATER && HERE(BOTTLE)) { - game.prop[BOTTLE] = EMPTY_BOTTLE; - game.place[WATER] = LOC_NOWHERE; - rspeak(BOTTLE_EMPTY); - } else { - rspeak(actions[verb].message); - } - } else { + } + + if (obj == BLOOD) { DESTROY(BLOOD); state_change(DRAGON, DRAGON_BLOODLESS); game.blooded = true; + return GO_CLEAROBJ; } + + if (obj != INTRANSITIVE && obj != WATER) { + rspeak(RIDICULOUS_ATTEMPT); + return GO_CLEAROBJ; + } + if (LIQUID() == WATER && HERE(BOTTLE)) { + game.place[WATER] = LOC_NOWHERE; + state_change(BOTTLE, EMPTY_BOTTLE); + return GO_CLEAROBJ; + } + + speak(actions[verb].message); return GO_CLEAROBJ; } -static int eat(token_t verb, token_t obj) +static int 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 int extinguish(verb_t verb, obj_t obj) /* Extinguish. Lamp, urn, dragon/volcano (nice try). */ { if (obj == INTRANSITIVE) { @@ -582,164 +622,197 @@ static int extinguish(token_t verb, int obj) obj = LAMP; if (HERE(URN) && game.prop[URN] == URN_LIT) obj = URN; - if (obj == INTRANSITIVE || - (HERE(LAMP) && game.prop[LAMP] == LAMP_BRIGHT && - HERE(URN) && game.prop[URN] == URN_LIT)) + if (obj == INTRANSITIVE) 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); } - - } else if (obj == LAMP) { + break; + case LAMP: state_change(LAMP, LAMP_DARK); rspeak(DARK(game.loc) ? PITCH_DARK : NO_MESSAGE); - - } else if (obj == DRAGON || - obj == VOLCANO) { + break; + case DRAGON: + case VOLCANO: rspeak(BEYOND_POWER); - - } else { - rspeak(actions[verb].message); + break; + default: + speak(actions[verb].message); } return GO_CLEAROBJ; } -static int feed(token_t verb, token_t obj) +static int 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) +int fill(verb_t verb, obj_t obj) /* Fill. Bottle or urn must be empty, and liquid available. (Vase * is nasty.) */ { - int k; - int spk = actions[verb].message; if (obj == VASE) { - spk = ARENT_CARRYING; - if (LIQLOC(game.loc) == 0) - spk = FILL_INVALID; - if (LIQLOC(game.loc) == 0 || - !TOTING(VASE)) { - rspeak(spk); + if (LIQLOC(game.loc) == NO_OBJECT) { + rspeak(FILL_INVALID); + return GO_CLEAROBJ; + } + if (!TOTING(VASE)) { + rspeak(ARENT_CARRYING); return GO_CLEAROBJ; } rspeak(SHATTER_VASE); game.prop[VASE] = VASE_BROKEN; - game.fixed[VASE] = -1; - return (discard(verb, obj, true)); - } else if (obj == URN) { - spk = FULL_URN; + game.fixed[VASE] = IS_FIXED; + drop(VASE, game.loc); + return GO_CLEAROBJ; + } + + if (obj == URN) { if (game.prop[URN] != URN_EMPTY) { - rspeak(spk); + rspeak(FULL_URN); return GO_CLEAROBJ; } - spk = FILL_INVALID; - k = LIQUID(); - if (k == 0 || - !HERE(BOTTLE)) { - rspeak(spk); + if (!HERE(BOTTLE)) { + rspeak(FILL_INVALID); return GO_CLEAROBJ; } - game.place[k] = LOC_NOWHERE; - game.prop[BOTTLE] = EMPTY_BOTTLE; - if (k == OIL) + int k = LIQUID(); + switch (k) { + case WATER: + game.prop[BOTTLE] = EMPTY_BOTTLE; + rspeak(WATER_URN); + break; + case OIL: game.prop[URN] = URN_DARK; - spk = WATER_URN + game.prop[URN]; - rspeak(spk); + game.prop[BOTTLE] = EMPTY_BOTTLE; + rspeak(OIL_URN); + break; + case NO_OBJECT: + default: + rspeak(FILL_INVALID); + return GO_CLEAROBJ; + } + game.place[k] = LOC_NOWHERE; return GO_CLEAROBJ; - } else if (obj != NO_OBJECT && obj != BOTTLE) { - rspeak(spk); + } + if (obj != INTRANSITIVE && obj != BOTTLE) { + speak(actions[verb].message); return GO_CLEAROBJ; - } else if (obj == NO_OBJECT && !HERE(BOTTLE)) + } + if (obj == INTRANSITIVE && !HERE(BOTTLE)) return GO_UNKNOWN; - spk = BOTTLED_WATER; - if (LIQLOC(game.loc) == 0) - spk = NO_LIQUID; - if (HERE(URN) && game.prop[URN] != URN_EMPTY) - spk = URN_NOPOUR; - if (LIQUID() != 0) - spk = BOTTLE_FULL; - if (spk == BOTTLED_WATER) { - /* FIXME: Arithmetic on property values */ - game.prop[BOTTLE] = MOD(conditions[game.loc], 4) / 2 * 2; - k = LIQUID(); - if (TOTING(BOTTLE)) - game.place[k] = CARRIED; - if (k == OIL) - spk = BOTTLED_OIL; + + if (HERE(URN) && game.prop[URN] != URN_EMPTY) { + rspeak(URN_NOPOUR); + return GO_CLEAROBJ; } - rspeak(spk); + if (LIQUID() != NO_OBJECT) { + rspeak(BOTTLE_FULL); + return GO_CLEAROBJ; + } + if (LIQLOC(game.loc) == NO_OBJECT) { + rspeak(NO_LIQUID); + return GO_CLEAROBJ; + } + + state_change(BOTTLE, (LIQLOC(game.loc) == OIL) + ? OIL_BOTTLE + : WATER_BOTTLE); + if (TOTING(BOTTLE)) + game.place[LIQUID()] = CARRIED; return GO_CLEAROBJ; } -static int find(token_t verb, token_t obj) +static int find(verb_t verb, obj_t obj) /* Find. Might be carrying it, or it might be here. Else give caveat. */ { - int spk = actions[verb].message; + if (TOTING(obj)) { + rspeak(ALREADY_CARRYING); + return GO_CLEAROBJ; + } + + if (game.closed) { + rspeak(NEEDED_NEARBY); + return GO_CLEAROBJ; + } + if (AT(obj) || (LIQUID() == obj && AT(BOTTLE)) || obj == LIQLOC(game.loc) || - (obj == DWARF && atdwrf(game.loc) > 0)) - spk = YOU_HAVEIT; - if (game.closed) - spk = NEEDED_NEARBY; - if (TOTING(obj)) - spk = ALREADY_CARRYING; - rspeak(spk); + (obj == DWARF && atdwrf(game.loc) > 0)) { + rspeak(YOU_HAVEIT); + return GO_CLEAROBJ; + } + + + speak(actions[verb].message); return GO_CLEAROBJ; } -static int fly(token_t verb, token_t obj) +static int fly(verb_t verb, obj_t obj) /* Fly. Snide remarks unless hovering rug is here. */ { if (obj == INTRANSITIVE) { @@ -755,7 +828,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) { @@ -764,13 +837,13 @@ 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; } @@ -778,81 +851,88 @@ static int fly(token_t verb, token_t obj) static int inven(void) /* Inventory. If object, treat same as find. Else report on current burden. */ { - int spk = NO_CARRY; - for (int i = 1; i <= NOBJECTS; i++) { + bool empty = true; + for (obj_t i = 1; i <= NOBJECTS; i++) { if (i == BEAR || !TOTING(i)) continue; - if (spk == NO_CARRY) + if (empty) { rspeak(NOW_HOLDING); + empty = false; + } pspeak(i, touch, -1, false); - spk = NO_MESSAGE; } if (TOTING(BEAR)) - spk = TAME_BEAR; - rspeak(spk); + rspeak(TAME_BEAR); + if (empty) + rspeak(NO_CARRY); return GO_CLEAROBJ; } -static int light(token_t verb, token_t obj) +static int 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. */ +/* 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, mi, true, 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; } @@ -860,7 +940,7 @@ static int listen(void) return GO_CLEAROBJ; } -static int lock(token_t verb, token_t obj) +static int lock(verb_t verb, obj_t obj) /* Lock, unlock, no object given. Assume various things if present. */ { if (obj == INTRANSITIVE) { @@ -874,8 +954,7 @@ static int lock(token_t verb, token_t obj) obj = GRATE; if (HERE(CHAIN)) obj = CHAIN; - if (obj == NO_OBJECT || - obj == INTRANSITIVE) { + if (obj == INTRANSITIVE) { rspeak(NOTHING_LOCKED); return GO_CLEAROBJ; } @@ -883,23 +962,18 @@ static int lock(token_t verb, token_t obj) /* Lock, unlock object. Special stuff for opening clam/oyster * and for chain. */ - int spk = actions[verb].message; - if (obj == CLAM || obj == OYSTER) - return bivalve(verb, obj); - if (obj == DOOR) - spk = (game.prop[DOOR] == DOOR_UNRUSTED) ? OK_MAN : RUSTY_DOOR; - if (obj == CAGE) - spk = NO_LOCK; - if (obj == KEYS) - spk = CANNOT_UNLOCK; - if (obj == GRATE || - obj == CHAIN) { - spk = NO_KEYS; + + 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) { - spk = EXIT_CLOSED; + rspeak(EXIT_CLOSED); if (!game.panic) game.clock2 = PANICTIME; game.panic = true; @@ -907,25 +981,59 @@ static int lock(token_t verb, token_t obj) state_change(GRATE, (verb == LOCK) ? GRATE_CLOSED : GRATE_OPEN); - return GO_CLEAROBJ; } + } else + rspeak(NO_KEYS); + break; + case CLAM: + if (verb == LOCK) + rspeak(HUH_MAN); + else if (!TOTING(TRIDENT)) + rspeak(CLAM_OPENER); + else { + DESTROY(CLAM); + drop(OYSTER, game.loc); + drop(PEARL, LOC_CULDESAC); + rspeak(PEARL_FALLS); } + break; + case OYSTER: + if (verb == LOCK) + rspeak(HUH_MAN); + else if (TOTING(OYSTER)) + rspeak(DROP_OYSTER); + else if (!TOTING(TRIDENT)) + rspeak(OYSTER_OPENER); + else + rspeak(OYSTER_OPENS); + break; + case DOOR: + rspeak((game.prop[DOOR] == DOOR_UNRUSTED) ? OK_MAN : RUSTY_DOOR); + break; + case CAGE: + rspeak( NO_LOCK); + break; + case KEYS: + rspeak(CANNOT_UNLOCK); + break; + default: + speak(actions[verb].message); } - rspeak(spk); + return GO_CLEAROBJ; } -static int pour(token_t verb, token_t obj) +static int 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; } @@ -943,15 +1051,15 @@ static int pour(token_t verb, token_t obj) return GO_CLEAROBJ; } if (!AT(DOOR)) { - if (obj != WATER) { + if (obj == WATER) { + /* cycle through the three plant states */ + state_change(PLANT, MOD(game.prop[PLANT] + 1, 3)); + game.prop[PLANT2] = game.prop[PLANT]; + return GO_MOVE; + } else { rspeak(SHAKING_LEAVES); return GO_CLEAROBJ; } - /* FIXME: Arithmetic on state numbers */ - pspeak(PLANT, look, game.prop[PLANT] + 3, true); - game.prop[PLANT] = MOD(game.prop[PLANT] + 1, 3); - game.prop[PLANT2] = game.prop[PLANT]; - return GO_MOVE; } else { state_change(DOOR, (obj == OIL) ? DOOR_UNRUSTED : @@ -972,13 +1080,13 @@ static int read(struct 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; } @@ -988,8 +1096,8 @@ static int read(struct command_t command) } 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); return GO_CLEAROBJ; @@ -998,14 +1106,12 @@ static int read(struct command_t command) static int 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 { - /* FIXME: Arithmetic on state numbers */ - pspeak(RESER, look, game.prop[RESER] + 1, true); - game.prop[RESER] = 1 - game.prop[RESER]; + state_change(RESER, + game.prop[RESER] == WATERS_PARTED ? WATERS_UNPARTED : WATERS_PARTED); if (AT(RESER)) return GO_CLEAROBJ; else { @@ -1017,7 +1123,7 @@ static int reservoir(void) } } -static int rub(token_t verb, token_t obj) +static int rub(verb_t verb, obj_t obj) /* Rub. Yields various snide remarks except for lit urn. */ { if (obj == URN && game.prop[URN] == URN_LIT) { @@ -1030,38 +1136,34 @@ 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. */ +/* Say. Echo WD2. Magic words override. */ { - long a = command->wd1; - if (command->wd2 > 0) { - a = command->wd2; - 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->type2 == MOTION && + (command->id2 == XYZZY || + command->id2 == PLUGH || + command->id2 == PLOVER)) { + return GO_WORD2; + } + if (command->type2 == ACTION && + (command->id2 == FEE || + command->id2 == FIE || + command->id2 == FOE || + command->id2 == FOO || + command->id2 == FUM || + command->id2 == PART)) { + return GO_WORD2; + } + sspeak(OKEY_DOKEY, command->raw2); return GO_CLEAROBJ; } -static int throw_support(long spk) +static int throw_support(vocab_t spk) { rspeak(spk); drop(AXE, game.loc); @@ -1074,17 +1176,15 @@ static int throw (struct command_t *command) * (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); + speak(actions[command->verb].message); return GO_CLEAROBJ; } if (objects[command->obj].is_treasure && AT(TROLL)) { /* Snarf a treasure for the troll. */ 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); @@ -1097,31 +1197,31 @@ static int throw (struct command_t *command) return (feed(command->verb, command->obj)); } if (command->obj != AXE) - return (discard(command->verb, command->obj, false)); + 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) ? @@ -1131,12 +1231,12 @@ static int throw (struct command_t *command) } } -static int wake(token_t verb, token_t obj) +static int 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); @@ -1144,7 +1244,7 @@ static int wake(token_t verb, token_t obj) } } -static int wave(token_t verb, token_t obj) +static int wave(verb_t verb, obj_t obj) /* Wave. No effect unless waving rod at fissure or at bird. */ { if (obj != ROD || @@ -1152,16 +1252,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; @@ -1184,9 +1284,8 @@ static int wave(token_t verb, token_t obj) CAGE_FLY : FREE_FLY); - /* FIXME: Arithemetic on property values */ - game.prop[FISSURE] = 1 - game.prop[FISSURE]; - pspeak(FISSURE, look, 2 - game.prop[FISSURE], true); + state_change(FISSURE, + game.prop[FISSURE] == BRIDGED ? UNBRIDGED : BRIDGED); return GO_CLEAROBJ; } } @@ -1206,28 +1305,15 @@ int action(struct command_t *command) * location. */ 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)) /* FALL THROUGH */; - else if (command->obj == OIL && HERE(URN) && game.prop[URN] != 0) { + 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) { + } 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) { @@ -1238,14 +1324,14 @@ int action(struct command_t *command) command->obj = ROD2; /* FALL THROUGH */; } else if ((command->verb == FIND || - command->verb == INVENTORY) && command->wd2 <= 0) + command->verb == INVENTORY) && (command->id2 == WORD_EMPTY || command->id2 == WORD_NOT_FOUND)) /* FALL THROUGH */; else { sspeak(NO_SEE, command->raw1); return GO_CLEAROBJ; } - if (command->wd2 > 0) + if (command->id2 != WORD_EMPTY && command->id2 != WORD_NOT_FOUND) return GO_WORD2; if (command->verb != 0) command->part = transitive; @@ -1253,11 +1339,14 @@ int action(struct command_t *command) switch (command->part) { case intransitive: - if (command->wd2 > 0 && command->verb != SAY) + if (command->raw2[0] != '\0' && command->verb != SAY) return GO_WORD2; if (command->verb == SAY) - command->obj = command->wd2; - if (command->obj == 0 || + /* 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->raw2[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) { @@ -1284,17 +1373,18 @@ int action(struct command_t *command) 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); case DRINK: - return drink(command->verb, command->obj); + return drink(command->verb, INTRANSITIVE); case RUB: return GO_UNKNOWN; case THROW: @@ -1308,15 +1398,19 @@ 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->id1); case BRIEF: return brief(); case READ: @@ -1336,7 +1430,7 @@ int action(struct command_t *command) return listen(); case PART: return reservoir(); - default: + default: // LCOV_EXCL_LINE BUG(INTRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE } } @@ -1347,7 +1441,7 @@ int action(struct command_t *command) case CARRY: 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: @@ -1365,11 +1459,11 @@ int action(struct command_t *command) case WAVE: 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: @@ -1385,7 +1479,7 @@ int action(struct command_t *command) case THROW: return throw (command); case QUIT: { - rspeak(actions[command->verb].message); + speak(actions[command->verb].message); return GO_CLEAROBJ; } case FIND: @@ -1400,15 +1494,19 @@ int action(struct command_t *command) 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: @@ -1418,29 +1516,29 @@ int action(struct command_t *command) case WAKE: 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); case LISTEN: { - rspeak(actions[command->verb].message); + speak(actions[command->verb].message); return GO_CLEAROBJ; } case PART: return reservoir(); - default: + 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); return GO_CHECKHINT; - default: + default: // LCOV_EXCL_LINE BUG(SPEECHPART_NOT_TRANSITIVE_OR_INTRANSITIVE_OR_UNKNOWN); // LCOV_EXCL_LINE } }