2 * Actions for the dungeon-running code.
4 * SPDX-FileCopyrightText: (C) 1977, 2005 Will Crowther and Don Woods
5 * SPDX-License-Identifier: BSD-2-Clause
15 static phase_codes_t fill(verb_t, obj_t);
17 static phase_codes_t attack(command_t command) {
18 /* Attack. Assume target if unambiguous. "Throw" also links here.
19 * Attackable objects fall into two categories: enemies (snake,
20 * dwarf, etc.) and others (bird, clam, machine). Ambiguous if 2
21 * enemies, or no enemies but 2 others. */
22 verb_t verb = command.verb;
23 obj_t obj = command.obj;
25 if (obj == INTRANSITIVE) {
27 if (atdwrf(game.loc) > 0) {
35 if (AT(DRAGON) && game.objects[DRAGON].prop == DRAGON_BARS) {
47 if (HERE(BEAR) && game.objects[BEAR].prop == UNTAMED_BEAR) {
51 /* check for low-priority targets */
52 if (obj == INTRANSITIVE) {
53 /* Can't attack bird or machine by throwing axe. */
54 if (HERE(BIRD) && verb != THROW) {
58 if (HERE(VEND) && verb != THROW) {
62 /* Clam and oyster both treated as clam for intransitive
63 * case; no harm done. */
64 if (HERE(CLAM) || HERE(OYSTER)) {
84 state_change(VEND, game.objects[VEND].prop == VEND_BLOCKS
92 switch (game.objects[BEAR].prop) {
97 rspeak(BEAR_CONFUSED);
100 rspeak(BEAR_CONFUSED);
103 rspeak(ALREADY_DEAD);
108 if (obj == DRAGON && game.objects[DRAGON].prop == DRAGON_BARS) {
109 /* Fun stuff for dragon. If he insists on attacking it, win!
110 * Set game.prop to dead, move dragon to central loc (still
111 * fixed), move rug there (not fixed), and move him there,
112 * too. Then do a null motion to get new description. */
113 rspeak(BARE_HANDS_QUERY);
114 if (!silent_yes_or_no()) {
115 speak(arbitrary_messages[NASTY_DRAGON]);
118 state_change(DRAGON, DRAGON_DEAD);
119 game.objects[RUG].prop = RUG_FLOOR;
120 /* Hardcoding LOC_SECRET5 as the dragon's death location is
121 * ugly. The way it was computed before was worse; it depended
122 * on the two dragon locations being LOC_SECRET4 and LOC_SECRET6
123 * and LOC_SECRET5 being right between them.
125 move(DRAGON + NOBJECTS, IS_FIXED);
126 move(RUG + NOBJECTS, IS_FREE);
127 move(DRAGON, LOC_SECRET5);
128 move(RUG, LOC_SECRET5);
129 drop(BLOOD, LOC_SECRET5);
130 for (obj_t i = 1; i <= NOBJECTS; i++) {
131 if (game.objects[i].place == objects[DRAGON].plac ||
132 game.objects[i].place == objects[DRAGON].fixd) {
133 move(i, LOC_SECRET5);
136 game.loc = LOC_SECRET5;
142 if (atdwrf(game.loc) == 0) {
145 rspeak(KNIFE_THROWN);
148 for (int i = 1; i < PIRATE; i++) {
149 if (game.dwarves[i].loc == game.loc) {
151 game.dwarves[i].loc = LOC_LONGWEST;
152 game.dwarves[i].seen = false;
155 rspeak((dwarves > 1) ? OGRE_PANIC1 : OGRE_PANIC2);
165 rspeak(SHELL_IMPERVIOUS);
168 rspeak(SNAKE_WARNING);
174 rspeak(BARE_HANDS_QUERY);
177 rspeak(ALREADY_DEAD);
183 speak(actions[verb].message);
188 static phase_codes_t bigwords(vocab_t id) {
189 /* Only called on FEE FIE FOE FOO (AND FUM). Advance to next state if
190 * given in proper order. Look up foo in special section of vocab to
191 * determine which word we've got. Last word zips the eggs back to the
192 * giant room (unless already there). */
193 int foobar = abs(game.foobar);
195 /* Only FEE can start a magic-word sequence. */
196 if ((foobar == WORD_EMPTY) &&
197 (id == FIE || id == FOE || id == FOO || id == FUM)) {
198 rspeak(NOTHING_HAPPENS);
202 if ((foobar == WORD_EMPTY && id == FEE) ||
203 (foobar == FEE && id == FIE) || (foobar == FIE && id == FOE) ||
204 (foobar == FOE && id == FOO)) {
210 game.foobar = WORD_EMPTY;
211 if (game.objects[EGGS].place == objects[EGGS].plac ||
212 (TOTING(EGGS) && game.loc == objects[EGGS].plac)) {
213 rspeak(NOTHING_HAPPENS);
216 /* Bring back troll if we steal the eggs back from him
217 * before crossing. */
218 if (game.objects[EGGS].place == LOC_NOWHERE &&
219 game.objects[TROLL].place == LOC_NOWHERE &&
220 game.objects[TROLL].prop == TROLL_UNPAID) {
221 game.objects[TROLL].prop = TROLL_PAIDONCE;
224 pspeak(EGGS, look, true, EGGS_VANISHED);
225 } else if (game.loc == objects[EGGS].plac) {
226 pspeak(EGGS, look, true, EGGS_HERE);
228 pspeak(EGGS, look, true, EGGS_DONE);
230 move(EGGS, objects[EGGS].plac);
235 /* Magic-word sequence was started but is incorrect */
236 if (settings.oldstyle || game.seenbigwords) {
239 rspeak(WELL_POINTLESS);
241 game.foobar = WORD_EMPTY;
246 static void blast(void) {
247 /* Blast. No effect unless you've got dynamite, which is a neat trick!
249 if (PROP_IS_NOTFOUND(ROD2) || !game.closed) {
250 rspeak(REQUIRES_DYNAMITE);
253 game.bonus = splatter;
254 rspeak(SPLATTER_MESSAGE);
255 } else if (game.loc == LOC_NE) {
257 rspeak(DEFEAT_MESSAGE);
259 game.bonus = victory;
260 rspeak(VICTORY_MESSAGE);
266 static phase_codes_t vbreak(verb_t verb, obj_t obj) {
267 /* Break. Only works for mirror in repository and, of course, the
272 state_change(MIRROR, MIRROR_BROKEN);
279 if (game.objects[VASE].prop == VASE_WHOLE) {
281 drop(VASE, game.loc);
283 state_change(VASE, VASE_BROKEN);
284 game.objects[VASE].fixed = IS_FIXED;
289 speak(actions[verb].message);
291 return (GO_CLEAROBJ);
294 static phase_codes_t brief(void) {
295 /* Brief. Intransitive only. Suppress full descriptions after first
299 rspeak(BRIEF_CONFIRM);
303 static phase_codes_t vcarry(verb_t verb, obj_t obj) {
304 /* Carry an object. Special cases for bird and cage (if bird in cage,
305 * can't take one without the other). Liquids also special, since they
306 * depend on status of bottle. Also various side effects, etc. */
307 if (obj == INTRANSITIVE) {
308 /* Carry, no object given yet. OK if only one object present.
310 if (game.locs[game.loc].atloc == NO_OBJECT ||
311 game.link[game.locs[game.loc].atloc] != 0 ||
312 atdwrf(game.loc) > 0) {
315 obj = game.locs[game.loc].atloc;
319 speak(actions[verb].message);
324 rspeak(REMOVE_MESSAGE);
329 if (game.objects[obj].fixed != IS_FREE) {
332 /* Next guard tests whether plant is tiny or stashed */
333 rspeak(game.objects[PLANT].prop <= PLANT_THIRSTY
338 rspeak(game.objects[BEAR].prop == SITTING_BEAR
343 rspeak(game.objects[BEAR].prop != UNTAMED_BEAR
348 rspeak(game.objects[RUG].prop == RUG_HOVER
356 rspeak(DOUGHNUT_HOLES);
362 rspeak(HAND_PASSTHROUGH);
370 if (obj == WATER || obj == OIL) {
371 if (!HERE(BOTTLE) || LIQUID() != obj) {
372 if (!TOTING(BOTTLE)) {
373 rspeak(NO_CONTAINER);
376 if (game.objects[BOTTLE].prop == EMPTY_BOTTLE) {
377 return (fill(verb, BOTTLE));
386 if (game.holdng >= INVLIMIT) {
391 if (obj == BIRD && game.objects[BIRD].prop != BIRD_CAGED &&
392 !PROP_IS_STASHED(BIRD)) {
393 if (game.objects[BIRD].prop == BIRD_FOREST_UNCAGED) {
399 rspeak(CANNOT_CARRY);
406 game.objects[BIRD].prop = BIRD_CAGED;
408 if ((obj == BIRD || obj == CAGE) &&
409 (game.objects[BIRD].prop == BIRD_CAGED ||
410 PROP_STASHED(BIRD) == BIRD_CAGED)) {
411 /* expression maps BIRD to CAGE and CAGE to BIRD */
412 carry(BIRD + CAGE - obj, game.loc);
415 carry(obj, game.loc);
417 if (obj == BOTTLE && LIQUID() != NO_OBJECT) {
418 game.objects[LIQUID()].place = CARRIED;
421 if (GSTONE(obj) && !PROP_IS_FOUND(obj)) {
423 game.objects[CAVITY].prop = CAVITY_EMPTY;
429 static int chain(verb_t verb) {
430 /* Do something to the bear's chain */
432 if (game.objects[BEAR].prop == UNTAMED_BEAR) {
436 if (game.objects[CHAIN].prop == CHAIN_HEAP) {
437 rspeak(ALREADY_UNLOCKED);
440 game.objects[CHAIN].prop = CHAIN_HEAP;
441 game.objects[CHAIN].fixed = IS_FREE;
442 if (game.objects[BEAR].prop != BEAR_DEAD) {
443 game.objects[BEAR].prop = CONTENTED_BEAR;
446 switch (game.objects[BEAR].prop) {
449 /* Can't be reached until the bear can die in some way
450 * other than a bridge collapse. Leave in in case this
451 * changes, but exclude from coverage testing. */
452 game.objects[BEAR].fixed = IS_FIXED;
456 game.objects[BEAR].fixed = IS_FREE;
458 rspeak(CHAIN_UNLOCKED);
462 if (game.objects[CHAIN].prop != CHAIN_HEAP) {
463 rspeak(ALREADY_LOCKED);
466 if (game.loc != objects[CHAIN].plac) {
471 game.objects[CHAIN].prop = CHAIN_FIXED;
474 drop(CHAIN, game.loc);
476 game.objects[CHAIN].fixed = IS_FIXED;
478 rspeak(CHAIN_LOCKED);
482 static phase_codes_t discard(verb_t verb, obj_t obj) {
483 /* Discard object. "Throw" also comes here for most objects. Special
484 * cases for bird (might attack snake or dragon) and cage (might contain
485 * bird) and vase. Drop coins at vending machine for extra batteries. */
486 if (obj == ROD && !TOTING(ROD) && TOTING(ROD2)) {
491 speak(actions[verb].message);
495 if (GSTONE(obj) && AT(CAVITY) &&
496 game.objects[CAVITY].prop != CAVITY_FULL) {
498 game.objects[obj].prop = STATE_IN_CAVITY;
499 game.objects[CAVITY].prop = CAVITY_FULL;
501 ((obj == EMERALD && game.objects[RUG].prop != RUG_HOVER) ||
502 (obj == RUBY && game.objects[RUG].prop == RUG_HOVER))) {
505 } else if (TOTING(RUG)) {
510 if (!TOTING(RUG) || obj == RUBY) {
511 int k = (game.objects[RUG].prop == RUG_HOVER)
514 game.objects[RUG].prop = k;
515 if (k == RUG_HOVER) {
516 k = objects[SAPPH].plac;
518 move(RUG + NOBJECTS, k);
525 if (obj == COINS && HERE(VEND)) {
527 drop(BATTERY, game.loc);
528 pspeak(BATTERY, look, true, FRESH_BATTERIES);
532 if (LIQUID() == obj) {
535 if (obj == BOTTLE && LIQUID() != NO_OBJECT) {
536 game.objects[LIQUID()].place = LOC_NOWHERE;
539 if (obj == BEAR && AT(TROLL)) {
540 state_change(TROLL, TROLL_GONE);
541 move(TROLL, LOC_NOWHERE);
542 move(TROLL + NOBJECTS, IS_FREE);
543 move(TROLL2, objects[TROLL].plac);
544 move(TROLL2 + NOBJECTS, objects[TROLL].fixd);
551 if (game.loc != objects[PILLOW].plac) {
553 AT(PILLOW) ? VASE_WHOLE : VASE_DROPPED);
554 if (game.objects[VASE].prop != VASE_WHOLE) {
555 game.objects[VASE].fixed = IS_FIXED;
562 if (obj == CAGE && game.objects[BIRD].prop == BIRD_CAGED) {
563 drop(BIRD, game.loc);
567 if (AT(DRAGON) && game.objects[DRAGON].prop == DRAGON_BARS) {
573 rspeak(BIRD_ATTACKS);
578 /* Set game.prop for use by travel options */
579 game.objects[SNAKE].prop = SNAKE_CHASED;
584 game.objects[BIRD].prop =
585 FOREST(game.loc) ? BIRD_FOREST_UNCAGED : BIRD_UNCAGED;
595 static phase_codes_t drink(verb_t verb, obj_t obj) {
596 /* Drink. If no object, assume water and look for it here. If water
597 * is in the bottle, drink that, else must be at a water loc, so drink
599 if (obj == INTRANSITIVE && LIQLOC(game.loc) != WATER &&
600 (LIQUID() != WATER || !HERE(BOTTLE))) {
606 state_change(DRAGON, DRAGON_BLOODLESS);
611 if (obj != INTRANSITIVE && obj != WATER) {
612 rspeak(RIDICULOUS_ATTEMPT);
615 if (LIQUID() == WATER && HERE(BOTTLE)) {
616 game.objects[WATER].place = LOC_NOWHERE;
617 state_change(BOTTLE, EMPTY_BOTTLE);
621 speak(actions[verb].message);
625 static phase_codes_t eat(verb_t verb, obj_t obj) {
626 /* Eat. Intransitive: assume food if present, else ask what.
627 * Transitive: food ok, some things lose appetite, rest are ridiculous.
637 rspeak(THANKS_DELICIOUS);
648 rspeak(LOST_APPETITE);
651 speak(actions[verb].message);
656 static phase_codes_t extinguish(verb_t verb, obj_t obj) {
657 /* Extinguish. Lamp, urn, dragon/volcano (nice try). */
658 if (obj == INTRANSITIVE) {
659 if (HERE(LAMP) && game.objects[LAMP].prop == LAMP_BRIGHT) {
662 if (HERE(URN) && game.objects[URN].prop == URN_LIT) {
665 if (obj == INTRANSITIVE) {
672 if (game.objects[URN].prop != URN_EMPTY) {
673 state_change(URN, URN_DARK);
675 pspeak(URN, change, true, URN_DARK);
679 state_change(LAMP, LAMP_DARK);
680 rspeak(DARK(game.loc) ? PITCH_DARK : NO_MESSAGE);
684 rspeak(BEYOND_POWER);
687 speak(actions[verb].message);
692 static phase_codes_t feed(verb_t verb, obj_t obj) {
693 /* Feed. If bird, no seed. Snake, dragon, troll: quip. If dwarf,
694 * make him mad. Bear, special. */
700 if (game.objects[DRAGON].prop != DRAGON_BARS) {
701 rspeak(RIDICULOUS_ATTEMPT);
703 rspeak(NOTHING_EDIBLE);
707 if (!game.closed && HERE(BIRD)) {
709 rspeak(BIRD_DEVOURED);
711 rspeak(NOTHING_EDIBLE);
722 speak(actions[verb].message);
726 if (game.objects[BEAR].prop == BEAR_DEAD) {
727 rspeak(RIDICULOUS_ATTEMPT);
730 if (game.objects[BEAR].prop == UNTAMED_BEAR) {
733 game.objects[AXE].fixed = IS_FREE;
734 game.objects[AXE].prop = AXE_HERE;
735 state_change(BEAR, SITTING_BEAR);
737 rspeak(NOTHING_EDIBLE);
741 speak(actions[verb].message);
747 speak(actions[verb].message);
756 phase_codes_t fill(verb_t verb, obj_t obj) {
757 /* Fill. Bottle or urn must be empty, and liquid available. (Vase
760 if (LIQLOC(game.loc) == NO_OBJECT) {
761 rspeak(FILL_INVALID);
765 rspeak(ARENT_CARRYING);
768 rspeak(SHATTER_VASE);
769 game.objects[VASE].prop = VASE_BROKEN;
770 game.objects[VASE].fixed = IS_FIXED;
771 drop(VASE, game.loc);
776 if (game.objects[URN].prop != URN_EMPTY) {
781 rspeak(FILL_INVALID);
787 game.objects[BOTTLE].prop = EMPTY_BOTTLE;
791 game.objects[URN].prop = URN_DARK;
792 game.objects[BOTTLE].prop = EMPTY_BOTTLE;
797 rspeak(FILL_INVALID);
800 game.objects[k].place = LOC_NOWHERE;
803 if (obj != INTRANSITIVE && obj != BOTTLE) {
804 speak(actions[verb].message);
807 if (obj == INTRANSITIVE && !HERE(BOTTLE)) {
811 if (HERE(URN) && game.objects[URN].prop != URN_EMPTY) {
815 if (LIQUID() != NO_OBJECT) {
819 if (LIQLOC(game.loc) == NO_OBJECT) {
825 (LIQLOC(game.loc) == OIL) ? OIL_BOTTLE : WATER_BOTTLE);
826 if (TOTING(BOTTLE)) {
827 game.objects[LIQUID()].place = CARRIED;
832 static phase_codes_t find(verb_t verb, obj_t obj) {
833 /* Find. Might be carrying it, or it might be here. Else give caveat.
836 rspeak(ALREADY_CARRYING);
841 rspeak(NEEDED_NEARBY);
845 if (AT(obj) || (LIQUID() == obj && AT(BOTTLE)) ||
846 obj == LIQLOC(game.loc) || (obj == DWARF && atdwrf(game.loc) > 0)) {
851 speak(actions[verb].message);
855 static phase_codes_t fly(verb_t verb, obj_t obj) {
856 /* Fly. Snide remarks unless hovering rug is here. */
857 if (obj == INTRANSITIVE) {
862 if (game.objects[RUG].prop != RUG_HOVER) {
863 rspeak(RUG_NOTHING2);
870 speak(actions[verb].message);
873 if (game.objects[RUG].prop != RUG_HOVER) {
874 rspeak(RUG_NOTHING1);
878 if (game.loc == LOC_CLIFF) {
879 game.oldlc2 = game.oldloc;
880 game.oldloc = game.loc;
881 game.newloc = LOC_LEDGE;
883 } else if (game.loc == LOC_LEDGE) {
884 game.oldlc2 = game.oldloc;
885 game.oldloc = game.loc;
886 game.newloc = LOC_CLIFF;
890 /* should never happen */
891 rspeak(NOTHING_HAPPENS);
897 static phase_codes_t inven(void) {
898 /* Inventory. If object, treat same as find. Else report on current
901 for (obj_t i = 1; i <= NOBJECTS; i++) {
902 if (i == BEAR || !TOTING(i)) {
909 pspeak(i, touch, false, -1);
920 static phase_codes_t light(verb_t verb, obj_t obj) {
921 /* Light. Applicable only to lamp and urn. */
922 if (obj == INTRANSITIVE) {
924 if (HERE(LAMP) && game.objects[LAMP].prop == LAMP_DARK &&
929 if (HERE(URN) && game.objects[URN].prop == URN_DARK) {
940 state_change(URN, game.objects[URN].prop == URN_EMPTY
945 if (game.limit < 0) {
949 state_change(LAMP, LAMP_BRIGHT);
955 speak(actions[verb].message);
960 static phase_codes_t listen(void) {
961 /* Listen. Intransitive only. Print stuff based on object sound
963 bool soundlatch = false;
964 vocab_t sound = locations[game.loc].sound;
965 if (sound != SILENT) {
967 if (!locations[game.loc].loud) {
972 for (obj_t i = 1; i <= NOBJECTS; i++) {
973 if (!HERE(i) || objects[i].sounds[0] == NULL ||
974 PROP_IS_STASHED_OR_UNSEEN(i)) {
977 int mi = game.objects[i].prop;
978 /* (ESR) Some unpleasant magic on object states here. Ideally
979 * we'd have liked the bird to be a normal object that we can
980 * use state_change() on; can't do it, because there are
981 * actually two different series of per-state birdsounds
982 * depending on whether player has drunk dragon's blood. */
984 mi += 3 * game.blooded;
986 pspeak(i, hear, true, mi, game.zzword);
988 if (i == BIRD && mi == BIRD_ENDSTATE) {
999 static phase_codes_t lock(verb_t verb, obj_t obj) {
1000 /* Lock, unlock, no object given. Assume various things if present. */
1001 if (obj == INTRANSITIVE) {
1017 if (obj == INTRANSITIVE) {
1018 rspeak(NOTHING_LOCKED);
1023 /* Lock, unlock object. Special stuff for opening clam/oyster
1037 rspeak(EXIT_CLOSED);
1039 game.clock2 = PANICTIME;
1043 state_change(GRATE, (verb == LOCK)
1054 } else if (TOTING(CLAM)) {
1056 } else if (!TOTING(TRIDENT)) {
1057 rspeak(CLAM_OPENER);
1060 drop(OYSTER, game.loc);
1061 drop(PEARL, LOC_CULDESAC);
1062 rspeak(PEARL_FALLS);
1068 } else if (TOTING(OYSTER)) {
1069 rspeak(DROP_OYSTER);
1070 } else if (!TOTING(TRIDENT)) {
1071 rspeak(OYSTER_OPENER);
1073 rspeak(OYSTER_OPENS);
1077 rspeak((game.objects[DOOR].prop == DOOR_UNRUSTED) ? OK_MAN
1084 rspeak(CANNOT_UNLOCK);
1087 speak(actions[verb].message);
1093 static phase_codes_t pour(verb_t verb, obj_t obj) {
1094 /* Pour. If no object, or object is bottle, assume contents of bottle.
1095 * special tests for pouring water or oil on plant or rusty door. */
1096 if (obj == BOTTLE || obj == INTRANSITIVE) {
1099 if (obj == NO_OBJECT) {
1103 speak(actions[verb].message);
1107 if (obj != OIL && obj != WATER) {
1111 if (HERE(URN) && game.objects[URN].prop == URN_EMPTY) {
1112 return fill(verb, URN);
1114 game.objects[BOTTLE].prop = EMPTY_BOTTLE;
1115 game.objects[obj].place = LOC_NOWHERE;
1116 if (!(AT(PLANT) || AT(DOOR))) {
1122 /* cycle through the three plant states */
1124 MOD(game.objects[PLANT].prop + 1, 3));
1125 game.objects[PLANT2].prop = game.objects[PLANT].prop;
1128 rspeak(SHAKING_LEAVES);
1132 state_change(DOOR, (obj == OIL) ? DOOR_UNRUSTED : DOOR_RUSTED);
1137 static phase_codes_t quit(void) {
1138 /* Quit. Intransitive only. Verify intent and exit if that's what he
1140 if (yes_or_no(arbitrary_messages[REALLY_QUIT],
1141 arbitrary_messages[OK_MAN], arbitrary_messages[OK_MAN])) {
1142 terminate(quitgame);
1147 static phase_codes_t read(command_t command)
1148 /* Read. Print stuff based on objtxt. Oyster (?) is special case. */
1150 if (command.obj == INTRANSITIVE) {
1151 command.obj = NO_OBJECT;
1152 for (int i = 1; i <= NOBJECTS; i++) {
1153 if (HERE(i) && objects[i].texts[0] != NULL &&
1154 !PROP_IS_STASHED(i)) {
1155 command.obj = command.obj * NOBJECTS + i;
1158 if (command.obj > NOBJECTS || command.obj == NO_OBJECT ||
1164 if (DARK(game.loc)) {
1165 sspeak(NO_SEE, command.word[0].raw);
1166 } else if (command.obj == OYSTER) {
1167 if (!TOTING(OYSTER) || !game.closed) {
1168 rspeak(DONT_UNDERSTAND);
1169 } else if (!game.clshnt) {
1170 game.clshnt = yes_or_no(arbitrary_messages[CLUE_QUERY],
1171 arbitrary_messages[WAYOUT_CLUE],
1172 arbitrary_messages[OK_MAN]);
1174 pspeak(OYSTER, hear, true,
1175 1); // Not really a sound, but oh well.
1177 } else if (objects[command.obj].texts[0] == NULL ||
1178 PROP_IS_NOTFOUND(command.obj)) {
1179 speak(actions[command.verb].message);
1181 pspeak(command.obj, study, true,
1182 game.objects[command.obj].prop);
1187 static phase_codes_t reservoir(void) {
1188 /* Z'ZZZ (word gets recomputed at startup; different each game). */
1189 if (!AT(RESER) && game.loc != LOC_RESBOTTOM) {
1190 rspeak(NOTHING_HAPPENS);
1193 state_change(RESER, game.objects[RESER].prop == WATERS_PARTED
1199 game.oldlc2 = game.loc;
1200 game.newloc = LOC_NOWHERE;
1202 return GO_TERMINATE;
1207 static phase_codes_t rub(verb_t verb, obj_t obj) {
1208 /* Rub. Yields various snide remarks except for lit urn. */
1209 if (obj == URN && game.objects[URN].prop == URN_LIT) {
1211 drop(AMBER, game.loc);
1212 game.objects[AMBER].prop = AMBER_IN_ROCK;
1214 drop(CAVITY, game.loc);
1216 } else if (obj != LAMP) {
1217 rspeak(PECULIAR_NOTHING);
1219 speak(actions[verb].message);
1224 static phase_codes_t say(command_t command) {
1225 /* Say. Echo WD2. Magic words override. */
1226 if (command.word[1].type == MOTION &&
1227 (command.word[1].id == XYZZY || command.word[1].id == PLUGH ||
1228 command.word[1].id == PLOVER)) {
1231 if (command.word[1].type == ACTION && command.word[1].id == PART) {
1235 if (command.word[1].type == ACTION &&
1236 (command.word[1].id == FEE || command.word[1].id == FIE ||
1237 command.word[1].id == FOE || command.word[1].id == FOO ||
1238 command.word[1].id == FUM || command.word[1].id == PART)) {
1239 return bigwords(command.word[1].id);
1241 sspeak(OKEY_DOKEY, command.word[1].raw);
1245 static phase_codes_t throw_support(vocab_t spk) {
1247 drop(AXE, game.loc);
1251 static phase_codes_t throwit(command_t command) {
1252 /* Throw. Same as discard unless axe. Then same as attack except
1253 * ignore bird, and if dwarf is present then one might be killed.
1254 * (Only way to do so!) Axe also special for dragon, bear, and
1255 * troll. Treasures special for troll. */
1256 if (!TOTING(command.obj)) {
1257 speak(actions[command.verb].message);
1260 if (objects[command.obj].is_treasure && AT(TROLL)) {
1261 /* Snarf a treasure for the troll. */
1262 drop(command.obj, LOC_NOWHERE);
1263 move(TROLL, LOC_NOWHERE);
1264 move(TROLL + NOBJECTS, IS_FREE);
1265 drop(TROLL2, objects[TROLL].plac);
1266 drop(TROLL2 + NOBJECTS, objects[TROLL].fixd);
1268 rspeak(TROLL_SATISFIED);
1271 if (command.obj == FOOD && HERE(BEAR)) {
1272 /* But throwing food is another story. */
1274 return (feed(command.verb, command.obj));
1276 if (command.obj != AXE) {
1277 return (discard(command.verb, command.obj));
1279 if (atdwrf(game.loc) <= 0) {
1281 game.objects[DRAGON].prop == DRAGON_BARS) {
1282 return throw_support(DRAGON_SCALES);
1285 return throw_support(TROLL_RETURNS);
1288 return throw_support(OGRE_DODGE);
1291 game.objects[BEAR].prop == UNTAMED_BEAR) {
1292 /* This'll teach him to throw the axe at the
1294 drop(AXE, game.loc);
1295 game.objects[AXE].fixed = IS_FIXED;
1297 state_change(AXE, AXE_LOST);
1300 command.obj = INTRANSITIVE;
1301 return (attack(command));
1304 if (randrange(NDWARVES + 1) < game.dflag) {
1305 return throw_support(DWARF_DODGES);
1307 int i = atdwrf(game.loc);
1308 game.dwarves[i].seen = false;
1309 game.dwarves[i].loc = LOC_NOWHERE;
1310 return throw_support(
1311 (++game.dkill == 1) ? DWARF_SMOKE : KILLED_DWARF);
1316 static phase_codes_t wake(verb_t verb, obj_t obj) {
1317 /* Wake. Only use is to disturb the dwarves. */
1318 if (obj != DWARF || !game.closed) {
1319 speak(actions[verb].message);
1323 return GO_DWARFWAKE;
1327 static phase_codes_t seed(verb_t verb, const char *arg) {
1329 int32_t seed = strtol(arg, NULL, 10);
1330 speak(actions[verb].message, seed);
1336 static phase_codes_t waste(verb_t verb, turn_t turns) {
1338 game.limit -= turns;
1339 speak(actions[verb].message, (int)game.limit);
1343 static phase_codes_t wave(verb_t verb, obj_t obj) {
1344 /* Wave. No effect unless waving rod at fissure or at bird. */
1345 if (obj != ROD || !TOTING(obj) ||
1346 (!HERE(BIRD) && (game.closng || !AT(FISSURE)))) {
1347 speak(((!TOTING(obj)) && (obj != ROD || !TOTING(ROD2)))
1348 ? arbitrary_messages[ARENT_CARRYING]
1349 : actions[verb].message);
1353 if (game.objects[BIRD].prop == BIRD_UNCAGED &&
1354 game.loc == game.objects[STEPS].place && PROP_IS_NOTFOUND(JADE)) {
1355 drop(JADE, game.loc);
1356 PROP_SET_FOUND(JADE);
1358 rspeak(NECKLACE_FLY);
1362 rspeak((game.objects[BIRD].prop == BIRD_CAGED)
1365 return GO_DWARFWAKE;
1367 if (game.closng || !AT(FISSURE)) {
1368 rspeak((game.objects[BIRD].prop == BIRD_CAGED)
1374 rspeak((game.objects[BIRD].prop == BIRD_CAGED)
1379 state_change(FISSURE, game.objects[FISSURE].prop == BRIDGED
1386 phase_codes_t action(command_t command) {
1387 /* Analyse a verb. Remember what it was, go back for object if second
1388 * word unless verb is "say", which snarfs arbitrary second word.
1390 /* Previously, actions that result in a message, but don't do anything
1391 * further were called "specials". Now they're handled here as normal
1392 * actions. If noaction is true, then we spit out the message and return
1394 if (actions[command.verb].noaction) {
1395 speak(actions[command.verb].message);
1399 if (command.part == unknown) {
1400 /* Analyse an object word. See if the thing is here, whether
1401 * we've got a verb yet, and so on. Object must be here
1402 * unless verb is "find" or "invent(ory)" (and no new verb
1403 * yet to be analysed). Water and oil are also funny, since
1404 * they are never actually dropped at any location, but might
1405 * be here inside the bottle or urn or as a feature of the
1407 if (HERE(command.obj)) {
1409 } else if (command.obj == DWARF && atdwrf(game.loc) > 0) {
1411 } else if (!game.closed &&
1412 ((LIQUID() == command.obj && HERE(BOTTLE)) ||
1413 command.obj == LIQLOC(game.loc))) {
1415 } else if (command.obj == OIL && HERE(URN) &&
1416 game.objects[URN].prop != URN_EMPTY) {
1419 } else if (command.obj == PLANT && AT(PLANT2) &&
1420 game.objects[PLANT2].prop != PLANT_THIRSTY) {
1421 command.obj = PLANT2;
1423 } else if (command.obj == KNIFE && game.knfloc == game.loc) {
1425 rspeak(KNIVES_VANISH);
1427 } else if (command.obj == ROD && HERE(ROD2)) {
1430 } else if ((command.verb == FIND ||
1431 command.verb == INVENTORY) &&
1432 (command.word[1].id == WORD_EMPTY ||
1433 command.word[1].id == WORD_NOT_FOUND)) {
1436 sspeak(NO_SEE, command.word[0].raw);
1440 if (command.verb != 0) {
1441 command.part = transitive;
1445 switch (command.part) {
1447 if (command.word[1].raw[0] != '\0' && command.verb != SAY) {
1450 if (command.verb == SAY) {
1451 /* KEYS is not special, anything not NO_OBJECT or
1452 * INTRANSITIVE will do here. We're preventing
1453 * interpretation as an intransitive verb when the word
1456 command.word[1].raw[0] != '\0' ? KEYS : NO_OBJECT;
1458 if (command.obj == NO_OBJECT || command.obj == INTRANSITIVE) {
1459 /* Analyse an intransitive verb (ie, no object given
1461 switch (command.verb) {
1463 return vcarry(command.verb, INTRANSITIVE);
1469 return lock(command.verb, INTRANSITIVE);
1472 return (GO_CLEAROBJ);
1475 return lock(command.verb, INTRANSITIVE);
1477 return light(command.verb, INTRANSITIVE);
1479 return extinguish(command.verb, INTRANSITIVE);
1485 speak(actions[command.verb].message);
1489 command.obj = INTRANSITIVE;
1490 return attack(command);
1492 return pour(command.verb, INTRANSITIVE);
1494 return eat(command.verb, INTRANSITIVE);
1496 return drink(command.verb, INTRANSITIVE);
1510 return fill(command.verb, INTRANSITIVE);
1522 return bigwords(command.word[0].id);
1526 command.obj = INTRANSITIVE;
1527 return read(command);
1537 return fly(command.verb, INTRANSITIVE);
1544 rspeak(NUMERIC_REQUIRED);
1546 default: // LCOV_EXCL_LINE
1547 BUG(INTRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE
1552 /* Analyse a transitive verb. */
1553 switch (command.verb) {
1555 return vcarry(command.verb, command.obj);
1557 return discard(command.verb, command.obj);
1559 return say(command);
1561 return lock(command.verb, command.obj);
1564 return (GO_CLEAROBJ);
1567 return lock(command.verb, command.obj);
1569 return light(command.verb, command.obj);
1571 return extinguish(command.verb, command.obj);
1573 return wave(command.verb, command.obj);
1575 speak(actions[command.verb].message);
1579 speak(actions[command.verb].message);
1583 return attack(command);
1585 return pour(command.verb, command.obj);
1587 return eat(command.verb, command.obj);
1589 return drink(command.verb, command.obj);
1591 return rub(command.verb, command.obj);
1593 return throwit(command);
1595 speak(actions[command.verb].message);
1598 return find(command.verb, command.obj);
1600 return find(command.verb, command.obj);
1602 return feed(command.verb, command.obj);
1604 return fill(command.verb, command.obj);
1609 speak(actions[command.verb].message);
1616 speak(actions[command.verb].message);
1619 speak(actions[command.verb].message);
1622 return read(command);
1624 return vbreak(command.verb, command.obj);
1626 return wake(command.verb, command.obj);
1628 speak(actions[command.verb].message);
1631 speak(actions[command.verb].message);
1634 return fly(command.verb, command.obj);
1636 speak(actions[command.verb].message);
1639 // This case should never happen - here only as placeholder
1644 return seed(command.verb, command.word[1].raw);
1646 return waste(command.verb,
1647 (turn_t)atol(command.word[1].raw));
1648 default: // LCOV_EXCL_LINE
1649 BUG(TRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE
1652 /* Unknown verb, couldn't deduce object - might need hint */
1653 sspeak(WHAT_DO, command.word[0].raw);
1654 return GO_CHECKHINT;
1655 default: // LCOV_EXCL_LINE
1656 BUG(SPEECHPART_NOT_TRANSITIVE_OR_INTRANSITIVE_OR_UNKNOWN); // LCOV_EXCL_LINE