/* * There used to be a note that said this: * * The author - Don Woods - apologises for the style of the code; it * is a result of running the original Fortran IV source through a * home-brew Fortran-to-C converter. * * Now that the code has been restructured into something much closer * to idiomatic C, the following is more appropriate: * * ESR apologizes for the remaing gotos (now confined to one function * in this file - there used to be over 350 of them, *everywhere*). * Applying the Structured Program Theorem can be hard. * * 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 #include #include #include #include #include #include #include #include /* Generated from adventure.yaml - do not hand-hack! */ #ifndef DUNGEON_H #define DUNGEON_H #include #include #define SILENT -1 /* no sound */ /* Symbols for cond bits */ #define COND_LIT 0 /* Light */ #define COND_OILY 1 /* If bit 2 is on: on for oil, off for water */ #define COND_FLUID 2 /* Liquid asset, see bit 1 */ #define COND_NOARRR 3 /* Pirate doesn't go here unless following */ #define COND_NOBACK 4 /* Cannot use "back" to move away */ #define COND_ABOVE 5 #define COND_DEEP 6 /* Deep - e.g where dwarves are active */ #define COND_FOREST 7 /* In the forest */ #define COND_FORCED 8 /* Only one way in or out of here */ /* Bits past 10 indicate areas of interest to "hint" routines */ #define COND_HBASE 10 /* Base for location hint bits */ #define COND_HCAVE 11 /* Trying to get into cave */ #define COND_HBIRD 12 /* Trying to catch bird */ #define COND_HSNAKE 13 /* Trying to deal with snake */ #define COND_HMAZE 14 /* Lost in maze */ #define COND_HDARK 15 /* Pondering dark room */ #define COND_HWITT 16 /* At Witt's End */ #define COND_HCLIFF 17 /* Cliff with urn */ #define COND_HWOODS 18 /* Lost in forest */ #define COND_HOGRE 19 /* Trying to deal with ogre */ #define COND_HJADE 20 /* Found all treasures except jade */ typedef struct { const char** strs; const int n; } string_group_t; typedef struct { const string_group_t words; const char* inventory; int plac, fixd; bool is_treasure; const char** descriptions; const char** sounds; const char** texts; const char** changes; } object_t; typedef struct { const char* small; const char* big; } descriptions_t; typedef struct { descriptions_t description; const long sound; const bool loud; } location_t; typedef struct { const char* query; const char* yes_response; } obituary_t; typedef struct { const int threshold; const int point_loss; const char* message; } turn_threshold_t; typedef struct { const int threshold; const char* message; } class_t; typedef struct { const int number; const int turns; const int penalty; const char* question; const char* hint; } hint_t; typedef struct { const string_group_t words; } motion_t; typedef struct { const string_group_t words; const char* message; const bool noaction; } action_t; enum condtype_t {cond_goto, cond_pct, cond_carry, cond_with, cond_not}; enum desttype_t {dest_goto, dest_special, dest_speak}; typedef struct { const long motion; const long condtype; const long condarg1; const long condarg2; const enum desttype_t desttype; const long destval; const bool nodwarves; const bool stop; } travelop_t; /* Abstract out the encoding of words in the travel array. Gives us * some hope of getting to a less cryptic representation than we * inherited from FORTRAN, someday. To understand these, read the * encoding description for travel. */ #define T_TERMINATE(entry) ((entry).motion == 1) extern const location_t locations[]; extern const object_t objects[]; extern const char* arbitrary_messages[]; extern const class_t classes[]; extern const turn_threshold_t turn_thresholds[]; extern const obituary_t obituaries[]; extern const hint_t hints[]; extern long conditions[]; extern const motion_t motions[]; extern const action_t actions[]; extern const travelop_t travel[]; extern const long tkey[]; extern const char *ignore; #define NLOCATIONS 184 #define NOBJECTS 69 #define NHINTS 10 #define NCLASSES 10 #define NDEATHS 3 #define NTHRESHOLDS 4 #define NMOTIONS 76 #define NACTIONS 58 #define NTRAVEL 878 #define NKEYS 185 #define BIRD_ENDSTATE 5 enum arbitrary_messages_refs { NO_MESSAGE, CAVE_NEARBY, DWARF_BLOCK, DWARF_RAN, DWARF_PACK, DWARF_SINGLE, KNIFE_THROWN, SAYS_PLUGH, GETS_YOU, MISSES_YOU, UNSURE_FACING, NO_INOUT_HERE, CANT_APPLY, AM_GAME, NO_MORE_DETAIL, PITCH_DARK, W_IS_WEST, REALLY_QUIT, PIT_FALL, ALREADY_CARRYING, YOU_JOKING, BIRD_EVADES, CANNOT_CARRY, NOTHING_LOCKED, ARENT_CARRYING, BIRD_ATTACKS, NO_KEYS, NO_LOCK, NOT_LOCKABLE, ALREADY_LOCKED, ALREADY_UNLOCKED, BEAR_BLOCKS, NOTHING_HAPPENS, WHERE_QUERY, NO_TARGET, BIRD_DEAD, SNAKE_WARNING, KILLED_DWARF, DWARF_DODGES, BARE_HANDS_QUERY, BAD_DIRECTION, TWO_WORDS, OK_MAN, CANNOT_UNLOCK, FUTILE_CRAWL, FOLLOW_STREAM, NEED_DETAIL, NEARBY, OGRE_SNARL, HUH_MAN, WELCOME_YOU, REQUIRES_DYNAMITE, FEET_WET, LOST_APPETITE, THANKS_DELICIOUS, PECULIAR_NOTHING, GROUND_WET, CANT_POUR, WHICH_WAY, FORGOT_PATH, CARRY_LIMIT, GRATE_NOWAY, YOU_HAVEIT, DONT_FIT, CROSS_BRIDGE, NO_CROSS, NO_CARRY, NOW_HOLDING, BIRD_PINING, BIRD_DEVOURED, NOTHING_EDIBLE, REALLY_MAD, NO_CONTAINER, BOTTLE_FULL, NO_LIQUID, RIDICULOUS_ATTEMPT, RUSTY_DOOR, SHAKING_LEAVES, DEEP_ROOTS, KNIVES_VANISH, MUST_DROP, CLAM_BLOCKER, OYSTER_BLOCKER, DROP_OYSTER, CLAM_OPENER, OYSTER_OPENER, PEARL_FALLS, OYSTER_OPENS, WAY_BLOCKED, PIRATE_RUSTLES, PIRATE_POUNCES, CAVE_CLOSING, EXIT_CLOSED, DEATH_CLOSING, CAVE_CLOSED, VICTORY_MESSAGE, DEFEAT_MESSAGE, SPLATTER_MESSAGE, DWARVES_AWAKEN, UNHAPPY_BIRD, NEEDED_NEARBY, NOT_CONNECTED, TAME_BEAR, WITHOUT_SUSPENDS, FILL_INVALID, SHATTER_VASE, BEYOND_POWER, NOT_KNOWHOW, TOO_FAR, DWARF_SMOKE, SHELL_IMPERVIOUS, START_OVER, WELL_POINTLESS, DRAGON_SCALES, NASTY_DRAGON, BIRD_BURNT, BRIEF_CONFIRM, ROCKY_TROLL, TROLL_RETURNS, TROLL_SATISFIED, TROLL_BLOCKS, BRIDGE_GONE, BEAR_HANDS, BEAR_CONFUSED, ALREADY_DEAD, BEAR_CHAINED, STILL_LOCKED, CHAIN_UNLOCKED, CHAIN_LOCKED, NO_LOCKSITE, WANT_HINT, TROLL_VICES, LAMP_DIM, LAMP_OUT, PLEASE_ANSWER, PIRATE_SPOTTED, GET_BATTERIES, REPLACE_BATTERIES, MISSING_BATTERIES, REMOVE_MESSAGE, CLUE_QUERY, WAYOUT_CLUE, DONT_UNDERSTAND, HAND_PASSTHROUGH, PROD_DWARF, THIS_ACCEPTABLE, OGRE_FULL, OGRE_DODGE, OGRE_PANIC1, OGRE_PANIC2, FREE_FLY, CAGE_FLY, NECKLACE_FLY, WATER_URN, OIL_URN, FULL_URN, URN_NOPOUR, URN_NOBUDGE, URN_GENIES, DOUGHNUT_HOLES, GEM_FITS, RUG_RISES, RUG_WIGGLES, RUG_SETTLES, RUG_HOVERS, RUG_NOTHING1, RUG_NOTHING2, FLAP_ARMS, RUG_GOES, RUG_RETURNS, ALL_SILENT, STREAM_GURGLES, WIND_WHISTLES, STREAM_SPLASHES, NO_MEANING, MURMURING_SNORING, SNAKES_HISSING, DULL_RUMBLING, LOUD_ROAR, TOTAL_ROAR, BIRD_CRAP, FEW_DROPS, NOT_BRIGHT, TOOK_LONG, UPSTREAM_DOWNSTREAM, FOREST_QUERY, WATERS_CRASHING, THROWN_KNIVES, MULTIPLE_HITS, ONE_HIT, NONE_HIT, DONT_KNOW, WHAT_DO, NO_SEE, DO_WHAT, OKEY_DOKEY, GARNERED_POINTS, SUSPEND_WARNING, HINT_COST, TOTAL_SCORE, NEXT_HIGHER, NO_HIGHER, OFF_SCALE, RESUME_HELP, RESUME_ABANDON, VERSION_SKEW, TWIST_TURN, GO_UNNEEDED, NUMERIC_REQUIRED, }; enum locations_refs { LOC_NOWHERE, LOC_START, LOC_HILL, LOC_BUILDING, LOC_VALLEY, LOC_ROADEND, LOC_CLIFF, LOC_SLIT, LOC_GRATE, LOC_BELOWGRATE, LOC_COBBLE, LOC_DEBRIS, LOC_AWKWARD, LOC_BIRD, LOC_PITTOP, LOC_MISTHALL, LOC_CRACK, LOC_EASTBANK, LOC_NUGGET, LOC_KINGHALL, LOC_NECKBROKE, LOC_NOMAKE, LOC_DOME, LOC_WESTEND, LOC_EASTPIT, LOC_WESTPIT, LOC_CLIMBSTALK, LOC_WESTBANK, LOC_FLOORHOLE, LOC_SOUTHSIDE, LOC_WESTSIDE, LOC_BUILDING1, LOC_SNAKEBLOCK, LOC_Y2, LOC_JUMBLE, LOC_WINDOW1, LOC_BROKEN, LOC_SMALLPITBRINK, LOC_SMALLPIT, LOC_DUSTY, LOC_PARALLEL1, LOC_MISTWEST, LOC_ALIKE1, LOC_ALIKE2, LOC_ALIKE3, LOC_ALIKE4, LOC_DEADEND1, LOC_DEADEND2, LOC_DEADEND3, LOC_ALIKE5, LOC_ALIKE6, LOC_ALIKE7, LOC_ALIKE8, LOC_ALIKE9, LOC_DEADEND4, LOC_ALIKE10, LOC_DEADEND5, LOC_PITBRINK, LOC_DEADEND6, LOC_PARALLEL2, LOC_LONGEAST, LOC_LONGWEST, LOC_CROSSOVER, LOC_DEADEND7, LOC_COMPLEX, LOC_BEDQUILT, LOC_SWISSCHEESE, LOC_EASTEND, LOC_SLAB, LOC_SECRET1, LOC_SECRET2, LOC_THREEJUNCTION, LOC_LOWROOM, LOC_DEADCRAWL, LOC_SECRET3, LOC_WIDEPLACE, LOC_TIGHTPLACE, LOC_TALL, LOC_BOULDERS1, LOC_SEWER, LOC_ALIKE11, LOC_DEADEND8, LOC_DEADEND9, LOC_ALIKE12, LOC_ALIKE13, LOC_DEADEND10, LOC_DEADEND11, LOC_ALIKE14, LOC_NARROW, LOC_NOCLIMB, LOC_PLANTTOP, LOC_INCLINE, LOC_GIANTROOM, LOC_CAVEIN, LOC_IMMENSE, LOC_WATERFALL, LOC_SOFTROOM, LOC_ORIENTAL, LOC_MISTY, LOC_ALCOVE, LOC_PLOVER, LOC_DARKROOM, LOC_ARCHED, LOC_SHELLROOM, LOC_SLOPING1, LOC_CULDESAC, LOC_ANTEROOM, LOC_DIFFERENT1, LOC_WITTSEND, LOC_MIRRORCANYON, LOC_WINDOW2, LOC_TOPSTALACTITE, LOC_DIFFERENT2, LOC_RESERVOIR, LOC_DEADEND12, LOC_NE, LOC_SW, LOC_SWCHASM, LOC_WINDING, LOC_SECRET4, LOC_SECRET5, LOC_SECRET6, LOC_NECHASM, LOC_CORRIDOR, LOC_FORK, LOC_WARMWALLS, LOC_BREATHTAKING, LOC_BOULDERS2, LOC_LIMESTONE, LOC_BARRENFRONT, LOC_BARRENROOM, LOC_DIFFERENT3, LOC_DIFFERENT4, LOC_DIFFERENT5, LOC_DIFFERENT6, LOC_DIFFERENT7, LOC_DIFFERENT8, LOC_DIFFERENT9, LOC_DIFFERENT10, LOC_DIFFERENT11, LOC_DEADEND13, LOC_ROUGHHEWN, LOC_BADDIRECTION, LOC_LARGE, LOC_STOREROOM, LOC_FOREST1, LOC_FOREST2, LOC_FOREST3, LOC_FOREST4, LOC_FOREST5, LOC_FOREST6, LOC_FOREST7, LOC_FOREST8, LOC_FOREST9, LOC_FOREST10, LOC_FOREST11, LOC_FOREST12, LOC_FOREST13, LOC_FOREST14, LOC_FOREST15, LOC_FOREST16, LOC_FOREST17, LOC_FOREST18, LOC_FOREST19, LOC_FOREST20, LOC_FOREST21, LOC_FOREST22, LOC_LEDGE, LOC_RESBOTTOM, LOC_RESNORTH, LOC_TREACHEROUS, LOC_STEEP, LOC_CLIFFBASE, LOC_CLIFFACE, LOC_FOOTSLIP, LOC_CLIFFTOP, LOC_CLIFFLEDGE, LOC_REACHDEAD, LOC_GRUESOME, LOC_FOOF1, LOC_FOOF2, LOC_FOOF3, LOC_FOOF4, LOC_FOOF5, LOC_FOOF6, }; enum object_refs { NO_OBJECT, KEYS, LAMP, GRATE, CAGE, ROD, ROD2, STEPS, BIRD, DOOR, PILLOW, SNAKE, FISSURE, OBJ_13, CLAM, OYSTER, MAGAZINE, DWARF, KNIFE, FOOD, BOTTLE, WATER, OIL, MIRROR, PLANT, PLANT2, OBJ_26, OBJ_27, AXE, OBJ_29, OBJ_30, DRAGON, CHASM, TROLL, TROLL2, BEAR, MESSAG, VOLCANO, VEND, BATTERY, OBJ_40, OGRE, URN, CAVITY, BLOOD, RESER, OBJ_46, OBJ_47, OBJ_48, SIGN, NUGGET, OBJ_51, OBJ_52, OBJ_53, COINS, CHEST, EGGS, TRIDENT, VASE, EMERALD, PYRAMID, PEARL, RUG, OBJ_63, CHAIN, RUBY, JADE, AMBER, SAPPH, OBJ_69, }; enum motion_refs { MOT_0, HERE, MOT_2, ENTER, MOT_4, MOT_5, MOT_6, FORWARD, BACK, MOT_9, MOT_10, OUTSIDE, MOT_12, MOT_13, STREAM, MOT_15, MOT_16, CRAWL, MOT_18, INSIDE, MOT_20, NUL, MOT_22, MOT_23, MOT_24, MOT_25, MOT_26, MOT_27, MOT_28, UP, DOWN, MOT_31, MOT_32, MOT_33, MOT_34, MOT_35, LEFT, RIGHT, MOT_38, MOT_39, MOT_40, MOT_41, MOT_42, EAST, WEST, NORTH, SOUTH, NE, SE, SW, NW, MOT_51, MOT_52, MOT_53, MOT_54, MOT_55, MOT_56, LOOK, MOT_58, MOT_59, MOT_60, MOT_61, XYZZY, DEPRESSION, ENTRANCE, PLUGH, MOT_66, CAVE, CROSS, BEDQUILT, PLOVER, ORIENTAL, CAVERN, SHELLROOM, RESERVOIR, OFFICE, }; enum action_refs { ACT_NULL, CARRY, DROP, SAY, UNLOCK, NOTHING, LOCK, LIGHT, EXTINGUISH, WAVE, TAME, GO, ATTACK, POUR, EAT, DRINK, RUB, THROW, QUIT, FIND, INVENTORY, FEED, FILL, BLAST, SCORE, FEE, FIE, FOE, FOO, FUM, BRIEF, READ, BREAK, WAKE, SAVE, RESUME, FLY, LISTEN, PART, SEED, WASTE, ACT_UNKNOWN, THANKYOU, INVALIDMAGIC, HELP, False, TREE, DIG, LOST, MIST, FBOMB, STOP, INFO, SWIM, WIZARD, YES, NEWS, ACT_VERSION, }; /* State definitions */ /* States for LAMP */ #define LAMP_DARK 0 #define LAMP_BRIGHT 1 /* States for GRATE */ #define GRATE_CLOSED 0 #define GRATE_OPEN 1 /* States for STEPS */ #define STEPS_DOWN 0 #define STEPS_UP 1 /* States for BIRD */ #define BIRD_UNCAGED 0 #define BIRD_CAGED 1 #define BIRD_FOREST_UNCAGED 2 /* States for DOOR */ #define DOOR_RUSTED 0 #define DOOR_UNRUSTED 1 /* States for SNAKE */ #define SNAKE_BLOCKS 0 #define SNAKE_CHASED 1 /* States for FISSURE */ #define UNBRIDGED 0 #define BRIDGED 1 /* States for BOTTLE */ #define WATER_BOTTLE 0 #define EMPTY_BOTTLE 1 #define OIL_BOTTLE 2 /* States for MIRROR */ #define MIRROR_UNBROKEN 0 #define MIRROR_BROKEN 1 /* States for PLANT */ #define PLANT_THIRSTY 0 #define PLANT_BELLOWING 1 #define PLANT_GROWN 2 /* States for AXE */ #define AXE_HERE 0 #define AXE_LOST 1 /* States for DRAGON */ #define DRAGON_BARS 0 #define DRAGON_DEAD 1 #define DRAGON_BLOODLESS 2 /* States for CHASM */ #define TROLL_BRIDGE 0 #define BRIDGE_WRECKED 1 /* States for TROLL */ #define TROLL_UNPAID 0 #define TROLL_PAIDONCE 1 #define TROLL_GONE 2 /* States for BEAR */ #define UNTAMED_BEAR 0 #define SITTING_BEAR 1 #define CONTENTED_BEAR 2 #define BEAR_DEAD 3 /* States for VEND */ #define VEND_BLOCKS 0 #define VEND_UNBLOCKS 1 /* States for BATTERY */ #define FRESH_BATTERIES 0 #define DEAD_BATTERIES 1 /* States for URN */ #define URN_EMPTY 0 #define URN_DARK 1 #define URN_LIT 2 /* States for CAVITY */ #define CAVITY_FULL 0 #define CAVITY_EMPTY 1 /* States for RESER */ #define WATERS_UNPARTED 0 #define WATERS_PARTED 1 /* States for SIGN */ #define INGAME_SIGN 0 #define ENDGAME_SIGN 1 /* States for EGGS */ #define EGGS_HERE 0 #define EGGS_VANISHED 1 #define EGGS_DONE 2 /* States for VASE */ #define VASE_WHOLE 0 #define VASE_DROPPED 1 #define VASE_BROKEN 2 /* States for RUG */ #define RUG_FLOOR 0 #define RUG_DRAGON 1 #define RUG_HOVER 2 /* States for CHAIN */ #define CHAIN_HEAP 0 #define CHAINING_BEAR 1 #define CHAIN_FIXED 2 /* States for AMBER */ #define AMBER_IN_URN 0 #define AMBER_IN_ROCK 1 #endif /* end DUNGEON_H */ /* LCG PRNG parameters tested against * Knuth vol. 2. by the original authors */ #define LCG_A 1093L #define LCG_C 221587L #define LCG_M 1048576L #define LINESIZE 1024 #define TOKLEN 5 // № sigificant characters in a token */ #define NDWARVES 6 // number of dwarves #define PIRATE NDWARVES // must be NDWARVES-1 when zero-origin #define DALTLC LOC_NUGGET // alternate dwarf location #define INVLIMIT 7 // inverntory limit (№ of objects) #define INTRANSITIVE -1 // illegal object number #define GAMELIMIT 330 // base limit of turns #define NOVICELIMIT 1000 // limit of turns for novice #define WARNTIME 30 // late game starts at game.limit-this #define FLASHTIME 50 // turns from first warning till blinding flash #define PANICTIME 15 // time left after closing #define BATTERYLIFE 2500 // turn limit increment from batteries #define WORD_NOT_FOUND -1 // "Word not found" flag value for the vocab hash functions. #define WORD_EMPTY 0 // "Word empty" flag value for the vocab hash functions #define CARRIED -1 // Player is toting it #define READ_MODE "rb" // b is not needed for POSIX but harmless #define WRITE_MODE "wb" // b is not needed for POSIX but harmless /* Special object-state values - integers > 0 are object-specific */ #define STATE_NOTFOUND -1 // 'Not found" state of treasures */ #define STATE_FOUND 0 // After discovered, before messed with #define STATE_IN_CAVITY 1 // State value common to all gemstones /* Special fixed object-state values - integers > 0 are location */ #define IS_FIXED -1 #define IS_FREE 0 /* Map a state property value to a negative range, where the object cannot be * picked up but the value can be recovered later. Avoid colliding with -1, * which has its own meaning. */ #define STASHED(obj) (-1 - game.prop[obj]) /* * MOD(N,M) = Arithmetic modulus * AT(OBJ) = true if on either side of two-placed object * CNDBIT(L,N) = true if COND(L) has bit n set (bit 0 is units bit) * DARK(LOC) = true if location "LOC" is dark * FORCED(LOC) = true if LOC moves without asking for input (COND=2) * FOREST(LOC) = true if LOC is part of the forest * GSTONE(OBJ) = true if OBJ is a gemstone * HERE(OBJ) = true if the OBJ is at "LOC" (or is being carried) * LIQUID() = object number of liquid in bottle * LIQLOC(LOC) = object number of liquid (if any) at LOC * PCT(N) = true N% of the time (N integer from 0 to 100) * TOTING(OBJ) = true if the OBJ is being carried */ #define DESTROY(N) move(N, LOC_NOWHERE) #define MOD(N,M) ((N) % (M)) #define TOTING(OBJ) (game.place[OBJ] == CARRIED) #define AT(OBJ) (game.place[OBJ] == game.loc || game.fixed[OBJ] == game.loc) #define HERE(OBJ) (AT(OBJ) || TOTING(OBJ)) #define CNDBIT(L,N) (tstbit(conditions[L],N)) #define LIQUID() (game.prop[BOTTLE] == WATER_BOTTLE? WATER : game.prop[BOTTLE] == OIL_BOTTLE ? OIL : NO_OBJECT ) #define LIQLOC(LOC) (CNDBIT((LOC),COND_FLUID)? CNDBIT((LOC),COND_OILY) ? OIL : WATER : NO_OBJECT) #define FORCED(LOC) CNDBIT(LOC, COND_FORCED) #define DARK(DUMMY) (!CNDBIT(game.loc,COND_LIT) && (game.prop[LAMP] == LAMP_DARK || !HERE(LAMP))) #define PCT(N) (randrange(100) < (N)) #define GSTONE(OBJ) ((OBJ) == EMERALD || (OBJ) == RUBY || (OBJ) == AMBER || (OBJ) == SAPPH) #define FOREST(LOC) CNDBIT(LOC, COND_FOREST) #define OUTSID(LOC) (CNDBIT(LOC, COND_ABOVE) || FOREST(LOC)) #define INSIDE(LOC) (!OUTSID(LOC) || LOC == LOC_BUILDING) #define INDEEP(LOC) ((LOC) >= LOC_MISTHALL && !OUTSID(LOC)) #define BUG(x) bug(x, #x) enum bugtype { SPECIAL_TRAVEL_500_GT_L_GT_300_EXCEEDS_GOTO_LIST, VOCABULARY_TYPE_N_OVER_1000_NOT_BETWEEN_0_AND_3, INTRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST, TRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST, CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION, LOCATION_HAS_NO_TRAVEL_ENTRIES, HINT_NUMBER_EXCEEDS_GOTO_LIST, SPEECHPART_NOT_TRANSITIVE_OR_INTRANSITIVE_OR_UNKNOWN, ACTION_RETURNED_PHASE_CODE_BEYOND_END_OF_SWITCH, }; enum speaktype {touch, look, hear, study, change}; enum termination {endgame, quitgame, scoregame}; enum speechpart {unknown, intransitive, transitive}; typedef enum {NO_WORD_TYPE, MOTION, OBJECT, ACTION, NUMERIC} word_type_t; typedef enum scorebonus {none, splatter, defeat, victory} score_t; /* Phase codes for action returns. * These were at one time FORTRAN line numbers. * The values don't matter, but perturb their order at your peril. */ enum phase_codes { GO_TERMINATE, GO_MOVE, GO_TOP, GO_CLEAROBJ, GO_WORD2, GO_UNKNOWN, GO_DWARFWAKE, }; typedef long vocab_t; // index into a vocabulary array */ typedef long verb_t; // index into an actions array */ typedef long obj_t; // index into the object array */ typedef long loc_t; // index into the locations array */ typedef long turn_t; // turn counter or threshold */ struct game_t { int32_t lcg_x; long abbnum; // How often to print long descriptions score_t bonus; // What kind of finishing bonus we are getting loc_t chloc; // pirate chest location loc_t chloc2; // pirate chest alternate location turn_t clock1; // # turns from finding last treasure to close turn_t clock2; // # turns from warning till blinding flash bool clshnt; // has player read the clue in the endgame? bool closed; // whether we're all the way closed bool closng; // whether it's closing time yet bool lmwarn; // has player been warned about lamp going dim? bool novice; // asked for instructions at start-up? bool panic; // has player found out he's trapped? bool wzdark; // whether the loc he's leaving was dark bool blooded; // has player drunk of dragon's blood? long conds; // min value for cond[loc] if loc has any hints long detail; // level of detail in descriptions /* dflag controls the level of activation of dwarves: * 0 No dwarf stuff yet (wait until reaches Hall Of Mists) * 1 Reached Hall Of Mists, but hasn't met first dwarf * 2 Met first dwarf, others start moving, no knives thrown yet * 3 A knife has been thrown (first set always misses) * 3+ Dwarves are mad (increases their accuracy) */ long dflag; long dkill; // dwarves killed long dtotal; // total dwarves (including pirate) in loc long foobar; // progress in saying "FEE FIE FOE FOO". long holdng; // number of objects being carried long igo; // # uses of "go" instead of a direction long iwest; // # times he's said "west" instead of "w" long knfloc; // knife location; 0 if none, -1 after caveat turn_t limit; // lifetime of lamp loc_t loc; // where player is now loc_t newloc; // where player is going turn_t numdie; // number of times killed so far loc_t oldloc; // where player was loc_t oldlc2; // where player was two moves ago obj_t oldobj; // last object player handled long saved; // point penalty for saves long tally; // count of treasures gained long thresh; // current threshold for endgame scoring tier turn_t trndex; // FIXME: not used, remove on next format bump turn_t trnluz; // # points lost so far due to turns used turn_t turns; // counts commands given (ignores yes/no) char zzword[TOKLEN + 1]; // randomly generated magic word from bird long abbrev[NLOCATIONS + 1]; // has location been seen? long atloc[NLOCATIONS + 1]; // head of object linked list per location long dseen[NDWARVES + 1]; // true if dwarf has seen him loc_t dloc[NDWARVES + 1]; // location of dwarves, initially hard-wired in loc_t odloc[NDWARVES + 1]; // prior loc of each dwarf, initially garbage loc_t fixed[NOBJECTS + 1]; // fixed location of object (if not IS_FREE) obj_t link[NOBJECTS * 2 + 1]; // object-list links loc_t place[NOBJECTS + 1]; // location of object long hinted[NHINTS]; // hinted[i] = true iff hint i has been used. long hintlc[NHINTS]; // hintlc[i] = how long at LOC with cond bit i long prop[NOBJECTS + 1]; // object state array */ }; /* * Game application settings - settings, but not state of the game, per se. * This data is not saved in a saved game. */ struct settings_t { FILE *logfp; bool oldstyle; bool prompt; }; typedef struct { char raw[LINESIZE]; vocab_t id; word_type_t type; } command_word_t; typedef struct { enum speechpart part; command_word_t word[2]; verb_t verb; obj_t obj; } command_t; extern struct game_t game; extern struct settings_t settings; extern bool get_command_input(command_t *); extern void speak(const char*, ...); extern void sspeak(int msg, ...); extern void pspeak(vocab_t, enum speaktype, int, bool, ...); extern void rspeak(vocab_t, ...); extern void echo_input(FILE*, const char*, const char*); extern bool silent_yes(void); extern bool yes(const char*, const char*, const char*); extern void juggle(obj_t); extern void move(obj_t, loc_t); extern loc_t put(obj_t, long, long); extern void carry(obj_t, loc_t); extern void drop(obj_t, loc_t); extern int atdwrf(loc_t); extern long setbit(int); extern bool tstbit(long, int); extern void set_seed(int32_t); extern int32_t randrange(int32_t); extern long score(enum termination); extern void terminate(enum termination) __attribute__((noreturn)); extern int savefile(FILE *, int32_t); extern int suspend(void); extern int resume(void); extern int restore(FILE *); extern long initialise(void); extern int action(command_t command); extern void state_change(obj_t, int); void bug(enum bugtype, const char *) __attribute__((__noreturn__)); /* represent an empty command word */ static const command_word_t empty_command_word = { .raw = "", .id = WORD_EMPTY, .type = NO_WORD_TYPE, }; /* end */ #define DIM(a) (sizeof(a)/sizeof(a[0])) // LCOV_EXCL_START // exclude from coverage analysis because it requires interactivity to test static void sig_handler(int signo) { if (signo == SIGINT) { if (settings.logfp != NULL) fflush(settings.logfp); } exit(EXIT_FAILURE); } // LCOV_EXCL_STOP /* * MAIN PROGRAM * * Adventure (rev 2: 20 treasures) * History: Original idea & 5-treasure version (adventures) by Willie Crowther * 15-treasure version (adventure) by Don Woods, April-June 1977 * 20-treasure version (rev 2) by Don Woods, August 1978 * Errata fixed: 78/12/25 * Revived 2017 as Open Adventure. */ static bool do_command(void); int main(int argc, char *argv[]) { int ch; /* Options. */ #ifndef ADVENT_NOSAVE const char* opts = "l:or:"; const char* usage = "Usage: %s [-l logfilename] [-o] [-r restorefilename]\n"; FILE *rfp = NULL; #else const char* opts = "l:o"; const char* usage = "Usage: %s [-l logfilename] [-o]\n"; #endif while ((ch = getopt(argc, argv, opts)) != EOF) { switch (ch) { case 'l': settings.logfp = fopen(optarg, "w"); if (settings.logfp == NULL) fprintf(stderr, "advent: can't open logfile %s for write\n", optarg); signal(SIGINT, sig_handler); break; case 'o': settings.oldstyle = true; settings.prompt = false; break; #ifndef ADVENT_NOSAVE case 'r': rfp = fopen(optarg, "r"); if (rfp == NULL) fprintf(stderr, "advent: can't open save file %s for read\n", optarg); break; #endif default: fprintf(stderr, usage, argv[0]); fprintf(stderr, " -l create a log file of your game named as specified'\n"); fprintf(stderr, " -o 'oldstyle' (no prompt, no command editing, displays 'Initialising...')\n"); #ifndef ADVENT_NOSAVE fprintf(stderr, " -r restore from specified saved game file\n"); #endif exit(EXIT_FAILURE); break; } } /* Initialize game variables */ long seedval = initialise(); #ifndef ADVENT_NOSAVE if (!rfp) { game.novice = yes(arbitrary_messages[WELCOME_YOU], arbitrary_messages[CAVE_NEARBY], arbitrary_messages[NO_MESSAGE]); if (game.novice) game.limit = NOVICELIMIT; } else { restore(rfp); } #else game.novice = yes(arbitrary_messages[WELCOME_YOU], arbitrary_messages[CAVE_NEARBY], arbitrary_messages[NO_MESSAGE]); if (game.novice) game.limit = NOVICELIMIT; #endif if (settings.logfp) fprintf(settings.logfp, "seed %ld\n", seedval); /* interpret commands until EOF or interrupt */ for (;;) { if (!do_command()) break; } /* show score and exit */ terminate(quitgame); } /* Check if this loc is eligible for any hints. If been here long * enough, display. Ignore "HINTS" < 4 (special stuff, see database * notes). */ static void checkhints(void) { if (conditions[game.loc] >= game.conds) { for (int hint = 0; hint < NHINTS; hint++) { if (game.hinted[hint]) continue; if (!CNDBIT(game.loc, hint + 1 + COND_HBASE)) game.hintlc[hint] = -1; ++game.hintlc[hint]; /* Come here if he's been long enough at required loc(s) for some * unused hint. */ if (game.hintlc[hint] >= hints[hint].turns) { int i; switch (hint) { case 0: /* cave */ if (game.prop[GRATE] == GRATE_CLOSED && !HERE(KEYS)) break; game.hintlc[hint] = 0; return; case 1: /* bird */ if (game.place[BIRD] == game.loc && TOTING(ROD) && game.oldobj == BIRD) break; return; case 2: /* snake */ if (HERE(SNAKE) && !HERE(BIRD)) break; game.hintlc[hint] = 0; return; case 3: /* maze */ if (game.atloc[game.loc] == NO_OBJECT && game.atloc[game.oldloc] == NO_OBJECT && game.atloc[game.oldlc2] == NO_OBJECT && game.holdng > 1) break; game.hintlc[hint] = 0; return; case 4: /* dark */ if (game.prop[EMERALD] != STATE_NOTFOUND && game.prop[PYRAMID] == STATE_NOTFOUND) break; game.hintlc[hint] = 0; return; case 5: /* witt */ break; case 6: /* urn */ if (game.dflag == 0) break; game.hintlc[hint] = 0; return; case 7: /* woods */ if (game.atloc[game.loc] == NO_OBJECT && game.atloc[game.oldloc] == NO_OBJECT && game.atloc[game.oldlc2] == NO_OBJECT) break; return; case 8: /* ogre */ i = atdwrf(game.loc); if (i < 0) { game.hintlc[hint] = 0; return; } if (HERE(OGRE) && i == 0) break; return; case 9: /* jade */ if (game.tally == 1 && game.prop[JADE] < 0) break; game.hintlc[hint] = 0; return; default: // LCOV_EXCL_LINE BUG(HINT_NUMBER_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE } /* Fall through to hint display */ game.hintlc[hint] = 0; if (!yes(hints[hint].question, arbitrary_messages[NO_MESSAGE], arbitrary_messages[OK_MAN])) return; rspeak(HINT_COST, hints[hint].penalty, hints[hint].penalty); game.hinted[hint] = yes(arbitrary_messages[WANT_HINT], hints[hint].hint, arbitrary_messages[OK_MAN]); if (game.hinted[hint] && game.limit > WARNTIME) game.limit += WARNTIME * hints[hint].penalty; } } } } static bool spotted_by_pirate(int i) { if (i != PIRATE) return false; /* The pirate's spotted him. Pirate leaves him alone once we've * found chest. K counts if a treasure is here. If not, and * tally=1 for an unseen chest, let the pirate be spotted. Note * that game.place[CHEST] = LOC_NOWHERE might mean that he's thrown * it to the troll, but in that case he's seen the chest * (game.prop[CHEST] == STATE_FOUND). */ if (game.loc == game.chloc || game.prop[CHEST] != STATE_NOTFOUND) return true; int snarfed = 0; bool movechest = false, robplayer = false; for (int treasure = 1; treasure <= NOBJECTS; treasure++) { if (!objects[treasure].is_treasure) continue; /* Pirate won't take pyramid from plover room or dark * room (too easy!). */ if (treasure == PYRAMID && (game.loc == objects[PYRAMID].plac || game.loc == objects[EMERALD].plac)) { continue; } if (TOTING(treasure) || HERE(treasure)) ++snarfed; if (TOTING(treasure)) { movechest = true; robplayer = true; } } /* Force chest placement before player finds last treasure */ if (game.tally == 1 && snarfed == 0 && game.place[CHEST] == LOC_NOWHERE && HERE(LAMP) && game.prop[LAMP] == LAMP_BRIGHT) { rspeak(PIRATE_SPOTTED); movechest = true; } /* Do things in this order (chest move before robbery) so chest is listed * last at the maze location. */ if (movechest) { move(CHEST, game.chloc); move(MESSAG, game.chloc2); game.dloc[PIRATE] = game.chloc; game.odloc[PIRATE] = game.chloc; game.dseen[PIRATE] = false; } else { /* You might get a hint of the pirate's presence even if the * chest doesn't move... */ if (game.odloc[PIRATE] != game.dloc[PIRATE] && PCT(20)) rspeak(PIRATE_RUSTLES); } if (robplayer) { rspeak(PIRATE_POUNCES); for (int treasure = 1; treasure <= NOBJECTS; treasure++) { if (!objects[treasure].is_treasure) continue; if (!(treasure == PYRAMID && (game.loc == objects[PYRAMID].plac || game.loc == objects[EMERALD].plac))) { if (AT(treasure) && game.fixed[treasure] == IS_FREE) carry(treasure, game.loc); if (TOTING(treasure)) drop(treasure, game.chloc); } } } return true; } static bool dwarfmove(void) /* Dwarves move. Return true if player survives, false if he dies. */ { int kk, stick, attack; loc_t tk[21]; /* Dwarf stuff. See earlier comments for description of * variables. Remember sixth dwarf is pirate and is thus * very different except for motion rules. */ /* First off, don't let the dwarves follow him into a pit or a * wall. Activate the whole mess the first time he gets as far * as the Hall of Mists (what INDEEP() tests). If game.newloc * is forbidden to pirate (in particular, if it's beyond the * troll bridge), bypass dwarf stuff. That way pirate can't * steal return toll, and dwarves can't meet the bear. Also * means dwarves won't follow him into dead end in maze, but * c'est la vie. They'll wait for him outside the dead end. */ if (game.loc == LOC_NOWHERE || FORCED(game.loc) || CNDBIT(game.newloc, COND_NOARRR)) return true; /* Dwarf activity level ratchets up */ if (game.dflag == 0) { if (INDEEP(game.loc)) game.dflag = 1; return true; } /* When we encounter the first dwarf, we kill 0, 1, or 2 of * the 5 dwarves. If any of the survivors is at game.loc, * replace him with the alternate. */ if (game.dflag == 1) { if (!INDEEP(game.loc) || (PCT(95) && (!CNDBIT(game.loc, COND_NOBACK) || PCT(85)))) return true; game.dflag = 2; for (int i = 1; i <= 2; i++) { int j = 1 + randrange(NDWARVES - 1); if (PCT(50)) game.dloc[j] = 0; } /* Alternate initial loc for dwarf, in case one of them * starts out on top of the adventurer. */ for (int i = 1; i <= NDWARVES - 1; i++) { if (game.dloc[i] == game.loc) game.dloc[i] = DALTLC; // game.odloc[i] = game.dloc[i]; } rspeak(DWARF_RAN); drop(AXE, game.loc); return true; } /* Things are in full swing. Move each dwarf at random, * except if he's seen us he sticks with us. Dwarves stay * deep inside. If wandering at random, they don't back up * unless there's no alternative. If they don't have to * move, they attack. And, of course, dead dwarves don't do * much of anything. */ game.dtotal = 0; attack = 0; stick = 0; for (int i = 1; i <= NDWARVES; i++) { if (game.dloc[i] == 0) continue; /* Fill tk array with all the places this dwarf might go. */ unsigned int j = 1; kk = tkey[game.dloc[i]]; if (kk != 0) do { enum desttype_t desttype = travel[kk].desttype; game.newloc = travel[kk].destval; /* Have we avoided a dwarf encounter? */ if (desttype != dest_goto) continue; else if (!INDEEP(game.newloc)) continue; else if (game.newloc == game.odloc[i]) continue; else if (j > 1 && game.newloc == tk[j - 1]) continue; else if (j >= DIM(tk) - 1) /* This can't actually happen. */ continue; // LCOV_EXCL_LINE else if (game.newloc == game.dloc[i]) continue; else if (FORCED(game.newloc)) continue; else if (i == PIRATE && CNDBIT(game.newloc, COND_NOARRR)) continue; else if (travel[kk].nodwarves) continue; tk[j++] = game.newloc; } while (!travel[kk++].stop); tk[j] = game.odloc[i]; if (j >= 2) --j; j = 1 + randrange(j); game.odloc[i] = game.dloc[i]; game.dloc[i] = tk[j]; game.dseen[i] = (game.dseen[i] && INDEEP(game.loc)) || (game.dloc[i] == game.loc || game.odloc[i] == game.loc); if (!game.dseen[i]) continue; game.dloc[i] = game.loc; if (spotted_by_pirate(i)) continue; /* This threatening little dwarf is in the room with him! */ ++game.dtotal; if (game.odloc[i] == game.dloc[i]) { ++attack; if (game.knfloc >= 0) game.knfloc = game.loc; if (randrange(1000) < 95 * (game.dflag - 2)) ++stick; } } /* Now we know what's happening. Let's tell the poor sucker about it. */ if (game.dtotal == 0) return true; rspeak(game.dtotal == 1 ? DWARF_SINGLE : DWARF_PACK, game.dtotal); if (attack == 0) return true; if (game.dflag == 2) game.dflag = 3; if (attack > 1) { rspeak(THROWN_KNIVES, attack); rspeak(stick > 1 ? MULTIPLE_HITS : (stick == 1 ? ONE_HIT : NONE_HIT), stick); } else { rspeak(KNIFE_THROWN); rspeak(stick ? GETS_YOU : MISSES_YOU); } if (stick == 0) return true; game.oldlc2 = game.loc; return false; } /* "You're dead, Jim." * * If the current loc is zero, it means the clown got himself killed. * We'll allow this maxdie times. NDEATHS is automatically set based * on the number of snide messages available. Each death results in * a message (obituaries[n]) which offers reincarnation; if accepted, * this results in message obituaries[0], obituaries[2], etc. The * last time, if he wants another chance, he gets a snide remark as * we exit. When reincarnated, all objects being carried get dropped * at game.oldlc2 (presumably the last place prior to being killed) * without change of props. The loop runs backwards to assure that * the bird is dropped before the cage. (This kluge could be changed * once we're sure all references to bird and cage are done by * keywords.) The lamp is a special case (it wouldn't do to leave it * in the cave). It is turned off and left outside the building (only * if he was carrying it, of course). He himself is left inside the * building (and heaven help him if he tries to xyzzy back into the * cave without the lamp!). game.oldloc is zapped so he can't just * "retreat". */ static void croak(void) /* Okay, he's dead. Let's get on with it. */ { if (game.numdie < 0) game.numdie = 0; const char* query = obituaries[game.numdie].query; const char* yes_response = obituaries[game.numdie].yes_response; ++game.numdie; if (game.closng) { /* He died during closing time. No resurrection. Tally up a * death and exit. */ rspeak(DEATH_CLOSING); terminate(endgame); } else if ( !yes(query, yes_response, arbitrary_messages[OK_MAN]) || game.numdie == NDEATHS) terminate(endgame); else { game.place[WATER] = game.place[OIL] = LOC_NOWHERE; if (TOTING(LAMP)) game.prop[LAMP] = LAMP_DARK; for (int j = 1; j <= NOBJECTS; j++) { int i = NOBJECTS + 1 - j; if (TOTING(i)) { /* Always leave lamp where it's accessible aboveground */ drop(i, (i == LAMP) ? LOC_START : game.oldlc2); } } game.oldloc = game.loc = game.newloc = LOC_BUILDING; } } static bool traveleq(int a, int b) /* Are two travel entries equal for purposes of skip after failed condition? */ { return (travel[a].condtype == travel[b].condtype) && (travel[a].condarg1 == travel[b].condarg1) && (travel[a].condarg2 == travel[b].condarg2) && (travel[a].desttype == travel[b].desttype) && (travel[a].destval == travel[b].destval); } /* Given the current location in "game.loc", and a motion verb number in * "motion", put the new location in "game.newloc". The current loc is saved * in "game.oldloc" in case he wants to retreat. The current * game.oldloc is saved in game.oldlc2, in case he dies. (if he * does, game.newloc will be limbo, and game.oldloc will be what killed * him, so we need game.oldlc2, which is the last place he was * safe.) */ static void playermove( int motion) { int scratchloc, travel_entry = tkey[game.loc]; game.newloc = game.loc; if (travel_entry == 0) BUG(LOCATION_HAS_NO_TRAVEL_ENTRIES); // LCOV_EXCL_LINE if (motion == NUL) return; else if (motion == BACK) { /* Handle "go back". Look for verb which goes from game.loc to * game.oldloc, or to game.oldlc2 If game.oldloc has forced-motion. * te_tmp saves entry -> forced loc -> previous loc. */ motion = game.oldloc; if (FORCED(motion)) motion = game.oldlc2; game.oldlc2 = game.oldloc; game.oldloc = game.loc; if (CNDBIT(game.loc, COND_NOBACK)) { rspeak(TWIST_TURN); return; } if (motion == game.loc) { rspeak(FORGOT_PATH); return; } int te_tmp = 0; for (;;) { enum desttype_t desttype = travel[travel_entry].desttype; scratchloc = travel[travel_entry].destval; if (desttype != dest_goto || scratchloc != motion) { if (desttype == dest_goto) { if (FORCED(scratchloc) && travel[tkey[scratchloc]].destval == motion) te_tmp = travel_entry; } if (!travel[travel_entry].stop) { ++travel_entry; /* go to next travel entry for this location */ continue; } /* we've reached the end of travel entries for game.loc */ travel_entry = te_tmp; if (travel_entry == 0) { rspeak(NOT_CONNECTED); return; } } motion = travel[travel_entry].motion; travel_entry = tkey[game.loc]; break; /* fall through to ordinary travel */ } } else if (motion == LOOK) { /* Look. Can't give more detail. Pretend it wasn't dark * (though it may now be dark) so he won't fall into a * pit while staring into the gloom. */ if (game.detail < 3) rspeak(NO_MORE_DETAIL); ++game.detail; game.wzdark = false; game.abbrev[game.loc] = 0; return; } else if (motion == CAVE) { /* Cave. Different messages depending on whether above ground. */ rspeak((OUTSID(game.loc) && game.loc != LOC_GRATE) ? FOLLOW_STREAM : NEED_DETAIL); return; } else { /* none of the specials */ game.oldlc2 = game.oldloc; game.oldloc = game.loc; } /* Look for a way to fulfil the motion verb passed in - travel_entry indexes * the beginning of the motion entries for here (game.loc). */ for (;;) { if (T_TERMINATE(travel[travel_entry]) || travel[travel_entry].motion == motion) break; if (travel[travel_entry].stop) { /* Couldn't find an entry matching the motion word passed * in. Various messages depending on word given. */ switch (motion) { case EAST: case WEST: case SOUTH: case NORTH: case NE: case NW: case SW: case SE: case UP: case DOWN: rspeak(BAD_DIRECTION); break; case FORWARD: case LEFT: case RIGHT: rspeak(UNSURE_FACING); break; case OUTSIDE: case INSIDE: rspeak(NO_INOUT_HERE); break; case XYZZY: case PLUGH: rspeak(NOTHING_HAPPENS); break; case CRAWL: rspeak(WHICH_WAY); break; default: rspeak(CANT_APPLY); } return; } ++travel_entry; } /* (ESR) We've found a destination that goes with the motion verb. * Next we need to check any conditional(s) on this destination, and * possibly on following entries. */ do { for (;;) { /* L12 loop */ for (;;) { enum condtype_t condtype = travel[travel_entry].condtype; long condarg1 = travel[travel_entry].condarg1; long condarg2 = travel[travel_entry].condarg2; if (condtype < cond_not) { /* YAML N and [pct N] conditionals */ if (condtype == cond_goto || condtype == cond_pct) { if (condarg1 == 0 || PCT(condarg1)) break; /* else fall through */ } /* YAML [with OBJ] clause */ else if (TOTING(condarg1) || (condtype == cond_with && AT(condarg1))) break; /* else fall through to check [not OBJ STATE] */ } else if (game.prop[condarg1] != condarg2) break; /* We arrive here on conditional failure. * Skip to next non-matching destination */ int te_tmp = travel_entry; do { if (travel[te_tmp].stop) BUG(CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION); // LCOV_EXCL_LINE ++te_tmp; } while (traveleq(travel_entry, te_tmp)); travel_entry = te_tmp; } /* Found an eligible rule, now execute it */ enum desttype_t desttype = travel[travel_entry].desttype; game.newloc = travel[travel_entry].destval; if (desttype == dest_goto) return; if (desttype == dest_speak) { /* Execute a speak rule */ rspeak(game.newloc); game.newloc = game.loc; return; } else { switch (game.newloc) { case 1: /* Special travel 1. Plover-alcove passage. Can carry only * emerald. Note: travel table must include "useless" * entries going through passage, which can never be used * for actual motion, but can be spotted by "go back". */ game.newloc = (game.loc == LOC_PLOVER) ? LOC_ALCOVE : LOC_PLOVER; if (game.holdng > 1 || (game.holdng == 1 && !TOTING(EMERALD))) { game.newloc = game.loc; rspeak(MUST_DROP); } return; case 2: /* Special travel 2. Plover transport. Drop the * emerald (only use special travel if toting * it), so he's forced to use the plover-passage * to get it out. Having dropped it, go back and * pretend he wasn't carrying it after all. */ drop(EMERALD, game.loc); { int te_tmp = travel_entry; do { if (travel[te_tmp].stop) BUG(CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION); // LCOV_EXCL_LINE ++te_tmp; } while (traveleq(travel_entry, te_tmp)); travel_entry = te_tmp; } continue; /* goto L12 */ case 3: /* Special travel 3. Troll bridge. Must be done * only as special motion so that dwarves won't * wander across and encounter the bear. (They * won't follow the player there because that * region is forbidden to the pirate.) If * game.prop[TROLL]=TROLL_PAIDONCE, he's crossed * since paying, so step out and block him. * (standard travel entries check for * game.prop[TROLL]=TROLL_UNPAID.) Special stuff * for bear. */ if (game.prop[TROLL] == TROLL_PAIDONCE) { pspeak(TROLL, look, TROLL_PAIDONCE, true); game.prop[TROLL] = TROLL_UNPAID; move(TROLL2, LOC_NOWHERE); move(TROLL2 + NOBJECTS, IS_FREE); move(TROLL, objects[TROLL].plac); move(TROLL + NOBJECTS, objects[TROLL].fixd); juggle(CHASM); game.newloc = game.loc; return; } else { game.newloc = objects[TROLL].plac + objects[TROLL].fixd - game.loc; if (game.prop[TROLL] == TROLL_UNPAID) game.prop[TROLL] = TROLL_PAIDONCE; if (!TOTING(BEAR)) return; state_change(CHASM, BRIDGE_WRECKED); game.prop[TROLL] = TROLL_GONE; drop(BEAR, game.newloc); game.fixed[BEAR] = IS_FIXED; game.prop[BEAR] = BEAR_DEAD; game.oldlc2 = game.newloc; croak(); return; } default: // LCOV_EXCL_LINE BUG(SPECIAL_TRAVEL_500_GT_L_GT_300_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE } } break; /* Leave L12 loop */ } } while (false); } static bool closecheck(void) /* Handle the closing of the cave. The cave closes "clock1" turns * after the last treasure has been located (including the pirate's * chest, which may of course never show up). Note that the * treasures need not have been taken yet, just located. Hence * clock1 must be large enough to get out of the cave (it only ticks * while inside the cave). When it hits zero, we branch to 10000 to * start closing the cave, and then sit back and wait for him to try * to get out. If he doesn't within clock2 turns, we close the cave; * if he does try, we assume he panics, and give him a few additional * turns to get frantic before we close. When clock2 hits zero, we * transport him into the final puzzle. Note that the puzzle depends * upon all sorts of random things. For instance, there must be no * water or oil, since there are beanstalks which we don't want to be * able to water, since the code can't handle it. Also, we can have * no keys, since there is a grate (having moved the fixed object!) * there separating him from all the treasures. Most of these * problems arise from the use of negative prop numbers to suppress * the object descriptions until he's actually moved the objects. */ { /* If a turn threshold has been met, apply penalties and tell * the player about it. */ for (int i = 0; i < NTHRESHOLDS; ++i) { if (game.turns == turn_thresholds[i].threshold + 1) { game.trnluz += turn_thresholds[i].point_loss; speak(turn_thresholds[i].message); } } /* Don't tick game.clock1 unless well into cave (and not at Y2). */ if (game.tally == 0 && INDEEP(game.loc) && game.loc != LOC_Y2) --game.clock1; /* When the first warning comes, we lock the grate, destroy * the bridge, kill all the dwarves (and the pirate), remove * the troll and bear (unless dead), and set "closng" to * true. Leave the dragon; too much trouble to move it. * from now until clock2 runs out, he cannot unlock the * grate, move to any location outside the cave, or create * the bridge. Nor can he be resurrected if he dies. Note * that the snake is already gone, since he got to the * treasure accessible only via the hall of the mountain * king. Also, he's been in giant room (to get eggs), so we * can refer to it. Also also, he's gotten the pearl, so we * know the bivalve is an oyster. *And*, the dwarves must * have been activated, since we've found chest. */ if (game.clock1 == 0) { game.prop[GRATE] = GRATE_CLOSED; game.prop[FISSURE] = UNBRIDGED; for (int i = 1; i <= NDWARVES; i++) { game.dseen[i] = false; game.dloc[i] = LOC_NOWHERE; } move(TROLL, LOC_NOWHERE); move(TROLL + NOBJECTS, IS_FREE); move(TROLL2, objects[TROLL].plac); move(TROLL2 + NOBJECTS, objects[TROLL].fixd); juggle(CHASM); if (game.prop[BEAR] != BEAR_DEAD) DESTROY(BEAR); game.prop[CHAIN] = CHAIN_HEAP; game.fixed[CHAIN] = IS_FREE; game.prop[AXE] = AXE_HERE; game.fixed[AXE] = IS_FREE; rspeak(CAVE_CLOSING); game.clock1 = -1; game.closng = true; return true; } else if (game.clock1 < 0) --game.clock2; if (game.clock2 == 0) { /* Once he's panicked, and clock2 has run out, we come here * to set up the storage room. The room has two locs, * hardwired as LOC_NE and LOC_SW. At the ne end, we * place empty bottles, a nursery of plants, a bed of * oysters, a pile of lamps, rods with stars, sleeping * dwarves, and him. At the sw end we place grate over * treasures, snake pit, covey of caged birds, more rods, and * pillows. A mirror stretches across one wall. Many of the * objects come from known locations and/or states (e.g. the * snake is known to have been destroyed and needn't be * carried away from its old "place"), making the various * objects be handled differently. We also drop all other * objects he might be carrying (lest he have some which * could cause trouble, such as the keys). We describe the * flash of light and trundle back. */ game.prop[BOTTLE] = put(BOTTLE, LOC_NE, EMPTY_BOTTLE); game.prop[PLANT] = put(PLANT, LOC_NE, PLANT_THIRSTY); game.prop[OYSTER] = put(OYSTER, LOC_NE, STATE_FOUND); game.prop[LAMP] = put(LAMP, LOC_NE, LAMP_DARK); game.prop[ROD] = put(ROD, LOC_NE, STATE_FOUND); game.prop[DWARF] = put(DWARF, LOC_NE, 0); game.loc = LOC_NE; game.oldloc = LOC_NE; game.newloc = LOC_NE; /* Leave the grate with normal (non-negative) property. * Reuse sign. */ put(GRATE, LOC_SW, 0); put(SIGN, LOC_SW, 0); game.prop[SIGN] = ENDGAME_SIGN; game.prop[SNAKE] = put(SNAKE, LOC_SW, SNAKE_CHASED); game.prop[BIRD] = put(BIRD, LOC_SW, BIRD_CAGED); game.prop[CAGE] = put(CAGE, LOC_SW, STATE_FOUND); game.prop[ROD2] = put(ROD2, LOC_SW, STATE_FOUND); game.prop[PILLOW] = put(PILLOW, LOC_SW, STATE_FOUND); game.prop[MIRROR] = put(MIRROR, LOC_NE, STATE_FOUND); game.fixed[MIRROR] = LOC_SW; for (int i = 1; i <= NOBJECTS; i++) { if (TOTING(i)) DESTROY(i); } rspeak(CAVE_CLOSED); game.closed = true; return true; } return false; } static void lampcheck(void) /* Check game limit and lamp timers */ { if (game.prop[LAMP] == LAMP_BRIGHT) --game.limit; /* Another way we can force an end to things is by having the * lamp give out. When it gets close, we come here to warn him. * First following arm checks if the lamp and fresh batteries are * here, in which case we replace the batteries and continue. * Second is for other cases of lamp dying. Even after it goes * out, he can explore outside for a while if desired. */ if (game.limit <= WARNTIME) { if (HERE(BATTERY) && game.prop[BATTERY] == FRESH_BATTERIES && HERE(LAMP)) { rspeak(REPLACE_BATTERIES); game.prop[BATTERY] = DEAD_BATTERIES; #ifdef __unused__ /* This code from the original game seems to have been faulty. * No tests ever passed the guard, and with the guard removed * the game hangs when the lamp limit is reached. */ if (TOTING(BATTERY)) drop(BATTERY, game.loc); #endif game.limit += BATTERYLIFE; game.lmwarn = false; } else if (!game.lmwarn && HERE(LAMP)) { game.lmwarn = true; if (game.prop[BATTERY] == DEAD_BATTERIES) rspeak(MISSING_BATTERIES); else if (game.place[BATTERY] == LOC_NOWHERE) rspeak(LAMP_DIM); else rspeak(GET_BATTERIES); } } if (game.limit == 0) { game.limit = -1; game.prop[LAMP] = LAMP_DARK; if (HERE(LAMP)) rspeak(LAMP_OUT); } } static void listobjects(void) /* Print out descriptions of objects at this location. If * not closing and property value is negative, tally off * another treasure. Rug is special case; once seen, its * game.prop is RUG_DRAGON (dragon on it) till dragon is killed. * Similarly for chain; game.prop is initially CHAINING_BEAR (locked to * bear). These hacks are because game.prop=0 is needed to * get full score. */ { if (!DARK(game.loc)) { ++game.abbrev[game.loc]; for (int i = game.atloc[game.loc]; i != 0; i = game.link[i]) { obj_t obj = i; if (obj > NOBJECTS) obj = obj - NOBJECTS; if (obj == STEPS && TOTING(NUGGET)) continue; if (game.prop[obj] < 0) { if (game.closed) continue; game.prop[obj] = STATE_FOUND; if (obj == RUG) game.prop[RUG] = RUG_DRAGON; if (obj == CHAIN) game.prop[CHAIN] = CHAINING_BEAR; --game.tally; /* Note: There used to be a test here to see whether the * player had blown it so badly that he could never ever see * the remaining treasures, and if so the lamp was zapped to * 35 turns. But the tests were too simple-minded; things * like killing the bird before the snake was gone (can never * see jewelry), and doing it "right" was hopeless. E.G., * could cross troll bridge several times, using up all * available treasures, breaking vase, using coins to buy * batteries, etc., and eventually never be able to get * across again. If bottle were left on far side, could then * never get eggs or trident, and the effects propagate. So * the whole thing was flushed. anyone who makes such a * gross blunder isn't likely to find everything else anyway * (so goes the rationalisation). */ } int kk = game.prop[obj]; if (obj == STEPS) kk = (game.loc == game.fixed[STEPS]) ? STEPS_UP : STEPS_DOWN; pspeak(obj, look, kk, true); } } } static bool do_command() /* Get and execute a command */ { static command_t command; /* Can't leave cave once it's closing (except by main office). */ if (OUTSID(game.newloc) && game.newloc != 0 && game.closng) { rspeak(EXIT_CLOSED); game.newloc = game.loc; if (!game.panic) game.clock2 = PANICTIME; game.panic = true; } /* See if a dwarf has seen him and has come from where he * wants to go. If so, the dwarf's blocking his way. If * coming from place forbidden to pirate (dwarves rooted in * place) let him get out (and attacked). */ if (game.newloc != game.loc && !FORCED(game.loc) && !CNDBIT(game.loc, COND_NOARRR)) { for (size_t i = 1; i <= NDWARVES - 1; i++) { if (game.odloc[i] == game.newloc && game.dseen[i]) { game.newloc = game.loc; rspeak(DWARF_BLOCK); break; } } } game.loc = game.newloc; if (!dwarfmove()) croak(); /* Describe the current location and (maybe) get next command. */ for (;;) { if (game.loc == 0) croak(); const char* msg = locations[game.loc].description.small; if (MOD(game.abbrev[game.loc], game.abbnum) == 0 || msg == 0) msg = locations[game.loc].description.big; if (!FORCED(game.loc) && DARK(game.loc)) { /* The easiest way to get killed is to fall into a pit in * pitch darkness. */ if (game.wzdark && PCT(35)) { rspeak(PIT_FALL); game.oldlc2 = game.loc; croak(); continue; /* back to top of main interpreter loop */ } msg = arbitrary_messages[PITCH_DARK]; } if (TOTING(BEAR)) rspeak(TAME_BEAR); speak(msg); if (FORCED(game.loc)) { playermove(HERE); return true; } if (game.loc == LOC_Y2 && PCT(25) && !game.closng) rspeak(SAYS_PLUGH); listobjects(); Lclearobj: game.oldobj = command.obj; checkhints(); /* If closing time, check for any objects being toted with * game.prop < 0 and stash them. This way objects won't be * described until they've been picked up and put down * separate from their respective piles. */ if (game.closed) { if (game.prop[OYSTER] < 0 && TOTING(OYSTER)) pspeak(OYSTER, look, 1, true); for (size_t i = 1; i <= NOBJECTS; i++) { if (TOTING(i) && game.prop[i] < 0) game.prop[i] = STASHED(i); } } game.wzdark = DARK(game.loc); if (game.knfloc > 0 && game.knfloc != game.loc) game.knfloc = 0; /* Preserve state from last command for reuse when required */ command_t preserve = command; // Get command input from user if (!get_command_input(&command)) return false; #ifdef GDEBUG /* Needs to stay synced with enum word_type_t */ const char *types[] = {"NO_WORD_TYPE", "MOTION", "OBJECT", "ACTION", "NUMERIC"}; /* needs to stay synced with enum speechpart */ const char *roles[] = {"unknown", "intransitive", "transitive"}; printf("Preserve: role = %s type1 = %s, id1 = %ld, type2 = %s, id2 = %ld\n", roles[preserve.part], types[preserve.word[0].type], preserve.word[0].id, types[preserve.word[1].type], preserve.word[1].id); printf("Command: role = %s type1 = %s, id1 = %ld, type2 = %s, id2 = %ld\n", roles[command.part], types[command.word[0].type], command.word[0].id, types[command.word[1].type], command.word[1].id); #endif /* Handle of objectless action followed by actionless object */ if (preserve.word[0].type == ACTION && preserve.word[1].type == NO_WORD_TYPE && command.word[1].id == 0) command.verb = preserve.verb; #ifdef BROKEN /* Handling of actionless object followed by objectless action */ if (preserve.word[0].type == OBJECT && preserve.word[1].type == NO_WORD_TYPE && command.word[1].id == 0 && command.word[0].id == CARRY) command.obj = preserve.obj; #endif /* BROKEN */ ++game.turns; if (closecheck()) { if (game.closed) return true; } else lampcheck(); if (command.word[0].type == MOTION && command.word[0].id == ENTER && (command.word[1].id == STREAM || command.word[1].id == WATER)) { if (LIQLOC(game.loc) == WATER) rspeak(FEET_WET); else rspeak(WHERE_QUERY); goto Lclearobj; } if (command.word[0].type == OBJECT) { if (command.word[0].id == GRATE) { command.word[0].type = MOTION; if (game.loc == LOC_START || game.loc == LOC_VALLEY || game.loc == LOC_SLIT) { command.word[0].id = DEPRESSION; } if (game.loc == LOC_COBBLE || game.loc == LOC_DEBRIS || game.loc == LOC_AWKWARD || game.loc == LOC_BIRD || game.loc == LOC_PITTOP) { command.word[0].id = ENTRANCE; } } if ((command.word[0].id == WATER || command.word[0].id == OIL) && (command.word[1].id == PLANT || command.word[1].id == DOOR)) { if (AT(command.word[1].id)) { command.word[1] = command.word[0]; command.word[0].id = POUR; command.word[0].type = ACTION; strncpy(command.word[0].raw, "pour", LINESIZE - 1); } } if (command.word[0].id == CAGE && command.word[1].id == BIRD && HERE(CAGE) && HERE(BIRD)) { command.word[0].id = CARRY; command.word[0].type = ACTION; } /* From OV to VO form */ if (command.word[0].type == OBJECT && command.word[1].type == ACTION) { command_word_t stage = command.word[0]; command.word[0] = command.word[1]; command.word[1] = stage; } } Lookup: if (strncasecmp(command.word[0].raw, "west", sizeof("west")) == 0) { if (++game.iwest == 10) rspeak(W_IS_WEST); } if (strncasecmp(command.word[0].raw, "go", sizeof("go")) == 0 && command.word[1].id != WORD_EMPTY) { if (++game.igo == 10) rspeak(GO_UNNEEDED); } if (command.word[0].id == WORD_NOT_FOUND) { /* Gee, I don't understand. */ sspeak(DONT_KNOW, command.word[0].raw); goto Lclearobj; } switch (command.word[0].type) { case NO_WORD_TYPE: // FIXME: treating NO_WORD_TYPE as a motion word is confusing case MOTION: playermove(command.word[0].id); return true; case OBJECT: command.part = unknown; command.obj = command.word[0].id; break; case ACTION: if (command.word[1].type == NUMERIC) command.part = transitive; else command.part = intransitive; command.verb = command.word[0].id; break; case NUMERIC: // LCOV_EXCL_LINE default: // LCOV_EXCL_LINE BUG(VOCABULARY_TYPE_N_OVER_1000_NOT_BETWEEN_0_AND_3); // LCOV_EXCL_LINE } switch (action(command)) { case GO_TERMINATE: return true; case GO_MOVE: playermove(NUL); return true; case GO_TOP: continue; /* back to top of main interpreter loop */ case GO_WORD2: #ifdef GDEBUG printf("Word shift\n"); #endif /* GDEBUG */ /* Get second word for analysis. */ command.word[0] = command.word[1]; command.word[1] = empty_command_word; goto Lookup; case GO_UNKNOWN: /* Random intransitive verbs come here. Clear obj just in case * (see attack()). */ command.word[0].raw[0] = toupper(command.word[0].raw[0]); sspeak(DO_WHAT, command.word[0].raw); command.obj = 0; // Fallthrough case GO_CLEAROBJ: goto Lclearobj; case GO_DWARFWAKE: /* Oh dear, he's disturbed the dwarves. */ rspeak(DWARVES_AWAKEN); terminate(endgame); default: // LCOV_EXCL_LINE BUG(ACTION_RETURNED_PHASE_CODE_BEYOND_END_OF_SWITCH); // LCOV_EXCL_LINE } } } /* end */ /* * Scoring and wrap-up. * * Copyright (c) 1977, 2005 by Will Crowther and Don Woods * Copyright (c) 2017 by Eric S. Raymond * SPDX-License-Identifier: BSD-2-clause */ #include static int mxscor; /* ugh..the price for having score() not exit. */ long score(enum termination mode) /* mode is 'scoregame' if scoring, 'quitgame' if quitting, 'endgame' if died * or won */ { int score = 0; /* The present scoring algorithm is as follows: * Objective: Points: Present total possible: * Getting well into cave 25 25 * Each treasure < chest 12 60 * Treasure chest itself 14 14 * Each treasure > chest 16 224 * Surviving (MAX-NUM)*10 30 * Not quitting 4 4 * Reaching "game.closng" 25 25 * "Closed": Quit/Killed 10 * Klutzed 25 * Wrong way 30 * Success 45 45 * Came to Witt's End 1 1 * Round out the total 2 2 * TOTAL: 430 * Points can also be deducted for using hints or too many turns, or for * saving intermediate positions. */ /* First tally up the treasures. Must be in building and not broken. * Give the poor guy 2 points just for finding each treasure. */ mxscor = 0; for (int i = 1; i <= NOBJECTS; i++) { if (!objects[i].is_treasure) continue; if (objects[i].inventory != 0) { int k = 12; if (i == CHEST) k = 14; if (i > CHEST) k = 16; if (game.prop[i] > STATE_NOTFOUND) score += 2; if (game.place[i] == LOC_BUILDING && game.prop[i] == STATE_FOUND) score += k - 2; mxscor += k; } } /* Now look at how he finished and how far he got. NDEATHS and * game.numdie tell us how well he survived. game.dflag will tell us * if he ever got suitably deep into the cave. game.closng still * indicates whether he reached the endgame. And if he got as far as * "cave closed" (indicated by "game.closed"), then bonus is zero for * mundane exits or 133, 134, 135 if he blew it (so to speak). */ score += (NDEATHS - game.numdie) * 10; mxscor += NDEATHS * 10; if (mode == endgame) score += 4; mxscor += 4; if (game.dflag != 0) score += 25; mxscor += 25; if (game.closng) score += 25; mxscor += 25; if (game.closed) { if (game.bonus == none) score += 10; if (game.bonus == splatter) score += 25; if (game.bonus == defeat) score += 30; if (game.bonus == victory) score += 45; } mxscor += 45; /* Did he come to Witt's End as he should? */ if (game.place[MAGAZINE] == LOC_WITTSEND) score += 1; mxscor += 1; /* Round it off. */ score += 2; mxscor += 2; /* Deduct for hints/turns/saves. Hints < 4 are special; see database desc. */ for (int i = 0; i < NHINTS; i++) { if (game.hinted[i]) score = score - hints[i].penalty; } if (game.novice) score -= 5; if (game.clshnt) score -= 10; score = score - game.trnluz - game.saved; /* Return to score command if that's where we came from. */ if (mode == scoregame) { rspeak(GARNERED_POINTS, score, mxscor, game.turns, game.turns); } return score; } void terminate(enum termination mode) /* End of game. Let's tell him all about it. */ { long points = score(mode); if (points + game.trnluz + 1 >= mxscor && game.trnluz != 0) rspeak(TOOK_LONG); if (points + game.saved + 1 >= mxscor && game.saved != 0) rspeak(WITHOUT_SUSPENDS); rspeak(TOTAL_SCORE, points, mxscor, game.turns, game.turns); for (int i = 1; i <= (long)NCLASSES; i++) { if (classes[i].threshold >= points) { speak(classes[i].message); i = classes[i].threshold + 1 - points; rspeak(NEXT_HIGHER, i, i); exit(EXIT_SUCCESS); } } rspeak(OFF_SCALE); rspeak(NO_HIGHER); exit(EXIT_SUCCESS); } /* end */ /* * 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 static int fill(verb_t, obj_t); static int 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. */ { verb_t verb = command.verb; obj_t obj = command.obj; if (obj == INTRANSITIVE) { int changes = 0; if (atdwrf(game.loc) > 0) { obj = DWARF; ++changes; } if (HERE(SNAKE)) { obj = SNAKE; ++changes; } if (AT(DRAGON) && game.prop[DRAGON] == DRAGON_BARS) { obj = DRAGON; ++changes; } if (AT(TROLL)) { obj = TROLL; ++changes; } if (AT(OGRE)) { obj = OGRE; ++changes; } if (HERE(BEAR) && game.prop[BEAR] == UNTAMED_BEAR) { obj = BEAR; ++changes; } /* check for low-priority targets */ if (obj == INTRANSITIVE) { /* Can't attack bird or machine by throwing axe. */ if (HERE(BIRD) && verb != THROW) { obj = BIRD; ++changes; } if (HERE(VEND) && verb != THROW) { obj = VEND; ++changes; } /* Clam and oyster both treated as clam for intransitive case; * no harm done. */ if (HERE(CLAM) || HERE(OYSTER)) { obj = CLAM; ++changes; } } if (changes >= 2) return GO_UNKNOWN; } if (obj == BIRD) { if (game.closed) { rspeak(UNHAPPY_BIRD); } else { DESTROY(BIRD); rspeak(BIRD_DEAD); } return GO_CLEAROBJ; } if (obj == VEND) { state_change(VEND, game.prop[VEND] == VEND_BLOCKS ? VEND_UNBLOCKS : VEND_BLOCKS); return GO_CLEAROBJ; } if (obj == BEAR) { switch (game.prop[BEAR]) { case UNTAMED_BEAR: rspeak(BEAR_HANDS); break; case SITTING_BEAR: rspeak(BEAR_CONFUSED); break; case CONTENTED_BEAR: rspeak(BEAR_CONFUSED); break; case BEAR_DEAD: rspeak(ALREADY_DEAD); break; } 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()) { speak(arbitrary_messages[NASTY_DRAGON]); return GO_MOVE; } state_change(DRAGON, DRAGON_DEAD); game.prop[RUG] = RUG_FLOOR; /* 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; } 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(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). */ { 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 = WORD_EMPTY; if (game.place[EGGS] == objects[EGGS].plac || (TOTING(EGGS) && game.loc == objects[EGGS].plac)) { 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; if (HERE(EGGS)) 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); 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] == STATE_NOTFOUND || !game.closed) rspeak(REQUIRES_DYNAMITE); else { 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(verb_t verb, obj_t obj) /* Break. Only works for mirror in repository and, of course, the vase. */ { switch (obj) { case MIRROR: if (game.closed) { state_change(MIRROR, MIRROR_BROKEN); return GO_DWARFWAKE; } else { rspeak(TOO_FAR); 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); } return (GO_CLEAROBJ); } static int brief(void) /* Brief. Intransitive only. Suppress full descriptions after first time. */ { game.abbnum = 10000; game.detail = 3; rspeak(BRIEF_CONFIRM); return GO_CLEAROBJ; } 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. */ { if (obj == INTRANSITIVE) { /* Carry, no object given yet. OK if only one object present. */ if (game.atloc[game.loc] == NO_OBJECT || game.link[game.atloc[game.loc]] != 0 || atdwrf(game.loc) > 0) return GO_UNKNOWN; obj = game.atloc[game.loc]; } if (TOTING(obj)) { speak(actions[verb].message); return GO_CLEAROBJ; } if (obj == MESSAG) { rspeak(REMOVE_MESSAGE); DESTROY(MESSAG); return GO_CLEAROBJ; } 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)) { 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; } if (game.holdng >= INVLIMIT) { rspeak(CARRY_LIMIT); return GO_CLEAROBJ; } 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)) { rspeak(CANNOT_CARRY); return GO_CLEAROBJ; } if (TOTING(ROD)) { rspeak(BIRD_EVADES); return GO_CLEAROBJ; } game.prop[BIRD] = BIRD_CAGED; } if ((obj == BIRD || obj == CAGE) && (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() != NO_OBJECT) game.place[LIQUID()] = CARRIED; 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(verb_t verb) /* Do something to the bear's chain */ { if (verb != LOCK) { 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] = IS_FREE; if (game.prop[BEAR] != BEAR_DEAD) game.prop[BEAR] = CONTENTED_BEAR; switch (game.prop[BEAR]) { // LCOV_EXCL_START case BEAR_DEAD: /* 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] = IS_FREE; } rspeak(CHAIN_UNLOCKED); return GO_CLEAROBJ; } 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(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. */ { 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 == 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 rspeak(OK_MAN); game.prop[BIRD] = FOREST(game.loc) ? BIRD_FOREST_UNCAGED : BIRD_UNCAGED; drop(obj, game.loc); return GO_CLEAROBJ; } rspeak(OK_MAN); drop(obj, game.loc); return GO_CLEAROBJ; } 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 == INTRANSITIVE && LIQLOC(game.loc) != WATER && (LIQUID() != WATER || !HERE(BOTTLE))) { return GO_UNKNOWN; } 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(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. */ { switch (obj) { case INTRANSITIVE: if (!HERE(FOOD)) return GO_UNKNOWN; /* FALLTHRU */ case FOOD: DESTROY(FOOD); rspeak(THANKS_DELICIOUS); break; case BIRD: case SNAKE: case CLAM: case OYSTER: case DWARF: case DRAGON: case TROLL: case BEAR: case OGRE: rspeak(LOST_APPETITE); break; default: speak(actions[verb].message); } return GO_CLEAROBJ; } static int extinguish(verb_t verb, obj_t obj) /* Extinguish. Lamp, urn, dragon/volcano (nice try). */ { if (obj == INTRANSITIVE) { if (HERE(LAMP) && game.prop[LAMP] == LAMP_BRIGHT) obj = LAMP; if (HERE(URN) && game.prop[URN] == URN_LIT) obj = URN; if (obj == INTRANSITIVE) return GO_UNKNOWN; } switch (obj) { case URN: if (game.prop[URN] != URN_EMPTY) { state_change(URN, URN_DARK); } else { pspeak(URN, change, URN_DARK, true); } break; case LAMP: state_change(LAMP, LAMP_DARK); rspeak(DARK(game.loc) ? PITCH_DARK : NO_MESSAGE); break; case DRAGON: case VOLCANO: rspeak(BEYOND_POWER); break; default: speak(actions[verb].message); } return GO_CLEAROBJ; } 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. */ { switch (obj) { case BIRD: rspeak(BIRD_PINING); 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); rspeak(BIRD_DEVOURED); } else rspeak(NOTHING_EDIBLE); break; case TROLL: rspeak(TROLL_VICES); break; case DWARF: if (HERE(FOOD)) { game.dflag += 2; rspeak(REALLY_MAD); } else speak(actions[verb].message); break; case BEAR: if (game.prop[BEAR] == BEAR_DEAD) { rspeak(RIDICULOUS_ATTEMPT); break; } 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; } speak(actions[verb].message); break; case OGRE: if (HERE(FOOD)) rspeak(OGRE_FULL); else speak(actions[verb].message); break; default: rspeak(AM_GAME); } return GO_CLEAROBJ; } int fill(verb_t verb, obj_t obj) /* Fill. Bottle or urn must be empty, and liquid available. (Vase * is nasty.) */ { if (obj == VASE) { 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] = IS_FIXED; drop(VASE, game.loc); return GO_CLEAROBJ; } if (obj == URN) { if (game.prop[URN] != URN_EMPTY) { rspeak(FULL_URN); return GO_CLEAROBJ; } if (!HERE(BOTTLE)) { rspeak(FILL_INVALID); return GO_CLEAROBJ; } int k = LIQUID(); switch (k) { case WATER: game.prop[BOTTLE] = EMPTY_BOTTLE; rspeak(WATER_URN); break; case OIL: game.prop[URN] = URN_DARK; 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; } if (obj != INTRANSITIVE && obj != BOTTLE) { speak(actions[verb].message); return GO_CLEAROBJ; } if (obj == INTRANSITIVE && !HERE(BOTTLE)) return GO_UNKNOWN; if (HERE(URN) && game.prop[URN] != URN_EMPTY) { rspeak(URN_NOPOUR); return GO_CLEAROBJ; } 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(verb_t verb, obj_t obj) /* Find. Might be carrying it, or it might be here. Else give caveat. */ { 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)) { rspeak(YOU_HAVEIT); return GO_CLEAROBJ; } speak(actions[verb].message); return GO_CLEAROBJ; } static int fly(verb_t verb, obj_t obj) /* Fly. Snide remarks unless hovering rug is here. */ { if (obj == INTRANSITIVE) { if (!HERE(RUG)) { rspeak(FLAP_ARMS); return GO_CLEAROBJ; } if (game.prop[RUG] != RUG_HOVER) { rspeak(RUG_NOTHING2); return GO_CLEAROBJ; } obj = RUG; } if (obj != RUG) { speak(actions[verb].message); return GO_CLEAROBJ; } if (game.prop[RUG] != RUG_HOVER) { rspeak(RUG_NOTHING1); return GO_CLEAROBJ; } game.oldlc2 = game.oldloc; game.oldloc = game.loc; if (game.prop[SAPPH] == STATE_NOTFOUND) { game.newloc = game.place[SAPPH]; rspeak(RUG_GOES); } else { game.newloc = LOC_CLIFF; rspeak(RUG_RETURNS); } return GO_TERMINATE; } static int inven(void) /* Inventory. If object, treat same as find. Else report on current burden. */ { bool empty = true; for (obj_t i = 1; i <= NOBJECTS; i++) { if (i == BEAR || !TOTING(i)) continue; if (empty) { rspeak(NOW_HOLDING); empty = false; } pspeak(i, touch, -1, false); } if (TOTING(BEAR)) rspeak(TAME_BEAR); if (empty) rspeak(NO_CARRY); return GO_CLEAROBJ; } static int light(verb_t verb, obj_t obj) /* Light. Applicable only to lamp and urn. */ { if (obj == INTRANSITIVE) { int selects = 0; if (HERE(LAMP) && game.prop[LAMP] == LAMP_DARK && game.limit >= 0) { obj = LAMP; selects++; } if (HERE(URN) && game.prop[URN] == URN_DARK) { obj = URN; selects++; } if (selects != 1) return GO_UNKNOWN; } switch (obj) { case URN: state_change(URN, game.prop[URN] == URN_EMPTY ? URN_EMPTY : URN_LIT); break; case LAMP: if (game.limit < 0) { rspeak(LAMP_OUT); break; } state_change(LAMP, LAMP_BRIGHT); if (game.wzdark) return GO_TOP; break; default: speak(actions[verb].message); } return GO_CLEAROBJ; } static int listen(void) /* Listen. Intransitive only. Print stuff based on object sound proprties. */ { vocab_t sound = locations[game.loc].sound; if (sound != SILENT) { rspeak(sound); if (!locations[game.loc].loud) rspeak(NO_MESSAGE); return GO_CLEAROBJ; } 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; pspeak(i, hear, mi, true, game.zzword); rspeak(NO_MESSAGE); if (i == BIRD && mi == BIRD_ENDSTATE) DESTROY(BIRD); return GO_CLEAROBJ; } rspeak(ALL_SILENT); return GO_CLEAROBJ; } static int lock(verb_t verb, obj_t obj) /* Lock, unlock, no object given. Assume various things if present. */ { if (obj == INTRANSITIVE) { if (HERE(CLAM)) obj = CLAM; if (HERE(OYSTER)) obj = OYSTER; if (AT(DOOR)) obj = DOOR; if (AT(GRATE)) obj = GRATE; if (HERE(CHAIN)) obj = CHAIN; if (obj == INTRANSITIVE) { rspeak(NOTHING_LOCKED); return GO_CLEAROBJ; } } /* Lock, unlock object. Special stuff for opening clam/oyster * and for chain. */ switch (obj) { case CHAIN: if (HERE(KEYS)) { return chain(verb); } else rspeak(NO_KEYS); break; case GRATE: if (HERE(KEYS)) { if (game.closng) { rspeak(EXIT_CLOSED); if (!game.panic) game.clock2 = PANICTIME; game.panic = true; } else { state_change(GRATE, (verb == LOCK) ? GRATE_CLOSED : GRATE_OPEN); } } 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); } return GO_CLEAROBJ; } 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 == INTRANSITIVE) obj = LIQUID(); if (obj == NO_OBJECT) return GO_UNKNOWN; if (!TOTING(obj)) { speak(actions[verb].message); return GO_CLEAROBJ; } if (obj != OIL && obj != WATER) { rspeak(CANT_POUR); return GO_CLEAROBJ; } if (HERE(URN) && game.prop[URN] == URN_EMPTY) return fill(verb, URN); game.prop[BOTTLE] = EMPTY_BOTTLE; game.place[obj] = LOC_NOWHERE; if (!(AT(PLANT) || AT(DOOR))) { rspeak(GROUND_WET); return GO_CLEAROBJ; } if (!AT(DOOR)) { 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; } } else { state_change(DOOR, (obj == OIL) ? DOOR_UNRUSTED : DOOR_RUSTED); return GO_CLEAROBJ; } } static int 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])) terminate(quitgame); return GO_CLEAROBJ; } static int actions_read(command_t command) /* Read. Print stuff based on objtxt. Oyster (?) is special case. */ { if (command.obj == INTRANSITIVE) { 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 == NO_OBJECT || DARK(game.loc)) return GO_UNKNOWN; } if (DARK(game.loc)) { sspeak(NO_SEE, command.word[0].raw); } else if (command.obj == OYSTER && !game.clshnt && game.closed) { game.clshnt = yes(arbitrary_messages[CLUE_QUERY], arbitrary_messages[WAYOUT_CLUE], arbitrary_messages[OK_MAN]); } else if (objects[command.obj].texts[0] == NULL || game.prop[command.obj] == STATE_NOTFOUND) { speak(actions[command.verb].message); } else pspeak(command.obj, study, game.prop[command.obj], true); return GO_CLEAROBJ; } static int reservoir(void) /* Z'ZZZ (word gets recomputed at startup; different each game). */ { if (!AT(RESER) && game.loc != LOC_RESBOTTOM) { rspeak(NOTHING_HAPPENS); return GO_CLEAROBJ; } else { state_change(RESER, game.prop[RESER] == WATERS_PARTED ? WATERS_UNPARTED : WATERS_PARTED); if (AT(RESER)) return GO_CLEAROBJ; else { game.oldlc2 = game.loc; game.newloc = LOC_NOWHERE; rspeak(NOT_BRIGHT); return GO_TERMINATE; } } } 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) { DESTROY(URN); drop(AMBER, game.loc); game.prop[AMBER] = AMBER_IN_ROCK; --game.tally; drop(CAVITY, game.loc); rspeak(URN_GENIES); } else if (obj != LAMP) { rspeak(PECULIAR_NOTHING); } else { speak(actions[verb].message); } return GO_CLEAROBJ; } static int say(command_t command) /* Say. Echo WD2. Magic words override. */ { if (command.word[1].type == MOTION && (command.word[1].id == XYZZY || command.word[1].id == PLUGH || command.word[1].id == PLOVER)) { return GO_WORD2; } 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(vocab_t spk) { rspeak(spk); drop(AXE, game.loc); return GO_MOVE; } static int 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(command.obj)) { 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, 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)) { /* But throwing food is another story. */ command.obj = BEAR; return (feed(command.verb, command.obj)); } 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); if (AT(OGRE)) return throw_support(OGRE_DODGE); 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] = IS_FIXED; juggle(BEAR); state_change(AXE, AXE_LOST); return GO_CLEAROBJ; } command.obj = INTRANSITIVE; return (attack(command)); } if (randrange(NDWARVES + 1) < game.dflag) { return throw_support(DWARF_DODGES); } else { int i = atdwrf(game.loc); game.dseen[i] = false; game.dloc[i] = LOC_NOWHERE; return throw_support((++game.dkill == 1) ? DWARF_SMOKE : KILLED_DWARF); } } } static int wake(verb_t verb, obj_t obj) /* Wake. Only use is to disturb the dwarves. */ { if (obj != DWARF || !game.closed) { speak(actions[verb].message); return GO_CLEAROBJ; } else { rspeak(PROD_DWARF); return GO_DWARFWAKE; } } static int 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 int waste(verb_t verb, turn_t turns) /* Burn turns */ { game.limit -= turns; speak(actions[verb].message, (int)game.limit); return GO_TOP; } static int wave(verb_t verb, obj_t obj) /* Wave. No effect unless waving rod at fissure or at bird. */ { if (obj != ROD || !TOTING(obj) || (!HERE(BIRD) && (game.closng || !AT(FISSURE)))) { 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] == STATE_NOTFOUND) { drop(JADE, game.loc); game.prop[JADE] = STATE_FOUND; --game.tally; rspeak(NECKLACE_FLY); return GO_CLEAROBJ; } else { if (game.closed) { rspeak((game.prop[BIRD] == BIRD_CAGED) ? CAGE_FLY : FREE_FLY); return GO_DWARFWAKE; } if (game.closng || !AT(FISSURE)) { rspeak((game.prop[BIRD] == BIRD_CAGED) ? CAGE_FLY : FREE_FLY); return GO_CLEAROBJ; } if (HERE(BIRD)) rspeak((game.prop[BIRD] == BIRD_CAGED) ? CAGE_FLY : FREE_FLY); state_change(FISSURE, game.prop[FISSURE] == BRIDGED ? UNBRIDGED : BRIDGED); return GO_CLEAROBJ; } } int action(command_t command) /* Analyse a verb. Remember what it was, go back for object if second word * unless verb is "say", which snarfs arbitrary second word. */ { /* Previously, actions that result in a message, but don't do anything * further were called "specials". Now they're handled here as normal * 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 * yet to be analysed). Water and oil are also funny, since * 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)) /* FALL THROUGH */; else if (command.obj == DWARF && atdwrf(game.loc) > 0) /* FALL THROUGH */; else if ((LIQUID() == command.obj && HERE(BOTTLE)) || command.obj == LIQLOC(game.loc)) /* FALL THROUGH */; 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] != PLANT_THIRSTY) { command.obj = PLANT2; /* FALL THROUGH */; } 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; /* FALL THROUGH */; } 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.word[0].raw); return GO_CLEAROBJ; } if (command.verb != 0) command.part = transitive; } switch (command.part) { case intransitive: if (command.word[1].raw[0] != '\0' && command.verb != SAY) return GO_WORD2; if (command.verb == SAY) /* KEYS is not special, anything not NO_OBJECT or INTRANSITIVE * will do here. We're preventing interpretation as an intransitive * verb when the word is unknown. */ 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) { case CARRY: return vcarry(command.verb, INTRANSITIVE); case DROP: return GO_UNKNOWN; case SAY: return GO_UNKNOWN; case UNLOCK: return lock(command.verb, INTRANSITIVE); case NOTHING: { rspeak(OK_MAN); return (GO_CLEAROBJ); } case LOCK: return lock(command.verb, INTRANSITIVE); case LIGHT: return light(command.verb, INTRANSITIVE); case EXTINGUISH: return extinguish(command.verb, INTRANSITIVE); case WAVE: return GO_UNKNOWN; case TAME: return GO_UNKNOWN; case GO: { speak(actions[command.verb].message); return GO_CLEAROBJ; } case ATTACK: command.obj = INTRANSITIVE; return attack(command); case POUR: return pour(command.verb, INTRANSITIVE); case EAT: return eat(command.verb, INTRANSITIVE); case DRINK: return drink(command.verb, INTRANSITIVE); case RUB: return GO_UNKNOWN; case THROW: return GO_UNKNOWN; case QUIT: return quit(); case FIND: return GO_UNKNOWN; case INVENTORY: return inven(); case FEED: return GO_UNKNOWN; case FILL: return fill(command.verb, INTRANSITIVE); case BLAST: blast(); return GO_CLEAROBJ; case SCORE: score(scoregame); return GO_CLEAROBJ; 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 actions_read(command); case BREAK: return GO_UNKNOWN; case WAKE: return GO_UNKNOWN; case SAVE: return suspend(); case RESUME: return resume(); case FLY: return fly(command.verb, INTRANSITIVE); case LISTEN: return listen(); case PART: return reservoir(); 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) { case CARRY: return vcarry(command.verb, command.obj); case DROP: return discard(command.verb, command.obj); case SAY: return say(command); case UNLOCK: return lock(command.verb, command.obj); case NOTHING: { rspeak(OK_MAN); return (GO_CLEAROBJ); } case LOCK: return lock(command.verb, command.obj); case LIGHT: return light(command.verb, command.obj); case EXTINGUISH: return extinguish(command.verb, command.obj); case WAVE: return wave(command.verb, command.obj); case TAME: { speak(actions[command.verb].message); return GO_CLEAROBJ; } case GO: { speak(actions[command.verb].message); return GO_CLEAROBJ; } case ATTACK: return attack(command); case POUR: return pour(command.verb, command.obj); case EAT: return eat(command.verb, command.obj); case DRINK: return drink(command.verb, command.obj); case RUB: return rub(command.verb, command.obj); case THROW: return throw (command); case QUIT: { speak(actions[command.verb].message); return GO_CLEAROBJ; } case FIND: return find(command.verb, command.obj); case INVENTORY: return find(command.verb, command.obj); case FEED: return feed(command.verb, command.obj); case FILL: return fill(command.verb, command.obj); case BLAST: blast(); return GO_CLEAROBJ; case SCORE: { speak(actions[command.verb].message); return GO_CLEAROBJ; } case FEE: case FIE: case FOE: case FOO: case FUM: { speak(actions[command.verb].message); return GO_CLEAROBJ; } case BRIEF: { speak(actions[command.verb].message); return GO_CLEAROBJ; } case READ: return actions_read(command); case BREAK: return vbreak(command.verb, command.obj); case WAKE: return wake(command.verb, command.obj); case SAVE: { speak(actions[command.verb].message); return GO_CLEAROBJ; } case RESUME: { speak(actions[command.verb].message); return GO_CLEAROBJ; } case FLY: return fly(command.verb, command.obj); case LISTEN: { speak(actions[command.verb].message); return GO_CLEAROBJ; } // LCOV_EXCL_START // This case should never happen - here only as placeholder case PART: return reservoir(); // 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.word[0].raw); return GO_CLEAROBJ; default: // LCOV_EXCL_LINE BUG(SPEECHPART_NOT_TRANSITIVE_OR_INTRANSITIVE_OR_UNKNOWN); // LCOV_EXCL_LINE } } /* * Initialisation * * 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 #include struct settings_t settings = { .logfp = NULL, .oldstyle = false, .prompt = true }; struct game_t game = { .dloc[1] = LOC_KINGHALL, .dloc[2] = LOC_WESTBANK, .dloc[3] = LOC_Y2, .dloc[4] = LOC_ALIKE3, .dloc[5] = LOC_COMPLEX, /* Sixth dwarf is special (the pirate). He always starts at his * chest's eventual location inside the maze. This loc is saved * in chloc for ref. The dead end in the other maze has its * loc stored in chloc2. */ .dloc[6] = LOC_DEADEND12, .chloc = LOC_DEADEND12, .chloc2 = LOC_DEADEND13, .abbnum = 5, .clock1 = WARNTIME, .clock2 = FLASHTIME, .newloc = LOC_START, .loc = LOC_START, .limit = GAMELIMIT, .foobar = WORD_EMPTY, }; long initialise(void) { if (settings.oldstyle) printf("Initialising...\n"); srand(time(NULL)); long seedval = (long)rand(); set_seed(seedval); for (int i = 1; i <= NOBJECTS; i++) { game.place[i] = LOC_NOWHERE; } for (int i = 1; i <= NLOCATIONS; i++) { if (!(locations[i].description.big == 0 || tkey[i] == 0)) { int k = tkey[i]; if (T_TERMINATE(travel[k])) conditions[i] |= (1 << COND_FORCED); } } /* Set up the game.atloc and game.link arrays. * We'll use the DROP subroutine, which prefaces new objects on the * lists. Since we want things in the other order, we'll run the * loop backwards. If the object is in two locs, we drop it twice. * Also, since two-placed objects are typically best described * last, we'll drop them first. */ for (int i = NOBJECTS; i >= 1; i--) { if (objects[i].fixd > 0) { drop(i + NOBJECTS, objects[i].fixd); drop(i, objects[i].plac); } } for (int i = 1; i <= NOBJECTS; i++) { int k = NOBJECTS + 1 - i; game.fixed[k] = objects[k].fixd; if (objects[k].plac != 0 && objects[k].fixd <= 0) drop(k, objects[k].plac); } /* Treasure props are initially -1, and are set to 0 the first time * they are described. game.tally keeps track of how many are * not yet found, so we know when to close the cave. */ for (int treasure = 1; treasure <= NOBJECTS; treasure++) { if (objects[treasure].is_treasure) { if (objects[treasure].inventory != 0) game.prop[treasure] = STATE_NOTFOUND; game.tally = game.tally - game.prop[treasure]; } } game.conds = setbit(11); return seedval; } /* * Saving and resuming. * * (ESR) This replaces a bunch of particularly nasty FORTRAN-derived code; * see the history.adoc file in the source distribution for discussion. * * 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 #include #define VRSION 28 /* bump on save format change */ /* * If you change the first three members, the resume function may not properly * reject saves from older versions. Yes, this glues us to a hardware- * dependent length of long. Later members can change, but bump the version * when you do that. */ struct save_t { int64_t savetime; int32_t mode; /* not used, must be present for version detection */ int32_t version; struct game_t game; }; struct save_t save; #define IGNORE(r) do{if (r){}}while(0) int savefile(FILE *fp, int32_t version) /* Save game to file. No input or output from user. */ { save.savetime = time(NULL); save.mode = -1; save.version = (version == 0) ? VRSION : version; save.game = game; IGNORE(fwrite(&save, sizeof(struct save_t), 1, fp)); return (0); } /* Suspend and resume */ int suspend(void) { /* Suspend. Offer to save things in a file, but charging * some points (so can't win by using saved games to retry * battles or to start over after learning zzword). * If ADVENT_NOSAVE is defined, do nothing instead. */ #ifdef ADVENT_NOSAVE return GO_UNKNOWN; #endif FILE *fp = NULL; rspeak(SUSPEND_WARNING); if (!yes(arbitrary_messages[THIS_ACCEPTABLE], arbitrary_messages[OK_MAN], arbitrary_messages[OK_MAN])) return GO_CLEAROBJ; game.saved = game.saved + 5; while (fp == NULL) { char *readline(char *prompt); char* name = readline("\nFile name: "); if (name == NULL) return GO_TOP; fp = fopen(name, WRITE_MODE); if (fp == NULL) printf("Can't open file %s, try again.\n", name); free(name); } savefile(fp, VRSION); fclose(fp); rspeak(RESUME_HELP); exit(EXIT_SUCCESS); } int resume(void) { /* Resume. Read a suspended game back from a file. * If ADVENT_NOSAVE is defined, do nothing instead. */ #ifdef ADVENT_NOSAVE return GO_UNKNOWN; #endif FILE *fp = NULL; if (game.loc != 1 || game.abbrev[1] != 1) { rspeak(RESUME_ABANDON); if (!yes(arbitrary_messages[THIS_ACCEPTABLE], arbitrary_messages[OK_MAN], arbitrary_messages[OK_MAN])) return GO_CLEAROBJ; } while (fp == NULL) { char *readline(char *prompt); char* name = readline("\nFile name: "); if (name == NULL) return GO_TOP; fp = fopen(name, READ_MODE); if (fp == NULL) printf("Can't open file %s, try again.\n", name); free(name); } return restore(fp); } bool is_valid(struct game_t*); int restore(FILE* fp) { /* Read and restore game state from file, assuming * sane initial state. * If ADVENT_NOSAVE is defined, do nothing instead. */ #ifdef ADVENT_NOSAVE return GO_UNKNOWN; #endif IGNORE(fread(&save, sizeof(struct save_t), 1, fp)); fclose(fp); if (save.version != VRSION) { rspeak(VERSION_SKEW, save.version / 10, MOD(save.version, 10), VRSION / 10, MOD(VRSION, 10)); } else if (is_valid(&save.game)) { game = save.game; } return GO_TOP; } bool is_valid(struct game_t* valgame) { /* Save files can be roughly grouped into three groups: * With valid, reaceable state, with valid, but unreachable * state and with invaild state. We check that state is * valid: no states are outside minimal or maximal value */ /* Prevent division by zero */ if (valgame->abbnum == 0) { return false; } /* Check for RNG overflow. Truncate */ if (valgame->lcg_x >= LCG_M) { valgame->lcg_x %= LCG_M; } /* Check for RNG underflow. Transpose */ if (valgame->lcg_x < LCG_M) { valgame->lcg_x = LCG_M + (valgame->lcg_x % LCG_M); } /* Bounds check for locations */ if ( valgame->chloc < -1 || valgame->chloc > NLOCATIONS || valgame->chloc2 < -1 || valgame->chloc2 > NLOCATIONS || valgame->loc < 0 || valgame->loc > NLOCATIONS || valgame->newloc < 0 || valgame->newloc > NLOCATIONS || valgame->oldloc < 0 || valgame->oldloc > NLOCATIONS || valgame->oldlc2 < 0 || valgame->oldlc2 > NLOCATIONS) { return false; } /* Bounds check for location arrays */ for (int i = 0; i <= NDWARVES; i++) { if (valgame->dloc[i] < -1 || valgame->dloc[i] > NLOCATIONS || valgame->odloc[i] < -1 || valgame->odloc[i] > NLOCATIONS) { return false; } } for (int i = 0; i <= NOBJECTS; i++) { if (valgame->place[i] < -1 || valgame->place[i] > NLOCATIONS || valgame->fixed[i] < -1 || valgame->fixed[i] > NLOCATIONS) { return false; } } /* Bounds check for dwarves */ if (valgame->dtotal < 0 || valgame->dtotal > NDWARVES || valgame->dkill < 0 || valgame->dkill > NDWARVES) { return false; } /* Validate that we didn't die too many times in save */ if (valgame->numdie >= NDEATHS) { return false; } /* Recalculate tally, throw the towel if in disagreement */ long temp_tally = 0; for (int treasure = 1; treasure <= NOBJECTS; treasure++) { if (objects[treasure].is_treasure) { if (valgame->prop[treasure] == STATE_NOTFOUND) { ++temp_tally; } } } if (temp_tally != valgame->tally) { return false; } /* Check that properties of objects aren't beyond expected */ for (obj_t obj = 0; obj <= NOBJECTS; obj++) { if (valgame->prop[obj] < STATE_NOTFOUND || valgame->prop[obj] > 1) { switch (obj) { case RUG: case DRAGON: case BIRD: case BOTTLE: case PLANT: case PLANT2: case TROLL: case URN: case EGGS: case VASE: case CHAIN: if (valgame->prop[obj] == 2) // There are multiple different states, but it's convenient to clump them together continue; /* FALLTHRU */ case BEAR: if (valgame->prop[BEAR] == CONTENTED_BEAR || valgame->prop[BEAR] == BEAR_DEAD) continue; /* FALLTHRU */ default: return false; } } } /* Check that values in linked lists for objects in locations are inside bounds */ for (loc_t loc = LOC_NOWHERE; loc <= NLOCATIONS; loc++) { if (valgame->atloc[loc] < NO_OBJECT || valgame->atloc[loc] > NOBJECTS * 2) { return false; } } for (obj_t obj = 0; obj <= NOBJECTS * 2; obj++ ) { if (valgame->link[obj] < NO_OBJECT || valgame->link[obj] > NOBJECTS * 2) { return false; } } return true; } /* end */ /* * I/O and support riutines. * * 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 #include #include #include #define VERSION "1.40" /* #include */ #include static void* xcalloc(size_t size) { void* ptr = calloc(size, 1); if (ptr == NULL) { // LCOV_EXCL_START // exclude from coverage analysis because we can't simulate an out of memory error in testing fprintf(stderr, "Out of memory!\n"); exit(EXIT_FAILURE); // LCOV_EXCL_STOP } return (ptr); } /* I/O routines (speak, pspeak, rspeak, sspeak, get_input, yes) */ static void vspeak(const char* msg, bool blank, va_list ap) { // Do nothing if we got a null pointer. if (msg == NULL) return; // Do nothing if we got an empty string. if (strlen(msg) == 0) return; if (blank == true) printf("\n"); int msglen = strlen(msg); // Rendered string ssize_t size = 2000; /* msglen > 50 ? msglen*2 : 100; */ char* rendered = xcalloc(size); char* renderp = rendered; // Handle format specifiers (including the custom %S) by // adjusting the parameter accordingly, and replacing the // specifier with %s. bool pluralize = false; for (int i = 0; i < msglen; i++) { if (msg[i] != '%') { /* Ugh. Least obtrusive way to deal with artifacts "on the floor" * being dropped outside of both cave and building. */ if (strncmp(msg + i, "floor", 5) == 0 && strchr(" .", msg[i + 5]) && !INSIDE(game.loc)) { strcpy(renderp, "ground"); renderp += 6; i += 4; size -= 5; } else { *renderp++ = msg[i]; size--; } } else { i++; // Integer specifier. if (msg[i] == 'd') { int32_t arg = va_arg(ap, int32_t); int ret = snprintf(renderp, size, "%" PRId32, arg); if (ret < size) { renderp += ret; size -= ret; } pluralize = (arg != 1); } // Unmodified string specifier. if (msg[i] == 's') { char *arg = va_arg(ap, char *); strncat(renderp, arg, size - 1); size_t len = strlen(renderp); renderp += len; size -= len; } // Singular/plural specifier. if (msg[i] == 'S') { // look at the *previous* numeric parameter if (pluralize) { *renderp++ = 's'; size--; } } // LCOV_EXCL_START - doesn't occur in test suite. /* Version specifier */ if (msg[i] == 'V') { strcpy(renderp, VERSION); size_t len = strlen(VERSION); renderp += len; size -= len; } // LCOV_EXCL_STOP } } *renderp = 0; // Print the message. printf("%s\n", rendered); free(rendered); } void speak(const char* msg, ...) { va_list ap; va_start(ap, msg); vspeak(msg, true, ap); va_end(ap); } void sspeak(const int msg, ...) { va_list ap; va_start(ap, msg); fputc('\n', stdout); vprintf(arbitrary_messages[msg], ap); fputc('\n', stdout); va_end(ap); } void pspeak(vocab_t msg, enum speaktype mode, int skip, bool blank, ...) /* Find the skip+1st message from msg and print it. Modes are: * feel = for inventory, what you can touch * look = the full description for the state the object is in * listen = the sound for the state the object is in * study = text on the object. */ { va_list ap; va_start(ap, blank); switch (mode) { case touch: vspeak(objects[msg].inventory, blank, ap); break; case look: vspeak(objects[msg].descriptions[skip], blank, ap); break; case hear: vspeak(objects[msg].sounds[skip], blank, ap); break; case study: vspeak(objects[msg].texts[skip], blank, ap); break; case change: vspeak(objects[msg].changes[skip], blank, ap); break; } va_end(ap); } void rspeak(vocab_t i, ...) /* Print the i-th "random" message (section 6 of database). */ { va_list ap; va_start(ap, i); vspeak(arbitrary_messages[i], true, ap); va_end(ap); } void echo_input(FILE* destination, const char* input_prompt, const char* input) { size_t len = strlen(input_prompt) + strlen(input) + 1; char* prompt_and_input = (char*) xcalloc(len); strcpy(prompt_and_input, input_prompt); strcat(prompt_and_input, input); fprintf(destination, "%s\n", prompt_and_input); free(prompt_and_input); } static int word_count(char* str) { char delims[] = " \t"; int count = 0; int inblanks = true; for (char *s = str; *s; s++) if (inblanks) { if (strchr(delims, *s) == 0) { ++count; inblanks = false; } } else { if (strchr(delims, *s) != 0) { inblanks = true; } } return (count); } char *readline(char *prompt) { char *resp; printf("%s",prompt); resp=malloc(256); if (fgets(resp,255,stdin)==NULL) { free(resp); return(NULL); } return(resp); } static char* get_input(void) { // Set up the prompt char input_prompt[] = "> "; if (!settings.prompt) input_prompt[0] = '\0'; // Print a blank line printf("\n"); char *input; while (true) { char *readline(char *prompt); input = readline(input_prompt); if (input == NULL) // Got EOF; return with it. return (input); if (input[0] == '#') { // Ignore comments. free(input); continue; } // We have a 'normal' line; leave the loop. break; } // Strip trailing newlines from the input input[strcspn(input, "\n")] = 0; // add_history(input); if (!isatty(0)) echo_input(stdout, input_prompt, input); if (settings.logfp) echo_input(settings.logfp, "", input); return (input); } bool silent_yes(void) { bool outcome = false; for (;;) { char* reply = get_input(); if (reply == NULL) { // LCOV_EXCL_START // Should be unreachable. Reply should never be NULL free(reply); exit(EXIT_SUCCESS); // LCOV_EXCL_STOP } if (strlen(reply) == 0) { free(reply); rspeak(PLEASE_ANSWER); continue; } char* firstword = (char*) xcalloc(strlen(reply) + 1); sscanf(reply, "%s", firstword); free(reply); for (int i = 0; i < (int)strlen(firstword); ++i) firstword[i] = tolower(firstword[i]); int yes = strncmp("yes", firstword, sizeof("yes") - 1); int y = strncmp("y", firstword, sizeof("y") - 1); int no = strncmp("no", firstword, sizeof("no") - 1); int n = strncmp("n", firstword, sizeof("n") - 1); free(firstword); if (yes == 0 || y == 0) { outcome = true; break; } else if (no == 0 || n == 0) { outcome = false; break; } else rspeak(PLEASE_ANSWER); } return (outcome); } bool yes(const char* question, const char* yes_response, const char* no_response) /* Print message X, wait for yes/no answer. If yes, print Y and return true; * if no, print Z and return false. */ { bool outcome = false; for (;;) { speak(question); char* reply = get_input(); if (reply == NULL) { // LCOV_EXCL_START // Should be unreachable. Reply should never be NULL free(reply); exit(EXIT_SUCCESS); // LCOV_EXCL_STOP } if (strlen(reply) == 0) { free(reply); rspeak(PLEASE_ANSWER); continue; } char* firstword = (char*) xcalloc(strlen(reply) + 1); sscanf(reply, "%s", firstword); free(reply); for (int i = 0; i < (int)strlen(firstword); ++i) firstword[i] = tolower(firstword[i]); int yes = strncmp("yes", firstword, sizeof("yes") - 1); int y = strncmp("y", firstword, sizeof("y") - 1); int no = strncmp("no", firstword, sizeof("no") - 1); int n = strncmp("n", firstword, sizeof("n") - 1); free(firstword); if (yes == 0 || y == 0) { speak(yes_response); outcome = true; break; } else if (no == 0 || n == 0) { speak(no_response); outcome = false; break; } else rspeak(PLEASE_ANSWER); } return (outcome); } /* Data structure routines */ static int get_motion_vocab_id(const char* word) // Return the first motion number that has 'word' as one of its words. { for (int i = 0; i < NMOTIONS; ++i) { for (int j = 0; j < motions[i].words.n; ++j) { if (strncasecmp(word, motions[i].words.strs[j], TOKLEN) == 0 && (strlen(word) > 1 || strchr(ignore, word[0]) == NULL || !settings.oldstyle)) return (i); } } // If execution reaches here, we didn't find the word. return (WORD_NOT_FOUND); } static int get_object_vocab_id(const char* word) // Return the first object number that has 'word' as one of its words. { for (int i = 0; i < NOBJECTS + 1; ++i) { // FIXME: the + 1 should go when 1-indexing for objects is removed for (int j = 0; j < objects[i].words.n; ++j) { if (strncasecmp(word, objects[i].words.strs[j], TOKLEN) == 0) return (i); } } // If execution reaches here, we didn't find the word. return (WORD_NOT_FOUND); } static int get_action_vocab_id(const char* word) // Return the first motion number that has 'word' as one of its words. { for (int i = 0; i < NACTIONS; ++i) { for (int j = 0; j < actions[i].words.n; ++j) { if (strncasecmp(word, actions[i].words.strs[j], TOKLEN) == 0 && (strlen(word) > 1 || strchr(ignore, word[0]) == NULL || !settings.oldstyle)) return (i); } } // If execution reaches here, we didn't find the word. return (WORD_NOT_FOUND); } static bool is_valid_int(const char *str) /* Returns true if the string passed in is represents a valid integer, * that could then be parsed by atoi() */ { // Handle negative number if (*str == '-') ++str; // Handle empty string or just "-". Should never reach this // point, because this is only used with transitive verbs. if (!*str) return false; // LCOV_EXCL_LINE // Check for non-digit chars in the rest of the stirng. while (*str) { if (!isdigit(*str)) return false; else ++str; } return true; } static void get_vocab_metadata(command_word_t* word) { /* Check for an empty string */ if (strncmp(word->raw, "", sizeof("")) == 0) { word->id = WORD_EMPTY; word->type = NO_WORD_TYPE; return; } vocab_t ref_num; ref_num = get_motion_vocab_id(word->raw); if (ref_num != WORD_NOT_FOUND) { word->id = ref_num; word->type = MOTION; return; } ref_num = get_object_vocab_id(word->raw); if (ref_num != WORD_NOT_FOUND) { word->id = ref_num; word->type = OBJECT; return; } ref_num = get_action_vocab_id(word->raw); if (ref_num != WORD_NOT_FOUND) { word->id = ref_num; word->type = ACTION; return; } // Check for the reservoir magic word. if (strcasecmp(word->raw, game.zzword) == 0) { word->id = PART; word->type = ACTION; return; } // Check words that are actually numbers. if (is_valid_int(word->raw)) { word->id = WORD_EMPTY; word->type = NUMERIC; return; } word->id = WORD_NOT_FOUND; word->type = NO_WORD_TYPE; return; } static void tokenize(char* raw, command_t *cmd) { memset(cmd, '\0', sizeof(command_t)); /* Bound prefix on the %s would be needed to prevent buffer * overflow. but we shortstop this more simply by making each * raw-input buffer as long as the entire input buffer. */ sscanf(raw, "%s%s", cmd->word[0].raw, cmd->word[1].raw); /* (ESR) In oldstyle mode, simulate the uppercasing and truncating * effect on raw tokens of packing them into sixbit characters, 5 * to a 32-bit word. This is something the FORTRAN version did * becuse archaic FORTRAN had no string types. Don Wood's * mechanical translation of 2.5 to C retained the packing and * thus this misfeature. * * It's philosophically questionable whether this is the right * thing to do even in oldstyle mode. On one hand, the text * mangling was not authorial intent, but a result of limitations * in their tools. On the other, not simulating this misbehavior * goes against the goal of making oldstyle as accurate as * possible an emulation of the original UI. */ if (settings.oldstyle) { cmd->word[0].raw[TOKLEN + TOKLEN] = cmd->word[1].raw[TOKLEN + TOKLEN] = '\0'; for (size_t i = 0; i < strlen(cmd->word[0].raw); i++) cmd->word[0].raw[i] = toupper(cmd->word[0].raw[i]); for (size_t i = 0; i < strlen(cmd->word[1].raw); i++) cmd->word[1].raw[i] = toupper(cmd->word[1].raw[i]); } /* populate command with parsed vocabulary metadata */ get_vocab_metadata(&(cmd->word[0])); get_vocab_metadata(&(cmd->word[1])); } bool get_command_input(command_t *command) /* Get user input on stdin, parse and map to command */ { char inputbuf[LINESIZE]; char* input; for (;;) { input = get_input(); if (input == NULL) return false; if (word_count(input) > 2) { rspeak(TWO_WORDS); free(input); continue; } if (strcmp(input, "") != 0) break; free(input); } strncpy(inputbuf, input, LINESIZE - 1); free(input); tokenize(inputbuf, command); return true; } void juggle(obj_t object) /* Juggle an object by picking it up and putting it down again, the purpose * being to get the object to the front of the chain of things at its loc. */ { loc_t i, j; i = game.place[object]; j = game.fixed[object]; move(object, i); move(object + NOBJECTS, j); } void move(obj_t object, loc_t where) /* Place any object anywhere by picking it up and dropping it. May * already be toting, in which case the carry is a no-op. Mustn't * pick up objects which are not at any loc, since carry wants to * remove objects from game.atloc chains. */ { loc_t from; if (object > NOBJECTS) from = game.fixed[object - NOBJECTS]; else from = game.place[object]; /* (ESR) Used to check for !SPECIAL(from). I *think* that was wrong... */ if (from != LOC_NOWHERE && from != CARRIED) carry(object, from); drop(object, where); } loc_t put(obj_t object, loc_t where, long pval) /* put() is the same as move(), except it returns a value used to set up the * negated game.prop values for the repository objects. */ { move(object, where); return STASHED(pval); } void carry(obj_t object, loc_t where) /* Start toting an object, removing it from the list of things at its former * location. Incr holdng unless it was already being toted. If object>NOBJECTS * (moving "fixed" second loc), don't change game.place or game.holdng. */ { long temp; if (object <= NOBJECTS) { if (game.place[object] == CARRIED) return; game.place[object] = CARRIED; if (object!= BIRD) ++game.holdng; } if (game.atloc[where] == object) { game.atloc[where] = game.link[object]; return; } temp = game.atloc[where]; while (game.link[temp] != object) { temp = game.link[temp]; } game.link[temp] = game.link[object]; } void drop(obj_t object, loc_t where) /* Place an object at a given loc, prefixing it onto the game.atloc list. Decr * game.holdng if the object was being toted. */ { if (object > NOBJECTS) game.fixed[object - NOBJECTS] = where; else { if (game.place[object] == CARRIED) if (object != BIRD) /* The bird has to be weightless. This ugly hack (and the * corresponding code in the drop function) brought to you * by the fact that when the bird is caged, we need to be able * to either 'take bird' or 'take cage' and have the right thing * happen. */ --game.holdng; game.place[object] = where; } if (where == LOC_NOWHERE || where == CARRIED) return; game.link[object] = game.atloc[where]; game.atloc[where] = object; } int atdwrf(loc_t where) /* Return the index of first dwarf at the given location, zero if no dwarf is * there (or if dwarves not active yet), -1 if all dwarves are dead. Ignore * the pirate (6th dwarf). */ { int at; at = 0; if (game.dflag < 2) return at; at = -1; for (long i = 1; i <= NDWARVES - 1; i++) { if (game.dloc[i] == where) return i; if (game.dloc[i] != 0) at = 0; } return at; } /* Utility routines (setbit, tstbit, set_seed, get_next_lcg_value, * randrange) */ long setbit(int bit) /* Returns 2**bit for use in constructing bit-masks. */ { return (1L << bit); } bool tstbit(long mask, int bit) /* Returns true if the specified bit is set in the mask. */ { return (mask & (1 << bit)) != 0; } void set_seed(int32_t seedval) /* Set the LCG seed */ { game.lcg_x = seedval % LCG_M; if (game.lcg_x < 0) { game.lcg_x = LCG_M + game.lcg_x; } // once seed is set, we need to generate the Z`ZZZ word for (int i = 0; i < 5; ++i) { game.zzword[i] = 'A' + randrange(26); } game.zzword[1] = '\''; // force second char to apostrophe game.zzword[5] = '\0'; } static int32_t get_next_lcg_value(void) /* Return the LCG's current value, and then iterate it. */ { int32_t old_x = game.lcg_x; game.lcg_x = (LCG_A * game.lcg_x + LCG_C) % LCG_M; return old_x; } int32_t randrange(int32_t range) /* Return a random integer from [0, range). */ { return range * get_next_lcg_value() / LCG_M; } // LCOV_EXCL_START void bug(enum bugtype num, const char *error_string) { fprintf(stderr, "Fatal error %d, %s.\n", num, error_string); exit(EXIT_FAILURE); } // LCOV_EXCL_STOP /* end */ void state_change(obj_t obj, int state) /* Object must have a change-message list for this to be useful; only some do */ { game.prop[obj] = state; pspeak(obj, change, state, true); } /* end */ /* Generated from adventure.yaml - do not hand-hack! */ const char* arbitrary_messages[] = { NULL, "Somewhere nearby is Colossal Cave, where others have found fortunes in\ntreasure and gold, though it is rumored that some who enter are never\nseen again. Magic is said to work in the cave. I will be your eyes\nand hands. Direct me with commands of 1 or 2 words. I should warn\nyou that I look at only the first five letters of each word, so you\'ll\nhave to enter \"northeast\" as \"ne\" to distinguish it from \"north\".\nYou can type \"help\" for some general hints. For information on how\nto end your adventure, scoring, etc., type \"info\".\n\t\t\t - - -\nThis program was originally developed by Willie Crowther. Most of the\nfeatures of the current program were added by Don Woods.", "A little dwarf with a big knife blocks your way.", "A little dwarf just walked around a corner, saw you, threw a little\naxe at you which missed, cursed, and ran away.", "There are %d threatening little dwarves in the room with you.", "There is a threatening little dwarf in the room with you!", "One sharp nasty knife is thrown at you!", "A hollow voice says \"PLUGH\".", "It gets you!", "It misses!", "I am unsure how you are facing. Use compass points or nearby objects.", "I don\'t know in from out here. Use compass points or name something\nin the general direction you want to go.", "I don\'t know how to apply that word here.", "I\'m game. Would you care to explain how?", "Sorry, but I am not allowed to give more detail. I will repeat the\nlong description of your location.", "It is now pitch dark. If you proceed you will likely fall into a pit.", "If you prefer, simply type w rather than west.", "Do you really want to quit now?", "You fell into a pit and broke every bone in your body!", "You are already carrying it!", "You can\'t be serious!", "The bird seemed unafraid at first, but as you approach it becomes\ndisturbed and you cannot catch it.", "You can catch the bird, but you cannot carry it.", "There is nothing here with a lock!", "You aren\'t carrying it!", "The little bird attacks the green snake, and in an astounding flurry\ndrives the snake away.", "You have no keys!", "It has no lock.", "I don\'t know how to lock or unlock such a thing.", "It was already locked.", "It was already unlocked.", "There is no way to get past the bear to unlock the chain, which is\nprobably just as well.", "Nothing happens.", "Where?", "There is nothing here to attack.", "The little bird is now dead. Its body disappears.", "Attacking the snake both doesn\'t work and is very dangerous.", "You killed a little dwarf.", "You attack a little dwarf, but he dodges out of the way.", "With what? Your bare hands?", "There is no way to go that direction.", "Please stick to 1- and 2-word commands.", "OK", "You can\'t unlock the keys.", "You have crawled around in some little holes and wound up back in the\nmain passage.", "I don\'t know where the cave is, but hereabouts no stream can run on\nthe surface for long. I would try the stream.", "I need more detailed instructions to do that.", "I can only tell you what you see as you move about and manipulate\nthings. I cannot tell you where remote things are.", "The ogre snarls and shoves you back.", "Huh?", "Welcome to Adventure!! Would you like instructions?", "Blasting requires dynamite.", "Your feet are now wet.", "I think I just lost my appetite.", "Thank you, it was delicious!", "Peculiar. Nothing unexpected happens.", "Your bottle is empty and the ground is wet.", "You can\'t pour that.", "Which way?", "Sorry, but I no longer seem to remember how it was you got here.", "You can\'t carry anything more. You\'ll have to drop something first.", "You can\'t go through a locked steel grate!", "I believe what you want is right here with you.", "You don\'t fit through a two-inch slit!", "I respectfully suggest you go across the bridge instead of jumping.", "There is no way across the fissure.", "You\'re not carrying anything.", "You are currently holding the following:", "It\'s not hungry (it\'s merely pinin\' for the fjords). Besides, you\nhave no bird seed.", "The snake has now devoured your bird.", "There\'s nothing here it wants to eat (except perhaps you).", "You fool, dwarves eat only coal! Now you\'ve made him *REALLY* mad!!", "You have nothing in which to carry it.", "Your bottle is already full.", "There is nothing here with which to fill the bottle.", "Don\'t be ridiculous!", "The door is extremely rusty and refuses to open.", "The plant indignantly shakes the oil off its leaves and asks, \"Water?\"", "The plant has exceptionally deep roots and cannot be pulled free.", "The dwarves\' knives vanish as they strike the walls of the cave.", "Something you\'re carrying won\'t fit through the tunnel with you.\nYou\'d best take inventory and drop something.", "You can\'t fit this five-foot clam through that little passage!", "You can\'t fit this five-foot oyster through that little passage!", "I advise you to put down the oyster before opening it. >WRENCH!<", "You don\'t have anything strong enough to open the clam.", "You don\'t have anything strong enough to open the oyster.", "A glistening pearl falls out of the clam and rolls away. Goodness,\nthis must really be an oyster. (I never was very good at identifying\nbivalves.) Whatever it is, it has now snapped shut again.", "The oyster creaks open, revealing nothing but oyster inside. It\npromptly snaps shut again.", "You have crawled around in some little holes and found your way\nblocked by a recent cave-in. You are now back in the main passage.", "There are faint rustling noises from the darkness behind you.", "Out from the shadows behind you pounces a bearded pirate! \"Har, har,\"\nhe chortles, \"I\'ll just take all this booty and hide it away with me\nchest deep in the maze!\" He snatches your treasure and vanishes into\nthe gloom.", "A sepulchral voice reverberating through the cave, says, \"Cave closing\nsoon. All adventurers exit immediately through main office.\"", "A mysterious recorded voice groans into life and announces:\n \"This exit is closed. Please leave via main office.\"", "It looks as though you\'re dead. Well, seeing as how it\'s so close to\nclosing time anyway, I think we\'ll just call it a day.", "The sepulchral voice intones, \"The cave is now closed.\" As the echoes\nfade, there is a blinding flash of light (and a small puff of orange\nsmoke). . . . As your eyes refocus, you look around and find...", "There is a loud explosion, and a twenty-foot hole appears in the far\nwall, burying the dwarves in the rubble. You march through the hole\nand find yourself in the main office, where a cheering band of\nfriendly elves carry the conquering adventurer off into the sunset.", "There is a loud explosion, and a twenty-foot hole appears in the far\nwall, burying the snakes in the rubble. A river of molten lava pours\nin through the hole, destroying everything in its path, including you!", "There is a loud explosion, and you are suddenly splashed across the\nwalls of the room.", "The resulting ruckus has awakened the dwarves. There are now several\nthreatening little dwarves in the room with you! Most of them throw\nknives at you! All of them get you!", "Oh, leave the poor unhappy bird alone.", "I daresay whatever you want is around here somewhere.", "You can\'t get there from here.", "You are being followed by a very large, tame bear.", "Now let\'s see you do it without suspending in mid-Adventure.", "There is nothing here with which to fill it.", "The sudden change in temperature has delicately shattered the vase.", "It is beyond your power to do that.", "I don\'t know how.", "It is too far up for you to reach.", "You killed a little dwarf. The body vanishes in a cloud of greasy\nblack smoke.", "The shell is very strong and is impervious to attack.", "What\'s the matter, can\'t you read? Now you\'d best start over.", "Well, that was remarkably pointless.", "The axe bounces harmlessly off the dragon\'s thick scales.", "The dragon looks rather nasty. You\'d best not try to get by.", "The little bird attacks the green dragon, and in an astounding flurry\ngets burnt to a cinder. The ashes blow away.", "Okay, from now on I\'ll only describe a place in full the first time\nyou come to it. To get the full description, say \"look\".", "Trolls are close relatives with the rocks and have skin as tough as\nthat of a rhinoceros. The troll fends off your blows effortlessly.", "The troll deftly catches the axe, examines it carefully, and tosses it\nback, declaring, \"Good workmanship, but it\'s not valuable enough.\"", "The troll catches your treasure and scurries away out of sight.", "The troll refuses to let you cross.", "There is no longer any way across the chasm.", "With what? Your bare hands? Against *HIS* bear hands??", "The bear is confused; he only wants to be your friend.", "For crying out loud, the poor thing is already dead!", "The bear is still chained to the wall.", "The chain is still locked.", "The chain is now unlocked.", "The chain is now locked.", "There is nothing here to which the chain can be locked.", "Do you want the hint?", "Gluttony is not one of the troll\'s vices. Avarice, however, is.", "Your lamp is getting dim. You\'d best start wrapping this up, unless\nyou can find some fresh batteries. I seem to recall there\'s a vending\nmachine in the maze. Bring some coins with you.", "Your lamp has run out of power.", "Please answer the question.", "There are faint rustling noises from the darkness behind you. As you\nturn toward them, the beam of your lamp falls across a bearded pirate.\nHe is carrying a large chest. \"Shiver me timbers!\" he cries, \"I\'ve\nbeen spotted! I\'d best hie meself off to the maze to hide me chest!\"\nWith that, he vanishes into the gloom.", "Your lamp is getting dim. You\'d best go back for those batteries.", "Your lamp is getting dim. I\'m taking the liberty of replacing the\nbatteries.", "Your lamp is getting dim, and you\'re out of spare batteries. You\'d\nbest start wrapping this up.", "You sift your fingers through the dust, but succeed only in\nobliterating the cryptic message.", "Hmmm, this looks like a clue, which means it\'ll cost you 10 points to\nread it. Should I go ahead and read it anyway?", "It says, \"There is a way out of this place. Do you need any more\ninformation to escape? Sorry, but this initial hint is all you get.\"", "I\'m afraid I don\'t understand.", "Your hand passes through it as though it weren\'t there.", "You prod the nearest dwarf, who wakes up grumpily, takes one look at\nyou, curses, and grabs for his axe.", "Is this acceptable?", "The ogre doesn\'t appear to be hungry.", "The ogre, who despite his bulk is quite agile, easily dodges your\nattack. He seems almost amused by your puny effort.", "The ogre, distracted by your rush, is struck by the knife. With a\nblood-curdling yell he turns and bounds after the dwarves, who flee\nin panic. You are left alone in the room.", "The ogre, distracted by your rush, is struck by the knife. With a\nblood-curdling yell he turns and bounds after the dwarf, who flees\nin panic. You are left alone in the room.", "The bird flies about agitatedly for a moment.", "The bird flies agitatedly about the cage.", "The bird flies about agitatedly for a moment, then disappears through\nthe crack. It reappears shortly, carrying in its beak a jade\nnecklace, which it drops at your feet.", "You empty the bottle into the urn, which promptly ejects the water\nwith uncanny accuracy, squirting you directly between the eyes.", "Your bottle is now empty and the urn is full of oil.", "The urn is already full of oil.", "There\'s no way to get the oil out of the urn.", "The urn is far too firmly embedded for your puny strength to budge it.", "As you rub the urn, there is a flash of light and a genie appears.\nHis aspect is stern as he advises: \"One who wouldst traffic in\nprecious stones must first learn to recognize the signals thereof.\"\nHe wrests the urn from the stone, leaving a small cavity. Turning to\nface you again, he fixes you with a steely eye and intones: \"Caution!\"\nGenie and urn vanish in a cloud of amber smoke. The smoke condenses\nto form a rare amber gemstone, resting in the cavity in the rock.", "I suppose you collect doughnut holes, too?", "The gem fits easily into the cavity.", "The persian rug stiffens and rises a foot or so off the ground.", "The persian rug draped over your shoulder seems to wriggle for a\nmoment, but then subsides.", "The persian rug settles gently to the ground.", "The rug hovers stubbornly where it is.", "The rug does not appear inclined to cooperate.", "If you mean to use the persian rug, it does not appear inclined to\ncooperate.", "Though you flap your arms furiously, it is to no avail.", "You board the persian rug, which promptly whisks you across the chasm.\nYou have time for a fleeting glimpse of a two thousand foot drop to a\nmighty river; then you find yourself on the other side.", "The rug ferries you back across the chasm.", "All is silent.", "The stream is gurgling placidly.", "The wind whistles coldly past your ears.", "The stream splashes loudly into the pool.", "You are unable to make anything of the splashing noise.", "You can hear the murmuring of the beanstalks and the snoring of the\ndwarves.", "A loud hissing emanates from the snake pit.", "The air is filled with a dull rumbling sound.", "The roar is quite loud here.", "The roaring is so loud that it drowns out all other sound.", "The bird eyes you suspiciously and flutters away. A moment later you\nfeel something wet land on your head, but upon looking up you can see\nno sign of the culprit.", "There are only a few drops--not enough to carry.", "(Uh, y\'know, that wasn\'t very bright.)", "It\'s a pity you took so long about it.", "Upstream or downstream?", NULL, "The waters are crashing loudly against the shore.", "%d of them throw knives at you!", "%d of them get you!", "One of them gets you!", "None of them hits you!", "Sorry, I don\'t know the word \"%s\".", "What do you want to do with the %s?", "I see no %s here.", "%s what?", "Okay, \"%s\".", "You have garnered %d out of a possible %d points, using %d turn%S.", "I can suspend your Adventure for you so that you can resume later, but\nit will cost you 5 points.", "I am prepared to give you a hint, but it will cost you %d point%S.", "You scored %d out of a possible %d, using %d turn%S.", "To achieve the next higher rating, you need %d more point%S.", "To achieve the next higher rating would be a neat trick!\nCongratulations!!", "You just went off my scale!!", "To resume your Adventure, start a new game and then say \"RESUME\".", "To resume an earlier Adventure, you must abandon the current one.", "I\'m sorry, but that Adventure was begun using Version %d.%d of the\nsave file format, and this program uses Version %d.%d. You must find an instance\nusing that other version in order to resume that Adventure.", "Sorry, but the path twisted and turned so much that I can\'t figure\nout which way to go to get back.", "You don\'t have to say \"go\" every time; just specify a direction or, if\nit\'s nearby, name the place to which you wish to move.", "This command requires a numeric argument.", }; const class_t classes[] = { { .threshold = 0, .message = NULL, }, { .threshold = 45, .message = "You are obviously a rank amateur. Better luck next time.", }, { .threshold = 120, .message = "Your score qualifies you as a novice class adventurer.", }, { .threshold = 170, .message = "You have achieved the rating: \"Experienced Adventurer\".", }, { .threshold = 250, .message = "You may now consider yourself a \"Seasoned Adventurer\".", }, { .threshold = 320, .message = "You have reached \"Junior Master\" status.", }, { .threshold = 375, .message = "Your score puts you in Master Adventurer Class C.", }, { .threshold = 410, .message = "Your score puts you in Master Adventurer Class B.", }, { .threshold = 426, .message = "Your score puts you in Master Adventurer Class A.", }, { .threshold = 429, .message = "All of Adventuredom gives tribute to you, Adventurer Grandmaster!", }, { .threshold = 9999, .message = "\'Adventuredom stands in awe -- you have now joined the ranks of the\n W O R L D C H A M P I O N A D V E N T U R E R S !\nIt may interest you to know that the Dungeon-Master himself has, to\nmy knowledge, never achieved this threshhold in fewer than 330 turns.\'", }, }; const turn_threshold_t turn_thresholds[] = { { .threshold = 350, .point_loss = 2, .message = "Tsk! A wizard wouldn\'t have to take 350 turns. This is going to cost\nyou a couple of points.", }, { .threshold = 500, .point_loss = 3, .message = "500 turns? That\'s another few points you\'ve lost.", }, { .threshold = 1000, .point_loss = 5, .message = "Are you still at it? Five points off for exceeding 1000 turns!", }, { .threshold = 2500, .point_loss = 10, .message = "Good grief, don\'t you *EVER* give up? Do you realize you\'ve spent\nover 2500 turns at this? That\'s another ten points off, a total of\ntwenty points lost for taking so long.", }, }; const location_t locations[] = { { // 0: LOC_NOWHERE .description = { .small = NULL, .big = NULL, }, .sound = SILENT, .loud = false, }, { // 1: LOC_START .description = { .small = "You\'re in front of building.", .big = "You are standing at the end of a road before a small brick building.\nAround you is a forest. A small stream flows out of the building and\ndown a gully.", }, .sound = STREAM_GURGLES, .loud = false, }, { // 2: LOC_HILL .description = { .small = "You\'re at hill in road.", .big = "You have walked up a hill, still in the forest. The road slopes back\ndown the other side of the hill. There is a building in the distance.", }, .sound = SILENT, .loud = false, }, { // 3: LOC_BUILDING .description = { .small = "You\'re inside building.", .big = "You are inside a building, a well house for a large spring.", }, .sound = STREAM_GURGLES, .loud = false, }, { // 4: LOC_VALLEY .description = { .small = "You\'re in valley.", .big = "You are in a valley in the forest beside a stream tumbling along a\nrocky bed.", }, .sound = STREAM_GURGLES, .loud = false, }, { // 5: LOC_ROADEND .description = { .small = "You\'re at end of road.", .big = "The road, which approaches from the east, ends here amid the trees.", }, .sound = SILENT, .loud = false, }, { // 6: LOC_CLIFF .description = { .small = "You\'re at cliff.", .big = "The forest thins out here to reveal a steep cliff. There is no way\ndown, but a small ledge can be seen to the west across the chasm.", }, .sound = SILENT, .loud = false, }, { // 7: LOC_SLIT .description = { .small = "You\'re at slit in streambed.", .big = "At your feet all the water of the stream splashes into a 2-inch slit\nin the rock. Downstream the streambed is bare rock.", }, .sound = STREAM_GURGLES, .loud = false, }, { // 8: LOC_GRATE .description = { .small = "You\'re outside grate.", .big = "You are in a 20-foot depression floored with bare dirt. Set into the\ndirt is a strong steel grate mounted in concrete. A dry streambed\nleads into the depression.", }, .sound = SILENT, .loud = false, }, { // 9: LOC_BELOWGRATE .description = { .small = "You\'re below the grate.", .big = "You are in a small chamber beneath a 3x3 steel grate to the surface.\nA low crawl over cobbles leads inward to the west.", }, .sound = SILENT, .loud = false, }, { // 10: LOC_COBBLE .description = { .small = "You\'re in cobble crawl.", .big = "You are crawling over cobbles in a low passage. There is a dim light\nat the east end of the passage.", }, .sound = SILENT, .loud = false, }, { // 11: LOC_DEBRIS .description = { .small = "You\'re in debris room.", .big = "You are in a debris room filled with stuff washed in from the surface.\nA low wide passage with cobbles becomes plugged with mud and debris\nhere, but an awkward canyon leads upward and west. In the mud someone\nhas scrawled, \"MAGIC WORD XYZZY\".", }, .sound = SILENT, .loud = false, }, { // 12: LOC_AWKWARD .description = { .small = NULL, .big = "You are in an awkward sloping east/west canyon.", }, .sound = SILENT, .loud = false, }, { // 13: LOC_BIRD .description = { .small = "You\'re in bird chamber.", .big = "You are in a splendid chamber thirty feet high. The walls are frozen\nrivers of orange stone. An awkward canyon and a good passage exit\nfrom east and west sides of the chamber.", }, .sound = SILENT, .loud = false, }, { // 14: LOC_PITTOP .description = { .small = "You\'re at top of small pit.", .big = "At your feet is a small pit breathing traces of white mist. An east\npassage ends here except for a small crack leading on.", }, .sound = SILENT, .loud = false, }, { // 15: LOC_MISTHALL .description = { .small = "You\'re in Hall of Mists.", .big = "You are at one end of a vast hall stretching forward out of sight to\nthe west. There are openings to either side. Nearby, a wide stone\nstaircase leads downward. The hall is filled with wisps of white mist\nswaying to and fro almost as if alive. A cold wind blows up the\nstaircase. There is a passage at the top of a dome behind you.", }, .sound = WIND_WHISTLES, .loud = false, }, { // 16: LOC_CRACK .description = { .small = NULL, .big = "The crack is far too small for you to follow. At its widest it is\nbarely wide enough to admit your foot.\'", }, .sound = SILENT, .loud = false, }, { // 17: LOC_EASTBANK .description = { .small = "You\'re on east bank of fissure.", .big = "You are on the east bank of a fissure slicing clear across the hall.\nThe mist is quite thick here, and the fissure is too wide to jump.", }, .sound = SILENT, .loud = false, }, { // 18: LOC_NUGGET .description = { .small = "You\'re in nugget-of-gold room.", .big = "This is a low room with a crude note on the wall. The note says,\n\"You won\'t get it up the steps\".", }, .sound = SILENT, .loud = false, }, { // 19: LOC_KINGHALL .description = { .small = "You\'re in Hall of Mt King.", .big = "You are in the Hall of the Mountain King, with passages off in all\ndirections.", }, .sound = SILENT, .loud = false, }, { // 20: LOC_NECKBROKE .description = { .small = NULL, .big = "You are at the bottom of the pit with a broken neck.", }, .sound = SILENT, .loud = false, }, { // 21: LOC_NOMAKE .description = { .small = NULL, .big = "You didn\'t make it.", }, .sound = SILENT, .loud = false, }, { // 22: LOC_DOME .description = { .small = NULL, .big = "The dome is unclimbable.", }, .sound = SILENT, .loud = false, }, { // 23: LOC_WESTEND .description = { .small = "You\'re at west end of Twopit Room.", .big = "You are at the west end of the Twopit Room. There is a large hole in\nthe wall above the pit at this end of the room.", }, .sound = SILENT, .loud = false, }, { // 24: LOC_EASTPIT .description = { .small = "You\'re in east pit.", .big = "You are at the bottom of the eastern pit in the Twopit Room. There is\na small pool of oil in one corner of the pit.", }, .sound = SILENT, .loud = false, }, { // 25: LOC_WESTPIT .description = { .small = "You\'re in west pit.", .big = "You are at the bottom of the western pit in the Twopit Room. There is\na large hole in the wall about 25 feet above you.", }, .sound = SILENT, .loud = false, }, { // 26: LOC_CLIMBSTALK .description = { .small = NULL, .big = "You clamber up the plant and scurry through the hole at the top.", }, .sound = SILENT, .loud = false, }, { // 27: LOC_WESTBANK .description = { .small = "You\'re on west bank of fissure.", .big = "You are on the west side of the fissure in the Hall of Mists.", }, .sound = SILENT, .loud = false, }, { // 28: LOC_FLOORHOLE .description = { .small = "You\'re in n/s passage above e/w passage.", .big = "You are in a low n/s passage at a hole in the floor. The hole goes\ndown to an e/w passage.", }, .sound = SILENT, .loud = false, }, { // 29: LOC_SOUTHSIDE .description = { .small = NULL, .big = "You are in the south side chamber.", }, .sound = SILENT, .loud = false, }, { // 30: LOC_WESTSIDE .description = { .small = "You\'re in the west side chamber.", .big = "You are in the west side chamber of the Hall of the Mountain King.\nA passage continues west and up here.", }, .sound = SILENT, .loud = false, }, { // 31: LOC_BUILDING1 .description = { .small = NULL, .big = "", }, .sound = SILENT, .loud = false, }, { // 32: LOC_SNAKEBLOCK .description = { .small = NULL, .big = "You can\'t get by the snake.", }, .sound = SILENT, .loud = false, }, { // 33: LOC_Y2 .description = { .small = "You\'re at \"Y2\".", .big = "You are in a large room, with a passage to the south, a passage to the\nwest, and a wall of broken rock to the east. There is a large \"Y2\" on\na rock in the room\'s center.", }, .sound = SILENT, .loud = false, }, { // 34: LOC_JUMBLE .description = { .small = NULL, .big = "You are in a jumble of rock, with cracks everywhere.", }, .sound = SILENT, .loud = false, }, { // 35: LOC_WINDOW1 .description = { .small = "You\'re at window on pit.", .big = "You\'re at a low window overlooking a huge pit, which extends up out of\nsight. A floor is indistinctly visible over 50 feet below. Traces of\nwhite mist cover the floor of the pit, becoming thicker to the right.\nMarks in the dust around the window would seem to indicate that\nsomeone has been here recently. Directly across the pit from you and\n25 feet away there is a similar window looking into a lighted room. A\nshadowy figure can be seen there peering back at you.", }, .sound = SILENT, .loud = false, }, { // 36: LOC_BROKEN .description = { .small = "You\'re in dirty passage.", .big = "You are in a dirty broken passage. To the east is a crawl. To the\nwest is a large passage. Above you is a hole to another passage.", }, .sound = SILENT, .loud = false, }, { // 37: LOC_SMALLPITBRINK .description = { .small = "You\'re at brink of small pit.", .big = "You are on the brink of a small clean climbable pit. A crawl leads\nwest.", }, .sound = SILENT, .loud = false, }, { // 38: LOC_SMALLPIT .description = { .small = "You\'re at bottom of pit with stream.", .big = "You are in the bottom of a small pit with a little stream, which\nenters and exits through tiny slits.", }, .sound = STREAM_GURGLES, .loud = false, }, { // 39: LOC_DUSTY .description = { .small = "You\'re in dusty rock room.", .big = "You are in a large room full of dusty rocks. There is a big hole in\nthe floor. There are cracks everywhere, and a passage leading east.", }, .sound = SILENT, .loud = false, }, { // 40: LOC_PARALLEL1 .description = { .small = NULL, .big = "You have crawled through a very low wide passage parallel to and north\nof the Hall of Mists.", }, .sound = SILENT, .loud = false, }, { // 41: LOC_MISTWEST .description = { .small = "You\'re at west end of Hall of Mists.", .big = "You are at the west end of the Hall of Mists. A low wide crawl\ncontinues west and another goes north. To the south is a little\npassage 6 feet off the floor.", }, .sound = SILENT, .loud = false, }, { // 42: LOC_ALIKE1 .description = { .small = NULL, .big = "You are in a maze of twisty little passages, all alike.", }, .sound = SILENT, .loud = false, }, { // 43: LOC_ALIKE2 .description = { .small = NULL, .big = "You are in a maze of twisty little passages, all alike.", }, .sound = SILENT, .loud = false, }, { // 44: LOC_ALIKE3 .description = { .small = NULL, .big = "You are in a maze of twisty little passages, all alike.", }, .sound = SILENT, .loud = false, }, { // 45: LOC_ALIKE4 .description = { .small = NULL, .big = "You are in a maze of twisty little passages, all alike.", }, .sound = SILENT, .loud = false, }, { // 46: LOC_DEADEND1 .description = { .small = NULL, .big = "Dead end", }, .sound = SILENT, .loud = false, }, { // 47: LOC_DEADEND2 .description = { .small = NULL, .big = "Dead end", }, .sound = SILENT, .loud = false, }, { // 48: LOC_DEADEND3 .description = { .small = NULL, .big = "Dead end", }, .sound = SILENT, .loud = false, }, { // 49: LOC_ALIKE5 .description = { .small = NULL, .big = "You are in a maze of twisty little passages, all alike.", }, .sound = SILENT, .loud = false, }, { // 50: LOC_ALIKE6 .description = { .small = NULL, .big = "You are in a maze of twisty little passages, all alike.", }, .sound = SILENT, .loud = false, }, { // 51: LOC_ALIKE7 .description = { .small = NULL, .big = "You are in a maze of twisty little passages, all alike.", }, .sound = SILENT, .loud = false, }, { // 52: LOC_ALIKE8 .description = { .small = NULL, .big = "You are in a maze of twisty little passages, all alike.", }, .sound = SILENT, .loud = false, }, { // 53: LOC_ALIKE9 .description = { .small = NULL, .big = "You are in a maze of twisty little passages, all alike.", }, .sound = SILENT, .loud = false, }, { // 54: LOC_DEADEND4 .description = { .small = NULL, .big = "Dead end", }, .sound = SILENT, .loud = false, }, { // 55: LOC_ALIKE10 .description = { .small = NULL, .big = "You are in a maze of twisty little passages, all alike.", }, .sound = SILENT, .loud = false, }, { // 56: LOC_DEADEND5 .description = { .small = NULL, .big = "Dead end", }, .sound = SILENT, .loud = false, }, { // 57: LOC_PITBRINK .description = { .small = "You\'re at brink of pit.", .big = "You are on the brink of a thirty foot pit with a massive orange column\ndown one wall. You could climb down here but you could not get back\nup. The maze continues at this level.", }, .sound = SILENT, .loud = false, }, { // 58: LOC_DEADEND6 .description = { .small = NULL, .big = "Dead end", }, .sound = SILENT, .loud = false, }, { // 59: LOC_PARALLEL2 .description = { .small = NULL, .big = "You have crawled through a very low wide passage parallel to and north\nof the Hall of Mists.", }, .sound = SILENT, .loud = false, }, { // 60: LOC_LONGEAST .description = { .small = "You\'re at east end of long hall.", .big = "You are at the east end of a very long hall apparently without side\nchambers. To the east a low wide crawl slants up. To the north a\nround two foot hole slants down.", }, .sound = SILENT, .loud = false, }, { // 61: LOC_LONGWEST .description = { .small = "You\'re at west end of long hall.", .big = "You are at the west end of a very long featureless hall. The hall\njoins up with a narrow north/south passage.", }, .sound = SILENT, .loud = false, }, { // 62: LOC_CROSSOVER .description = { .small = NULL, .big = "You are at a crossover of a high n/s passage and a low e/w one.", }, .sound = SILENT, .loud = false, }, { // 63: LOC_DEADEND7 .description = { .small = NULL, .big = "Dead end", }, .sound = SILENT, .loud = false, }, { // 64: LOC_COMPLEX .description = { .small = "You\'re at complex junction.", .big = "You are at a complex junction. A low hands and knees passage from the\nnorth joins a higher crawl from the east to make a walking passage\ngoing west. There is also a large room above. The air is damp here.", }, .sound = WIND_WHISTLES, .loud = false, }, { // 65: LOC_BEDQUILT .description = { .small = "You\'re in Bedquilt.", .big = "You are in Bedquilt, a long east/west passage with holes everywhere.\nTo explore at random select north, south, up, or down.", }, .sound = SILENT, .loud = false, }, { // 66: LOC_SWISSCHEESE .description = { .small = "You\'re in Swiss Cheese Room.", .big = "You are in a room whose walls resemble swiss cheese. Obvious passages\ngo west, east, ne, and nw. Part of the room is occupied by a large\nbedrock block.", }, .sound = SILENT, .loud = false, }, { // 67: LOC_EASTEND .description = { .small = "You\'re at east end of Twopit Room.", .big = "You are at the east end of the Twopit Room. The floor here is\nlittered with thin rock slabs, which make it easy to descend the pits.\nThere is a path here bypassing the pits to connect passages from east\nand west. There are holes all over, but the only big one is on the\nwall directly over the west pit where you can\'t get to it.", }, .sound = SILENT, .loud = false, }, { // 68: LOC_SLAB .description = { .small = "You\'re in Slab Room.", .big = "You are in a large low circular chamber whose floor is an immense slab\nfallen from the ceiling (Slab Room). East and west there once were\nlarge passages, but they are now filled with boulders. Low small\npassages go north and south, and the south one quickly bends west\naround the boulders.", }, .sound = SILENT, .loud = false, }, { // 69: LOC_SECRET1 .description = { .small = NULL, .big = "You are in a secret n/s canyon above a large room.", }, .sound = SILENT, .loud = false, }, { // 70: LOC_SECRET2 .description = { .small = NULL, .big = "You are in a secret n/s canyon above a sizable passage.", }, .sound = SILENT, .loud = false, }, { // 71: LOC_THREEJUNCTION .description = { .small = "You\'re at junction of three secret canyons.", .big = "You are in a secret canyon at a junction of three canyons, bearing\nnorth, south, and se. The north one is as tall as the other two\ncombined.", }, .sound = SILENT, .loud = false, }, { // 72: LOC_LOWROOM .description = { .small = "You\'re in large low room.", .big = "You are in a large low room. Crawls lead north, se, and sw.", }, .sound = SILENT, .loud = false, }, { // 73: LOC_DEADCRAWL .description = { .small = NULL, .big = "Dead end crawl.", }, .sound = SILENT, .loud = false, }, { // 74: LOC_SECRET3 .description = { .small = "You\'re in secret e/w canyon above tight canyon.", .big = "You are in a secret canyon which here runs e/w. It crosses over a\nvery tight canyon 15 feet below. If you go down you may not be able\nto get back up.", }, .sound = SILENT, .loud = false, }, { // 75: LOC_WIDEPLACE .description = { .small = NULL, .big = "You are at a wide place in a very tight n/s canyon.", }, .sound = SILENT, .loud = false, }, { // 76: LOC_TIGHTPLACE .description = { .small = NULL, .big = "The canyon here becomes too tight to go further south.", }, .sound = SILENT, .loud = false, }, { // 77: LOC_TALL .description = { .small = NULL, .big = "You are in a tall e/w canyon. A low tight crawl goes 3 feet north and\nseems to open up.", }, .sound = SILENT, .loud = false, }, { // 78: LOC_BOULDERS1 .description = { .small = NULL, .big = "The canyon runs into a mass of boulders -- dead end.", }, .sound = SILENT, .loud = false, }, { // 79: LOC_SEWER .description = { .small = NULL, .big = "The stream flows out through a pair of 1 foot diameter sewer pipes.\nIt would be advisable to use the exit.", }, .sound = SILENT, .loud = false, }, { // 80: LOC_ALIKE11 .description = { .small = NULL, .big = "You are in a maze of twisty little passages, all alike.", }, .sound = SILENT, .loud = false, }, { // 81: LOC_DEADEND8 .description = { .small = NULL, .big = "Dead end", }, .sound = SILENT, .loud = false, }, { // 82: LOC_DEADEND9 .description = { .small = NULL, .big = "Dead end", }, .sound = SILENT, .loud = false, }, { // 83: LOC_ALIKE12 .description = { .small = NULL, .big = "You are in a maze of twisty little passages, all alike.", }, .sound = SILENT, .loud = false, }, { // 84: LOC_ALIKE13 .description = { .small = NULL, .big = "You are in a maze of twisty little passages, all alike.", }, .sound = SILENT, .loud = false, }, { // 85: LOC_DEADEND10 .description = { .small = NULL, .big = "Dead end", }, .sound = SILENT, .loud = false, }, { // 86: LOC_DEADEND11 .description = { .small = NULL, .big = "Dead end", }, .sound = SILENT, .loud = false, }, { // 87: LOC_ALIKE14 .description = { .small = NULL, .big = "You are in a maze of twisty little passages, all alike.", }, .sound = SILENT, .loud = false, }, { // 88: LOC_NARROW .description = { .small = "You\'re in narrow corridor.", .big = "You are in a long, narrow corridor stretching out of sight to the\nwest. At the eastern end is a hole through which you can see a\nprofusion of leaves.", }, .sound = SILENT, .loud = false, }, { // 89: LOC_NOCLIMB .description = { .small = NULL, .big = "There is nothing here to climb. Use \"up\" or \"out\" to leave the pit.", }, .sound = SILENT, .loud = false, }, { // 90: LOC_PLANTTOP .description = { .small = NULL, .big = "You have climbed up the plant and out of the pit.", }, .sound = SILENT, .loud = false, }, { // 91: LOC_INCLINE .description = { .small = "You\'re at steep incline above large room.", .big = "You are at the top of a steep incline above a large room. You could\nclimb down here, but you would not be able to climb up. There is a\npassage leading back to the north.", }, .sound = SILENT, .loud = false, }, { // 92: LOC_GIANTROOM .description = { .small = "You\'re in Giant Room.", .big = "You are in the Giant Room. The ceiling here is too high up for your\nlamp to show it. Cavernous passages lead east, north, and south. On\nthe west wall is scrawled the inscription, \"FEE FIE FOE FOO\" [sic].", }, .sound = SILENT, .loud = false, }, { // 93: LOC_CAVEIN .description = { .small = NULL, .big = "The passage here is blocked by a recent cave-in.", }, .sound = SILENT, .loud = false, }, { // 94: LOC_IMMENSE .description = { .small = NULL, .big = "You are at one end of an immense north/south passage.", }, .sound = WIND_WHISTLES, .loud = false, }, { // 95: LOC_WATERFALL .description = { .small = "You\'re in cavern with waterfall.", .big = "You are in a magnificent cavern with a rushing stream, which cascades\nover a sparkling waterfall into a roaring whirlpool which disappears\nthrough a hole in the floor. Passages exit to the south and west.", }, .sound = STREAM_SPLASHES, .loud = false, }, { // 96: LOC_SOFTROOM .description = { .small = "You\'re in Soft Room.", .big = "You are in the Soft Room. The walls are covered with heavy curtains,\nthe floor with a thick pile carpet. Moss covers the ceiling.", }, .sound = SILENT, .loud = false, }, { // 97: LOC_ORIENTAL .description = { .small = "You\'re in Oriental Room.", .big = "This is the Oriental Room. Ancient oriental cave drawings cover the\nwalls. A gently sloping passage leads upward to the north, another\npassage leads se, and a hands and knees crawl leads west.", }, .sound = SILENT, .loud = false, }, { // 98: LOC_MISTY .description = { .small = "You\'re in misty cavern.", .big = "You are following a wide path around the outer edge of a large cavern.\nFar below, through a heavy white mist, strange splashing noises can be\nheard. The mist rises up through a fissure in the ceiling. The path\nexits to the south and west.", }, .sound = NO_MEANING, .loud = false, }, { // 99: LOC_ALCOVE .description = { .small = "You\'re in alcove.", .big = "You are in an alcove. A small nw path seems to widen after a short\ndistance. An extremely tight tunnel leads east. It looks like a very\ntight squeeze. An eerie light can be seen at the other end.", }, .sound = SILENT, .loud = false, }, { // 100: LOC_PLOVER .description = { .small = "You\'re in Plover Room.", .big = "You\'re in a small chamber lit by an eerie green light. An extremely\nnarrow tunnel exits to the west. A dark corridor leads ne.", }, .sound = SILENT, .loud = false, }, { // 101: LOC_DARKROOM .description = { .small = "You\'re in dark-room.", .big = "You\'re in the dark-room. A corridor leading south is the only exit.", }, .sound = SILENT, .loud = false, }, { // 102: LOC_ARCHED .description = { .small = "You\'re in arched hall.", .big = "You are in an arched hall. A coral passage once continued up and east\nfrom here, but is now blocked by debris. The air smells of sea water.", }, .sound = SILENT, .loud = false, }, { // 103: LOC_SHELLROOM .description = { .small = "You\'re in Shell Room.", .big = "You\'re in a large room carved out of sedimentary rock. The floor and\nwalls are littered with bits of shells imbedded in the stone. A\nshallow passage proceeds downward, and a somewhat steeper one leads\nup. A low hands and knees passage enters from the south.", }, .sound = SILENT, .loud = false, }, { // 104: LOC_SLOPING1 .description = { .small = NULL, .big = "You are in a long sloping corridor with ragged sharp walls.", }, .sound = SILENT, .loud = false, }, { // 105: LOC_CULDESAC .description = { .small = NULL, .big = "You are in a cul-de-sac about eight feet across.", }, .sound = SILENT, .loud = false, }, { // 106: LOC_ANTEROOM .description = { .small = "You\'re in anteroom.", .big = "You are in an anteroom leading to a large passage to the east. Small\npassages go west and up. The remnants of recent digging are evident.\nA sign in midair here says \"Cave under construction beyond this point.\nProceed at own risk. [Witt Construction Company]\"", }, .sound = SILENT, .loud = false, }, { // 107: LOC_DIFFERENT1 .description = { .small = NULL, .big = "You are in a maze of twisty little passages, all different.", }, .sound = SILENT, .loud = false, }, { // 108: LOC_WITTSEND .description = { .small = "You\'re at Witt\'s End.", .big = "You are at Witt\'s End. Passages lead off in *ALL* directions.", }, .sound = SILENT, .loud = false, }, { // 109: LOC_MIRRORCANYON .description = { .small = "You\'re in Mirror Canyon.", .big = "You are in a north/south canyon about 25 feet across. The floor is\ncovered by white mist seeping in from the north. The walls extend\nupward for well over 100 feet. Suspended from some unseen point far\nabove you, an enormous two-sided mirror is hanging parallel to and\nmidway between the canyon walls. (The mirror is obviously provided\nfor the use of the dwarves who, as you know, are extremely vain.) A\nsmall window can be seen in either wall, some fifty feet up.", }, .sound = WIND_WHISTLES, .loud = false, }, { // 110: LOC_WINDOW2 .description = { .small = "You\'re at window on pit.", .big = "You\'re at a low window overlooking a huge pit, which extends up out of\nsight. A floor is indistinctly visible over 50 feet below. Traces of\nwhite mist cover the floor of the pit, becoming thicker to the left.\nMarks in the dust around the window would seem to indicate that\nsomeone has been here recently. Directly across the pit from you and\n25 feet away there is a similar window looking into a lighted room. A\nshadowy figure can be seen there peering back at you.", }, .sound = SILENT, .loud = false, }, { // 111: LOC_TOPSTALACTITE .description = { .small = "You\'re at top of stalactite.", .big = "A large stalactite extends from the roof and almost reaches the floor\nbelow. You could climb down it, and jump from it to the floor, but\nhaving done so you would be unable to reach it to climb back up.", }, .sound = SILENT, .loud = false, }, { // 112: LOC_DIFFERENT2 .description = { .small = NULL, .big = "You are in a little maze of twisting passages, all different.", }, .sound = SILENT, .loud = false, }, { // 113: LOC_RESERVOIR .description = { .small = "You\'re at reservoir.", .big = "You are at the edge of a large underground reservoir. An opaque cloud\nof white mist fills the room and rises rapidly upward. The lake is\nfed by a stream, which tumbles out of a hole in the wall about 10 feet\noverhead and splashes noisily into the water somewhere within the\nmist. There is a passage going back toward the south.", }, .sound = STREAM_SPLASHES, .loud = false, }, { // 114: LOC_DEADEND12 .description = { .small = NULL, .big = "Dead end", }, .sound = SILENT, .loud = false, }, { // 115: LOC_NE .description = { .small = "You\'re at ne end.", .big = "You are at the northeast end of an immense room, even larger than the\nGiant Room. It appears to be a repository for the \"Adventure\"\nprogram. Massive torches far overhead bathe the room with smoky\nyellow light. Scattered about you can be seen a pile of bottles (all\nof them empty), a nursery of young beanstalks murmuring quietly, a bed\nof oysters, a bundle of black rods with rusty stars on their ends, and\na collection of brass lanterns. Off to one side a great many dwarves\nare sleeping on the floor, snoring loudly. A notice nearby reads: \"Do\nnot disturb the dwarves!\" An immense mirror is hanging against one\nwall, and stretches to the other end of the room, where various other\nsundry objects can be glimpsed dimly in the distance.", }, .sound = MURMURING_SNORING, .loud = false, }, { // 116: LOC_SW .description = { .small = "You\'re at sw end.", .big = "You are at the southwest end of the repository. To one side is a pit\nfull of fierce green snakes. On the other side is a row of small\nwicker cages, each of which contains a little sulking bird. In one\ncorner is a bundle of black rods with rusty marks on their ends. A\nlarge number of velvet pillows are scattered about on the floor. A\nvast mirror stretches off to the northeast. At your feet is a large\nsteel grate, next to which is a sign that reads, \"Treasure Vault.\nKeys in main office.\"", }, .sound = SNAKES_HISSING, .loud = false, }, { // 117: LOC_SWCHASM .description = { .small = "You\'re on sw side of chasm.", .big = "You are on one side of a large, deep chasm. A heavy white mist rising\nup from below obscures all view of the far side. A sw path leads away\nfrom the chasm into a winding corridor.", }, .sound = SILENT, .loud = false, }, { // 118: LOC_WINDING .description = { .small = "You\'re in sloping corridor.", .big = "You are in a long winding corridor sloping out of sight in both\ndirections.", }, .sound = SILENT, .loud = false, }, { // 119: LOC_SECRET4 .description = { .small = NULL, .big = "You are in a secret canyon which exits to the north and east.", }, .sound = SILENT, .loud = false, }, { // 120: LOC_SECRET5 .description = { .small = NULL, .big = "You are in a secret canyon which exits to the north and east.", }, .sound = SILENT, .loud = false, }, { // 121: LOC_SECRET6 .description = { .small = NULL, .big = "You are in a secret canyon which exits to the north and east.", }, .sound = SILENT, .loud = false, }, { // 122: LOC_NECHASM .description = { .small = "You\'re on ne side of chasm.", .big = "You are on the far side of the chasm. A ne path leads away from the\nchasm on this side.", }, .sound = SILENT, .loud = false, }, { // 123: LOC_CORRIDOR .description = { .small = "You\'re in corridor.", .big = "You\'re in a long east/west corridor. A faint rumbling noise can be\nheard in the distance.", }, .sound = DULL_RUMBLING, .loud = false, }, { // 124: LOC_FORK .description = { .small = "You\'re at fork in path.", .big = "The path forks here. The left fork leads northeast. A dull rumbling\nseems to get louder in that direction. The right fork leads southeast\ndown a gentle slope. The main corridor enters from the west.", }, .sound = DULL_RUMBLING, .loud = false, }, { // 125: LOC_WARMWALLS .description = { .small = "You\'re at junction with warm walls.", .big = "The walls are quite warm here. From the north can be heard a steady\nroar, so loud that the entire cave seems to be trembling. Another\npassage leads south, and a low crawl goes east.", }, .sound = LOUD_ROAR, .loud = false, }, { // 126: LOC_BREATHTAKING .description = { .small = "You\'re at breath-taking view.", .big = "You are on the edge of a breath-taking view. Far below you is an\nactive volcano, from which great gouts of molten lava come surging\nout, cascading back down into the depths. The glowing rock fills the\nfarthest reaches of the cavern with a blood-red glare, giving every-\nthing an eerie, macabre appearance. The air is filled with flickering\nsparks of ash and a heavy smell of brimstone. The walls are hot to\nthe touch, and the thundering of the volcano drowns out all other\nsounds. Embedded in the jagged roof far overhead are myriad twisted\nformations composed of pure white alabaster, which scatter the murky\nlight into sinister apparitions upon the walls. To one side is a deep\ngorge, filled with a bizarre chaos of tortured rock which seems to\nhave been crafted by the devil himself. An immense river of fire\ncrashes out from the depths of the volcano, burns its way through the\ngorge, and plummets into a bottomless pit far off to your left. To\nthe right, an immense geyser of blistering steam erupts continuously\nfrom a barren island in the center of a sulfurous lake, which bubbles\nominously. The far right wall is aflame with an incandescence of its\nown, which lends an additional infernal splendor to the already\nhellish scene. A dark, foreboding passage exits to the south.", }, .sound = TOTAL_ROAR, .loud = true, }, { // 127: LOC_BOULDERS2 .description = { .small = "You\'re in Chamber of Boulders.", .big = "You are in a small chamber filled with large boulders. The walls are\nvery warm, causing the air in the room to be almost stifling from the\nheat. The only exit is a crawl heading west, through which is coming\na low rumbling.", }, .sound = DULL_RUMBLING, .loud = false, }, { // 128: LOC_LIMESTONE .description = { .small = "You\'re in limestone passage.", .big = "You are walking along a gently sloping north/south passage lined with oddly shaped limestone formations.", }, .sound = SILENT, .loud = false, }, { // 129: LOC_BARRENFRONT .description = { .small = "You\'re in front of Barren Room.", .big = "You are standing at the entrance to a large, barren room. A notice\nabove the entrance reads: \"Caution! Bear in room!\"", }, .sound = SILENT, .loud = false, }, { // 130: LOC_BARRENROOM .description = { .small = "You\'re in Barren Room.", .big = "You are inside a barren room. The center of the room is completely\nempty except for some dust. Marks in the dust lead away toward the\nfar end of the room. The only exit is the way you came in.", }, .sound = SILENT, .loud = false, }, { // 131: LOC_DIFFERENT3 .description = { .small = NULL, .big = "You are in a maze of twisting little passages, all different.", }, .sound = SILENT, .loud = false, }, { // 132: LOC_DIFFERENT4 .description = { .small = NULL, .big = "You are in a little maze of twisty passages, all different.", }, .sound = SILENT, .loud = false, }, { // 133: LOC_DIFFERENT5 .description = { .small = NULL, .big = "You are in a twisting maze of little passages, all different.", }, .sound = SILENT, .loud = false, }, { // 134: LOC_DIFFERENT6 .description = { .small = NULL, .big = "You are in a twisting little maze of passages, all different.", }, .sound = SILENT, .loud = false, }, { // 135: LOC_DIFFERENT7 .description = { .small = NULL, .big = "You are in a twisty little maze of passages, all different.", }, .sound = SILENT, .loud = false, }, { // 136: LOC_DIFFERENT8 .description = { .small = NULL, .big = "You are in a twisty maze of little passages, all different.", }, .sound = SILENT, .loud = false, }, { // 137: LOC_DIFFERENT9 .description = { .small = NULL, .big = "You are in a little twisty maze of passages, all different.", }, .sound = SILENT, .loud = false, }, { // 138: LOC_DIFFERENT10 .description = { .small = NULL, .big = "You are in a maze of little twisting passages, all different.", }, .sound = SILENT, .loud = false, }, { // 139: LOC_DIFFERENT11 .description = { .small = NULL, .big = "You are in a maze of little twisty passages, all different.", }, .sound = SILENT, .loud = false, }, { // 140: LOC_DEADEND13 .description = { .small = NULL, .big = "Dead end", }, .sound = SILENT, .loud = false, }, { // 141: LOC_ROUGHHEWN .description = { .small = NULL, .big = "You are in a long, rough-hewn, north/south corridor.", }, .sound = SILENT, .loud = false, }, { // 142: LOC_BADDIRECTION .description = { .small = NULL, .big = "There is no way to go that direction.", }, .sound = SILENT, .loud = false, }, { // 143: LOC_LARGE .description = { .small = NULL, .big = "You are in a large chamber with passages to the west and north.", }, .sound = SILENT, .loud = false, }, { // 144: LOC_STOREROOM .description = { .small = NULL, .big = "You are in the ogre\'s storeroom. The only exit is to the south.", }, .sound = SILENT, .loud = false, }, { // 145: LOC_FOREST1 .description = { .small = NULL, .big = "You are wandering aimlessly through the forest.", }, .sound = SILENT, .loud = false, }, { // 146: LOC_FOREST2 .description = { .small = NULL, .big = "You are wandering aimlessly through the forest.", }, .sound = SILENT, .loud = false, }, { // 147: LOC_FOREST3 .description = { .small = NULL, .big = "You are wandering aimlessly through the forest.", }, .sound = SILENT, .loud = false, }, { // 148: LOC_FOREST4 .description = { .small = NULL, .big = "You are wandering aimlessly through the forest.", }, .sound = SILENT, .loud = false, }, { // 149: LOC_FOREST5 .description = { .small = NULL, .big = "You are wandering aimlessly through the forest.", }, .sound = SILENT, .loud = false, }, { // 150: LOC_FOREST6 .description = { .small = NULL, .big = "You are wandering aimlessly through the forest.", }, .sound = SILENT, .loud = false, }, { // 151: LOC_FOREST7 .description = { .small = NULL, .big = "You are wandering aimlessly through the forest.", }, .sound = SILENT, .loud = false, }, { // 152: LOC_FOREST8 .description = { .small = NULL, .big = "You are wandering aimlessly through the forest.", }, .sound = SILENT, .loud = false, }, { // 153: LOC_FOREST9 .description = { .small = NULL, .big = "You are wandering aimlessly through the forest.", }, .sound = SILENT, .loud = false, }, { // 154: LOC_FOREST10 .description = { .small = NULL, .big = "You are wandering aimlessly through the forest.", }, .sound = SILENT, .loud = false, }, { // 155: LOC_FOREST11 .description = { .small = NULL, .big = "You are wandering aimlessly through the forest.", }, .sound = SILENT, .loud = false, }, { // 156: LOC_FOREST12 .description = { .small = NULL, .big = "You are wandering aimlessly through the forest.", }, .sound = SILENT, .loud = false, }, { // 157: LOC_FOREST13 .description = { .small = NULL, .big = "You are wandering aimlessly through the forest.", }, .sound = SILENT, .loud = false, }, { // 158: LOC_FOREST14 .description = { .small = NULL, .big = "You are wandering aimlessly through the forest.", }, .sound = SILENT, .loud = false, }, { // 159: LOC_FOREST15 .description = { .small = NULL, .big = "You are wandering aimlessly through the forest.", }, .sound = SILENT, .loud = false, }, { // 160: LOC_FOREST16 .description = { .small = NULL, .big = "You are wandering aimlessly through the forest.", }, .sound = SILENT, .loud = false, }, { // 161: LOC_FOREST17 .description = { .small = NULL, .big = "You are wandering aimlessly through the forest.", }, .sound = SILENT, .loud = false, }, { // 162: LOC_FOREST18 .description = { .small = NULL, .big = "You are wandering aimlessly through the forest.", }, .sound = SILENT, .loud = false, }, { // 163: LOC_FOREST19 .description = { .small = NULL, .big = "You are wandering aimlessly through the forest.", }, .sound = SILENT, .loud = false, }, { // 164: LOC_FOREST20 .description = { .small = NULL, .big = "You are wandering aimlessly through the forest.", }, .sound = SILENT, .loud = false, }, { // 165: LOC_FOREST21 .description = { .small = NULL, .big = "You are wandering aimlessly through the forest.", }, .sound = SILENT, .loud = false, }, { // 166: LOC_FOREST22 .description = { .small = NULL, .big = "You are wandering aimlessly through the forest.", }, .sound = SILENT, .loud = false, }, { // 167: LOC_LEDGE .description = { .small = "You\'re on ledge.", .big = "You are on a small ledge on one face of a sheer cliff. There are no\npaths away from the ledge. Across the chasm is a small clearing\nsurrounded by forest.", }, .sound = SILENT, .loud = false, }, { // 168: LOC_RESBOTTOM .description = { .small = "You\'re at bottom of reservoir.", .big = "You are walking across the bottom of the reservoir. Walls of water\nrear up on either side. The roar of the water cascading past is\nnearly deafening, and the mist is so thick you can barely see.", }, .sound = TOTAL_ROAR, .loud = true, }, { // 169: LOC_RESNORTH .description = { .small = "You\'re north of reservoir.", .big = "You are at the northern edge of the reservoir. A northwest passage\nleads sharply up from here.", }, .sound = WATERS_CRASHING, .loud = false, }, { // 170: LOC_TREACHEROUS .description = { .small = NULL, .big = "You are scrambling along a treacherously steep, rocky passage.", }, .sound = SILENT, .loud = false, }, { // 171: LOC_STEEP .description = { .small = NULL, .big = "You are on a very steep incline, which widens at it goes upward.", }, .sound = SILENT, .loud = false, }, { // 172: LOC_CLIFFBASE .description = { .small = "You\'re at base of cliff.", .big = "You are at the base of a nearly vertical cliff. There are some\nslim footholds which would enable you to climb up, but it looks\nextremely dangerous. Here at the base of the cliff lie the remains\nof several earlier adventurers who apparently failed to make it.", }, .sound = SILENT, .loud = false, }, { // 173: LOC_CLIFFACE .description = { .small = NULL, .big = "You are climbing along a nearly vertical cliff.", }, .sound = SILENT, .loud = false, }, { // 174: LOC_FOOTSLIP .description = { .small = NULL, .big = "Just as you reach the top, your foot slips on a loose rock and you\ntumble several hundred feet to join the other unlucky adventurers.", }, .sound = SILENT, .loud = false, }, { // 175: LOC_CLIFFTOP .description = { .small = NULL, .big = "Just as you reach the top, your foot slips on a loose rock and you\nmake one last desperate grab. Your luck holds, as does your grip.\nWith an enormous heave, you lift yourself to the ledge above.", }, .sound = SILENT, .loud = false, }, { // 176: LOC_CLIFFLEDGE .description = { .small = "You\'re at top of cliff.", .big = "You are on a small ledge at the top of a nearly vertical cliff.\nThere is a low crawl leading off to the northeast.", }, .sound = SILENT, .loud = false, }, { // 177: LOC_REACHDEAD .description = { .small = NULL, .big = "You have reached a dead end.", }, .sound = SILENT, .loud = false, }, { // 178: LOC_GRUESOME .description = { .small = NULL, .big = "There is now one more gruesome aspect to the spectacular vista.", }, .sound = SILENT, .loud = false, }, { // 179: LOC_FOOF1 .description = { .small = NULL, .big = ">>Foof!<<", }, .sound = SILENT, .loud = false, }, { // 180: LOC_FOOF2 .description = { .small = NULL, .big = ">>Foof!<<", }, .sound = SILENT, .loud = false, }, { // 181: LOC_FOOF3 .description = { .small = NULL, .big = ">>Foof!<<", }, .sound = SILENT, .loud = false, }, { // 182: LOC_FOOF4 .description = { .small = NULL, .big = ">>Foof!<<", }, .sound = SILENT, .loud = false, }, { // 183: LOC_FOOF5 .description = { .small = NULL, .big = ">>Foof!<<", }, .sound = SILENT, .loud = false, }, { // 184: LOC_FOOF6 .description = { .small = NULL, .big = ">>Foof!<<", }, .sound = SILENT, .loud = false, }, }; const object_t objects[] = { { // 0: NO_OBJECT .words = { .strs = NULL, .n = 0, }, .inventory = NULL, .plac = LOC_NOWHERE, .fixd = LOC_NOWHERE, .is_treasure = false, .descriptions = (const char* []) { NULL, }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 1: KEYS .words = { .strs = (const char* []) {"keys", "key"}, .n = 2, }, .inventory = "Set of keys", .plac = LOC_BUILDING, .fixd = 0, .is_treasure = false, .descriptions = (const char* []) { "There are some keys on the ground here.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 2: LAMP .words = { .strs = (const char* []) {"lamp", "lante"}, .n = 2, }, .inventory = "Brass lantern", .plac = LOC_BUILDING, .fixd = 0, .is_treasure = false, .descriptions = (const char* []) { "There is a shiny brass lamp nearby.", "There is a lamp shining nearby.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { "Your lamp is now off.", "Your lamp is now on.", }, }, { // 3: GRATE .words = { .strs = (const char* []) {"grate"}, .n = 1, }, .inventory = "*grate", .plac = LOC_GRATE, .fixd = LOC_BELOWGRATE, .is_treasure = false, .descriptions = (const char* []) { "The grate is locked.", "The grate is open.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { "The grate is now locked.", "The grate is now unlocked.", }, }, { // 4: CAGE .words = { .strs = (const char* []) {"cage"}, .n = 1, }, .inventory = "Wicker cage", .plac = LOC_COBBLE, .fixd = 0, .is_treasure = false, .descriptions = (const char* []) { "There is a small wicker cage discarded nearby.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 5: ROD .words = { .strs = (const char* []) {"rod"}, .n = 1, }, .inventory = "Black rod", .plac = LOC_DEBRIS, .fixd = 0, .is_treasure = false, .descriptions = (const char* []) { "A three foot black rod with a rusty star on an end lies nearby.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 6: ROD2 .words = { .strs = (const char* []) {"rod"}, .n = 1, }, .inventory = "Black rod", .plac = LOC_NOWHERE, .fixd = 0, .is_treasure = false, .descriptions = (const char* []) { "A three foot black rod with a rusty mark on an end lies nearby.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 7: STEPS .words = { .strs = (const char* []) {"steps"}, .n = 1, }, .inventory = "*steps", .plac = LOC_PITTOP, .fixd = LOC_MISTHALL, .is_treasure = false, .descriptions = (const char* []) { "Rough stone steps lead down the pit.", "Rough stone steps lead up the dome.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 8: BIRD .words = { .strs = (const char* []) {"bird"}, .n = 1, }, .inventory = "Little bird in cage", .plac = LOC_BIRD, .fixd = 0, .is_treasure = false, .descriptions = (const char* []) { "A cheerful little bird is sitting here singing.", "There is a little bird in the cage.", "A cheerful little bird is sitting here singing.", }, .sounds = (const char* []) { "The bird\'s singing is quite melodious.", "The bird does not seem inclined to sing while in the cage.", "It almost seems as though the bird is trying to tell you something.", "To your surprise, you can understand the bird\'\'s chirping; it is\nsinging about the joys of its forest home.", "The bird does not seem inclined to sing while in the cage.", "The bird is singing to you in gratitude for your having returned it to\nits home. In return, it informs you of a magic word which it thinks\nyou may find useful somewhere near the Hall of Mists. The magic word\nchanges frequently, but for now the bird believes it is \"%s\". You\nthank the bird for this information, and it flies off into the forest.", }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 9: DOOR .words = { .strs = (const char* []) {"door"}, .n = 1, }, .inventory = "*rusty door", .plac = LOC_IMMENSE, .fixd = -1, .is_treasure = false, .descriptions = (const char* []) { "The way north is barred by a massive, rusty, iron door.", "The way north leads through a massive, rusty, iron door.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { "The hinges are quite thoroughly rusted now and won\'t budge.", "The oil has freed up the hinges so that the door will now move,\nalthough it requires some effort.", }, }, { // 10: PILLOW .words = { .strs = (const char* []) {"pillo", "velve"}, .n = 2, }, .inventory = "Velvet pillow", .plac = LOC_SOFTROOM, .fixd = 0, .is_treasure = false, .descriptions = (const char* []) { "A small velvet pillow lies on the floor.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 11: SNAKE .words = { .strs = (const char* []) {"snake"}, .n = 1, }, .inventory = "*snake", .plac = LOC_KINGHALL, .fixd = -1, .is_treasure = false, .descriptions = (const char* []) { "A huge green fierce snake bars the way!", "", }, .sounds = (const char* []) { "The snake is hissing venomously.", "", }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 12: FISSURE .words = { .strs = (const char* []) {"fissu"}, .n = 1, }, .inventory = "*fissure", .plac = LOC_EASTBANK, .fixd = LOC_WESTBANK, .is_treasure = false, .descriptions = (const char* []) { "", "A crystal bridge spans the fissure.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { "The crystal bridge has vanished!", "A crystal bridge now spans the fissure.", }, }, { // 13: OBJ_13 .words = { .strs = (const char* []) {"table"}, .n = 1, }, .inventory = "*stone tablet", .plac = LOC_DARKROOM, .fixd = -1, .is_treasure = false, .descriptions = (const char* []) { "A massive stone tablet imbedded in the wall reads:\n\"Congratulations on bringing light into the dark-room!\"", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { "\"Congratulations on bringing light into the dark-room!\"", }, .changes = (const char* []) { NULL, }, }, { // 14: CLAM .words = { .strs = (const char* []) {"clam"}, .n = 1, }, .inventory = "Giant clam >GRUNT!<", .plac = LOC_SHELLROOM, .fixd = 0, .is_treasure = false, .descriptions = (const char* []) { "There is an enormous clam here with its shell tightly closed.", }, .sounds = (const char* []) { "The clam is as tight-mouthed as a, er, clam.", }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 15: OYSTER .words = { .strs = (const char* []) {"oyste"}, .n = 1, }, .inventory = "Giant oyster >GROAN!<", .plac = LOC_NOWHERE, .fixd = 0, .is_treasure = false, .descriptions = (const char* []) { "There is an enormous oyster here with its shell tightly closed.", "Interesting. There seems to be something written on the underside of\nthe oyster.", }, .sounds = (const char* []) { "Even though it\'s an oyster, the critter\'s as tight-mouthed as a clam.", "It says the same thing it did before. Hm, maybe it\'s a pun?", }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 16: MAGAZINE .words = { .strs = (const char* []) {"magaz", "issue", "spelu", "\"spel"}, .n = 4, }, .inventory = "\"Spelunker Today\"", .plac = LOC_ANTEROOM, .fixd = 0, .is_treasure = false, .descriptions = (const char* []) { "There are a few recent issues of \"Spelunker Today\" magazine here.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { "I\'m afraid the magazine is written in dwarvish. But pencilled on one\ncover you see, \"Please leave the magazines at the construction site.\"", }, .changes = (const char* []) { NULL, }, }, { // 17: DWARF .words = { .strs = (const char* []) {"dwarf", "dwarv"}, .n = 2, }, .inventory = NULL, .plac = LOC_NOWHERE, .fixd = -1, .is_treasure = false, .descriptions = (const char* []) { NULL, }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 18: KNIFE .words = { .strs = (const char* []) {"knife", "knive"}, .n = 2, }, .inventory = NULL, .plac = LOC_NOWHERE, .fixd = 0, .is_treasure = false, .descriptions = (const char* []) { NULL, }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 19: FOOD .words = { .strs = (const char* []) {"food", "ratio"}, .n = 2, }, .inventory = "Tasty food", .plac = LOC_BUILDING, .fixd = 0, .is_treasure = false, .descriptions = (const char* []) { "There is food here.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 20: BOTTLE .words = { .strs = (const char* []) {"bottl", "jar"}, .n = 2, }, .inventory = "Small bottle", .plac = LOC_BUILDING, .fixd = 0, .is_treasure = false, .descriptions = (const char* []) { "There is a bottle of water here.", "There is an empty bottle here.", "There is a bottle of oil here.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { "Your bottle is now full of water.", "The bottle of water is now empty.", "Your bottle is now full of oil.", }, }, { // 21: WATER .words = { .strs = (const char* []) {"water", "h2o"}, .n = 2, }, .inventory = "Water in the bottle", .plac = LOC_NOWHERE, .fixd = 0, .is_treasure = false, .descriptions = (const char* []) { NULL, }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 22: OIL .words = { .strs = (const char* []) {"oil"}, .n = 1, }, .inventory = "Oil in the bottle", .plac = LOC_NOWHERE, .fixd = 0, .is_treasure = false, .descriptions = (const char* []) { NULL, }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 23: MIRROR .words = { .strs = (const char* []) {"mirro"}, .n = 1, }, .inventory = "*mirror", .plac = LOC_MIRRORCANYON, .fixd = -1, .is_treasure = false, .descriptions = (const char* []) { "", "", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { "", "You strike the mirror a resounding blow, whereupon it shatters into a\nmyriad tiny fragments.", }, }, { // 24: PLANT .words = { .strs = (const char* []) {"plant", "beans"}, .n = 2, }, .inventory = "*plant", .plac = LOC_WESTPIT, .fixd = -1, .is_treasure = false, .descriptions = (const char* []) { "There is a tiny little plant in the pit, murmuring \"water, water, ...\"", "There is a 12-foot-tall beanstalk stretching up out of the pit,\nbellowing \"WATER!! WATER!!\"", "There is a gigantic beanstalk stretching all the way up to the hole.", }, .sounds = (const char* []) { "The plant continues to ask plaintively for water.", "The plant continues to demand water.", "The plant now maintains a contented silence.", }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { "You\'ve over-watered the plant! It\'s shriveling up! And now . . .", "The plant spurts into furious growth for a few seconds.", "The plant grows explosively, almost filling the bottom of the pit.", }, }, { // 25: PLANT2 .words = { .strs = (const char* []) {"plant"}, .n = 1, }, .inventory = "*phony plant", .plac = LOC_WESTEND, .fixd = LOC_EASTEND, .is_treasure = false, .descriptions = (const char* []) { "", "The top of a 12-foot-tall beanstalk is poking out of the west pit.", "There is a huge beanstalk growing out of the west pit up to the hole.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 26: OBJ_26 .words = { .strs = (const char* []) {"stala"}, .n = 1, }, .inventory = "*stalactite", .plac = LOC_TOPSTALACTITE, .fixd = -1, .is_treasure = false, .descriptions = (const char* []) { "", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 27: OBJ_27 .words = { .strs = (const char* []) {"shado", "figur", "windo"}, .n = 3, }, .inventory = "*shadowy figure and/or window", .plac = LOC_WINDOW1, .fixd = LOC_WINDOW2, .is_treasure = false, .descriptions = (const char* []) { "The shadowy figure seems to be trying to attract your attention.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 28: AXE .words = { .strs = (const char* []) {"axe"}, .n = 1, }, .inventory = "Dwarf\'s axe", .plac = LOC_NOWHERE, .fixd = 0, .is_treasure = false, .descriptions = (const char* []) { "There is a little axe here.", "There is a little axe lying beside the bear.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { "", "The axe misses and lands near the bear where you can\'t get at it.", }, }, { // 29: OBJ_29 .words = { .strs = (const char* []) {"drawi"}, .n = 1, }, .inventory = "*cave drawings", .plac = LOC_ORIENTAL, .fixd = -1, .is_treasure = false, .descriptions = (const char* []) { NULL, }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 30: OBJ_30 .words = { .strs = (const char* []) {"pirat", "genie", "djinn"}, .n = 3, }, .inventory = "*pirate/genie", .plac = LOC_NOWHERE, .fixd = -1, .is_treasure = false, .descriptions = (const char* []) { NULL, }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 31: DRAGON .words = { .strs = (const char* []) {"drago"}, .n = 1, }, .inventory = "*dragon", .plac = LOC_SECRET4, .fixd = LOC_SECRET6, .is_treasure = false, .descriptions = (const char* []) { "A huge green fierce dragon bars the way!", "The blood-specked body of a huge green dead dragon lies to one side.", "The body of a huge green dead dragon is lying off to one side.", }, .sounds = (const char* []) { "The dragon\'s ominous hissing does not bode well for you.", "The dragon is, not surprisingly, silent.", "The dragon is, not surprisingly, silent.", }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { "", "Congratulations! You have just vanquished a dragon with your bare\nhands! (Unbelievable, isn\'t it?)", "Your head buzzes strangely for a moment.", }, }, { // 32: CHASM .words = { .strs = (const char* []) {"chasm"}, .n = 1, }, .inventory = "*chasm", .plac = LOC_SWCHASM, .fixd = LOC_NECHASM, .is_treasure = false, .descriptions = (const char* []) { "A rickety wooden bridge extends across the chasm, vanishing into the\nmist. A notice posted on the bridge reads, \"Stop! Pay troll!\"", "The wreckage of a bridge (and a dead bear) can be seen at the bottom\nof the chasm.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { "", "Just as you reach the other side, the bridge buckles beneath the\nweight of the bear, which was still following you around. You\nscrabble desperately for support, but as the bridge collapses you\nstumble back and fall into the chasm.", }, }, { // 33: TROLL .words = { .strs = (const char* []) {"troll"}, .n = 1, }, .inventory = "*troll", .plac = LOC_SWCHASM, .fixd = LOC_NECHASM, .is_treasure = false, .descriptions = (const char* []) { "A burly troll stands by the bridge and insists you throw him a\ntreasure before you may cross.", "The troll steps out from beneath the bridge and blocks your way.", "", }, .sounds = (const char* []) { "The troll sounds quite adamant in his demand for a treasure.", "The troll sounds quite adamant in his demand for a treasure.", "", }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { "", "", "The bear lumbers toward the troll, who lets out a startled shriek and\nscurries away. The bear soon gives up the pursuit and wanders back.", }, }, { // 34: TROLL2 .words = { .strs = (const char* []) {"troll"}, .n = 1, }, .inventory = "*phony troll", .plac = LOC_NOWHERE, .fixd = LOC_NOWHERE, .is_treasure = false, .descriptions = (const char* []) { "The troll is nowhere to be seen.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 35: BEAR .words = { .strs = (const char* []) {"bear"}, .n = 1, }, .inventory = NULL, .plac = LOC_BARRENROOM, .fixd = -1, .is_treasure = false, .descriptions = (const char* []) { "There is a ferocious cave bear eying you from the far end of the room!", "There is a gentle cave bear sitting placidly in one corner.", "There is a contented-looking bear wandering about nearby.", "", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { "", "The bear eagerly wolfs down your food, after which he seems to calm\ndown considerably and even becomes rather friendly.", "", "", }, }, { // 36: MESSAG .words = { .strs = (const char* []) {"messa"}, .n = 1, }, .inventory = "*message in second maze", .plac = LOC_NOWHERE, .fixd = -1, .is_treasure = false, .descriptions = (const char* []) { "There is a message scrawled in the dust in a flowery script, reading:\n\"This is not the maze where the pirate leaves his treasure chest.\"", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { "\"This is not the maze where the pirate leaves his treasure chest.\"", }, .changes = (const char* []) { NULL, }, }, { // 37: VOLCANO .words = { .strs = (const char* []) {"volca", "geyse"}, .n = 2, }, .inventory = "*volcano and/or geyser", .plac = LOC_BREATHTAKING, .fixd = -1, .is_treasure = false, .descriptions = (const char* []) { NULL, }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 38: VEND .words = { .strs = (const char* []) {"machi", "vendi"}, .n = 2, }, .inventory = "*vending machine", .plac = LOC_DEADEND13, .fixd = -1, .is_treasure = false, .descriptions = (const char* []) { "There is a massive and somewhat battered vending machine here. The\ninstructions on it read: \"Drop coins here to receive fresh batteries.\"", "There is a massive vending machine here, swung back to reveal a\nsouthward passage.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { "\"Drop coins here to receive fresh batteries.\"", "\"Drop coins here to receive fresh batteries.\"", }, .changes = (const char* []) { "The vending machine swings back to block the passage.", "As you strike the vending machine, it pivots backward along with a\nsection of wall, revealing a dark passage leading south.", }, }, { // 39: BATTERY .words = { .strs = (const char* []) {"batte"}, .n = 1, }, .inventory = "Batteries", .plac = LOC_NOWHERE, .fixd = 0, .is_treasure = false, .descriptions = (const char* []) { "There are fresh batteries here.", "Some worn-out batteries have been discarded nearby.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 40: OBJ_40 .words = { .strs = (const char* []) {"carpe", "moss"}, .n = 2, }, .inventory = "*carpet and/or moss and/or curtains", .plac = LOC_SOFTROOM, .fixd = -1, .is_treasure = false, .descriptions = (const char* []) { NULL, }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 41: OGRE .words = { .strs = (const char* []) {"ogre"}, .n = 1, }, .inventory = "*ogre", .plac = LOC_LARGE, .fixd = -1, .is_treasure = false, .descriptions = (const char* []) { "A formidable ogre bars the northern exit.", }, .sounds = (const char* []) { "The ogre is apparently the strong, silent type.", }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 42: URN .words = { .strs = (const char* []) {"urn"}, .n = 1, }, .inventory = "*urn", .plac = LOC_CLIFF, .fixd = -1, .is_treasure = false, .descriptions = (const char* []) { "A small urn is embedded in the rock.", "A small urn full of oil is embedded in the rock.", "A small oil flame extrudes from an urn embedded in the rock.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { "The urn is empty and will not light.", "The urn is now dark.", "The urn is now lit.", }, }, { // 43: CAVITY .words = { .strs = (const char* []) {"cavit"}, .n = 1, }, .inventory = "*cavity", .plac = LOC_NOWHERE, .fixd = -1, .is_treasure = false, .descriptions = (const char* []) { "", "There is a small urn-shaped cavity in the rock.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 44: BLOOD .words = { .strs = (const char* []) {"blood"}, .n = 1, }, .inventory = "*blood", .plac = LOC_NOWHERE, .fixd = -1, .is_treasure = false, .descriptions = (const char* []) { "", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 45: RESER .words = { .strs = (const char* []) {"reser"}, .n = 1, }, .inventory = "*reservoir", .plac = LOC_RESERVOIR, .fixd = LOC_RESNORTH, .is_treasure = false, .descriptions = (const char* []) { "", "The waters have parted to form a narrow path across the reservoir.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { "The waters crash together again.", "The waters have parted to form a narrow path across the reservoir.", }, }, { // 46: OBJ_46 .words = { .strs = (const char* []) {"appen", "lepor"}, .n = 2, }, .inventory = "Leporine appendage", .plac = LOC_FOREST22, .fixd = 0, .is_treasure = false, .descriptions = (const char* []) { "Your keen eye spots a severed leporine appendage lying on the ground.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 47: OBJ_47 .words = { .strs = (const char* []) {"mud"}, .n = 1, }, .inventory = "*mud", .plac = LOC_DEBRIS, .fixd = -1, .is_treasure = false, .descriptions = (const char* []) { "", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { "\"MAGIC WORD XYZZY\"", }, .changes = (const char* []) { NULL, }, }, { // 48: OBJ_48 .words = { .strs = (const char* []) {"note"}, .n = 1, }, .inventory = "*note", .plac = LOC_NUGGET, .fixd = -1, .is_treasure = false, .descriptions = (const char* []) { "", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { "\"You won\'t get it up the steps\"", }, .changes = (const char* []) { NULL, }, }, { // 49: SIGN .words = { .strs = (const char* []) {"sign"}, .n = 1, }, .inventory = "*sign", .plac = LOC_ANTEROOM, .fixd = -1, .is_treasure = false, .descriptions = (const char* []) { "", "", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { "Cave under construction beyond this point.\n Proceed at own risk.\n [Witt Construction Company]", "\"Treasure Vault. Keys in main office.\"", }, .changes = (const char* []) { NULL, }, }, { // 50: NUGGET .words = { .strs = (const char* []) {"gold", "nugge"}, .n = 2, }, .inventory = "Large gold nugget", .plac = LOC_NUGGET, .fixd = 0, .is_treasure = true, .descriptions = (const char* []) { "There is a large sparkling nugget of gold here!", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 51: OBJ_51 .words = { .strs = (const char* []) {"diamo"}, .n = 1, }, .inventory = "Several diamonds", .plac = LOC_WESTBANK, .fixd = 0, .is_treasure = true, .descriptions = (const char* []) { "There are diamonds here!", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 52: OBJ_52 .words = { .strs = (const char* []) {"silve", "bars"}, .n = 2, }, .inventory = "Bars of silver", .plac = LOC_FLOORHOLE, .fixd = 0, .is_treasure = true, .descriptions = (const char* []) { "There are bars of silver here!", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 53: OBJ_53 .words = { .strs = (const char* []) {"jewel"}, .n = 1, }, .inventory = "Precious jewelry", .plac = LOC_SOUTHSIDE, .fixd = 0, .is_treasure = true, .descriptions = (const char* []) { "There is precious jewelry here!", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 54: COINS .words = { .strs = (const char* []) {"coins"}, .n = 1, }, .inventory = "Rare coins", .plac = LOC_WESTSIDE, .fixd = 0, .is_treasure = true, .descriptions = (const char* []) { "There are many coins here!", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 55: CHEST .words = { .strs = (const char* []) {"chest", "box", "treas"}, .n = 3, }, .inventory = "Treasure chest", .plac = LOC_NOWHERE, .fixd = 0, .is_treasure = true, .descriptions = (const char* []) { "The pirate\'s treasure chest is here!", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 56: EGGS .words = { .strs = (const char* []) {"eggs", "egg", "nest"}, .n = 3, }, .inventory = "Golden eggs", .plac = LOC_GIANTROOM, .fixd = 0, .is_treasure = true, .descriptions = (const char* []) { "There is a large nest here, full of golden eggs!", "The nest of golden eggs has vanished!", "Done!", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 57: TRIDENT .words = { .strs = (const char* []) {"tride"}, .n = 1, }, .inventory = "Jeweled trident", .plac = LOC_WATERFALL, .fixd = 0, .is_treasure = true, .descriptions = (const char* []) { "There is a jewel-encrusted trident here!", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 58: VASE .words = { .strs = (const char* []) {"vase", "ming", "shard", "potte"}, .n = 4, }, .inventory = "Ming vase", .plac = LOC_ORIENTAL, .fixd = 0, .is_treasure = true, .descriptions = (const char* []) { "There is a delicate, precious, ming vase here!", "The floor is littered with worthless shards of pottery.", "The floor is littered with worthless shards of pottery.", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { "The vase is now resting, delicately, on a velvet pillow.", "The ming vase drops with a delicate crash.", "You have taken the vase and hurled it delicately to the ground.", }, }, { // 59: EMERALD .words = { .strs = (const char* []) {"emera"}, .n = 1, }, .inventory = "Egg-sized emerald", .plac = LOC_PLOVER, .fixd = 0, .is_treasure = true, .descriptions = (const char* []) { "There is an emerald here the size of a plover\'s egg!", "There is an emerald resting in a small cavity in the rock!", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 60: PYRAMID .words = { .strs = (const char* []) {"plati", "pyram"}, .n = 2, }, .inventory = "Platinum pyramid", .plac = LOC_DARKROOM, .fixd = 0, .is_treasure = true, .descriptions = (const char* []) { "There is a platinum pyramid here, 8 inches on a side!", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 61: PEARL .words = { .strs = (const char* []) {"pearl"}, .n = 1, }, .inventory = "Glistening pearl", .plac = LOC_NOWHERE, .fixd = 0, .is_treasure = true, .descriptions = (const char* []) { "Off to one side lies a glistening pearl!", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 62: RUG .words = { .strs = (const char* []) {"rug", "persi"}, .n = 2, }, .inventory = "Persian rug", .plac = LOC_SECRET4, .fixd = LOC_SECRET6, .is_treasure = true, .descriptions = (const char* []) { "There is a persian rug spread out on the floor!", "The dragon is sprawled out on a persian rug!!", "There is a persian rug here, hovering in mid-air!", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 63: OBJ_63 .words = { .strs = (const char* []) {"spice"}, .n = 1, }, .inventory = "Rare spices", .plac = LOC_BOULDERS2, .fixd = 0, .is_treasure = true, .descriptions = (const char* []) { "There are rare spices here!", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 64: CHAIN .words = { .strs = (const char* []) {"chain"}, .n = 1, }, .inventory = "Golden chain", .plac = LOC_BARRENROOM, .fixd = -1, .is_treasure = true, .descriptions = (const char* []) { "There is a golden chain lying in a heap on the floor!", "The bear is locked to the wall with a golden chain!", "There is a golden chain locked to the wall!", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 65: RUBY .words = { .strs = (const char* []) {"ruby"}, .n = 1, }, .inventory = "Giant ruby", .plac = LOC_STOREROOM, .fixd = 0, .is_treasure = true, .descriptions = (const char* []) { "There is an enormous ruby here!", "There is a ruby resting in a small cavity in the rock!", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 66: JADE .words = { .strs = (const char* []) {"jade", "neckl"}, .n = 2, }, .inventory = "Jade necklace", .plac = LOC_NOWHERE, .fixd = 0, .is_treasure = true, .descriptions = (const char* []) { "A precious jade necklace has been dropped here!", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 67: AMBER .words = { .strs = (const char* []) {"amber", "gemst"}, .n = 2, }, .inventory = "Amber gemstone", .plac = LOC_NOWHERE, .fixd = 0, .is_treasure = true, .descriptions = (const char* []) { "There is a rare amber gemstone here!", "There is an amber gemstone resting in a small cavity in the rock!", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 68: SAPPH .words = { .strs = (const char* []) {"sapph"}, .n = 1, }, .inventory = "Star sapphire", .plac = LOC_LEDGE, .fixd = 0, .is_treasure = true, .descriptions = (const char* []) { "A brilliant blue star sapphire is here!", "There is a star sapphire resting in a small cavity in the rock!", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, { // 69: OBJ_69 .words = { .strs = (const char* []) {"ebony", "statu"}, .n = 2, }, .inventory = "Ebony statuette", .plac = LOC_REACHDEAD, .fixd = 0, .is_treasure = true, .descriptions = (const char* []) { "There is a richly-carved ebony statuette here!", }, .sounds = (const char* []) { NULL, }, .texts = (const char* []) { NULL, }, .changes = (const char* []) { NULL, }, }, }; const obituary_t obituaries[] = { { .query = "Oh dear, you seem to have gotten yourself killed. I might be able to\nhelp you out, but I\'ve never really done this before. Do you want me\nto try to reincarnate you?", .yes_response = "All right. But don\'t blame me if something goes wr......\n --- POOF!! ---\nYou are engulfed in a cloud of orange smoke. Coughing and gasping,\nyou emerge from the smoke and find....", }, { .query = "You clumsy oaf, you\'ve done it again! I don\'t know how long I can\nkeep this up. Do you want me to try reincarnating you again?", .yes_response = "Okay, now where did I put my orange smoke?.... >POOF!<\nEverything disappears in a dense cloud of orange smoke.", }, { .query = "Now you\'ve really done it! I\'m out of orange smoke! You don\'t expect\nme to do a decent reincarnation without any orange smoke, do you?", .yes_response = "Okay, if you\'re so smart, do it yourself! I\'m leaving!", }, }; const hint_t hints[] = { { .number = 1, .penalty = 2, .turns = 4, .question = "Are you trying to get into the cave?", .hint = "The grate is very solid and has a hardened steel lock. You cannot\nenter without a key, and there are no keys nearby. I would recommend\nlooking elsewhere for the keys.", }, { .number = 2, .penalty = 2, .turns = 5, .question = "Are you trying to catch the bird?", .hint = "Something about you seems to be frightening the bird. Perhaps you\nmight figure out what it is.", }, { .number = 3, .penalty = 2, .turns = 8, .question = "Are you trying to somehow deal with the snake?", .hint = "You can\'t kill the snake, or drive it away, or avoid it, or anything\nlike that. There is a way to get by, but you don\'t have the necessary\nresources right now.", }, { .number = 4, .penalty = 4, .turns = 75, .question = "Do you need help getting out of the maze?", .hint = "You can make the passages look less alike by dropping things.", }, { .number = 5, .penalty = 5, .turns = 25, .question = "Are you trying to explore beyond the plover room?", .hint = "There is a way to explore that region without having to worry about\nfalling into a pit. None of the objects available is immediately\nuseful in discovering the secret.", }, { .number = 6, .penalty = 3, .turns = 20, .question = "Do you need help getting out of here?", .hint = "Don\'t go west.\n", }, { .number = 7, .penalty = 2, .turns = 8, .question = "Are you wondering what to do here?", .hint = "This section is quite advanced. Find the cave first.\n", }, { .number = 8, .penalty = 2, .turns = 25, .question = "Would you like to be shown out of the forest?", .hint = "Go east ten times. If that doesn\'t get you out, then go south, then\nwest twice, then south.", }, { .number = 9, .penalty = 4, .turns = 10, .question = "Do you need help dealing with the ogre?", .hint = "There is nothing the presence of which will prevent you from defeating\nhim; thus it can\'t hurt to fetch everything you possibly can.", }, { .number = 10, .penalty = 4, .turns = 1, .question = "You\'re missing only one other treasure. Do you need help finding it?", .hint = "Once you\'ve found all the other treasures, it is no longer possible to\nlocate the one you\'re now missing.", }, }; long conditions[] = { 0, // LOC_NOWHERE (1<