/* * File: command.cc * Summary: Misc commands. * Written by: Linley Henzell * * Modified for Crawl Reference by $Author$ on $Date$ * * Change History (most recent first): * * <4> 10/12/99 BCR BUILD_DATE is now used in version() * <3> 6/13/99 BWR New equipment listing commands * <2> 5/20/99 BWR Swapping inventory letters. * <1> -/--/-- LRH Created */ #include "AppHdr.h" #include "command.h" #include #include #include #include #include "externs.h" #include "abl-show.h" #include "branch.h" #include "chardump.h" #include "cio.h" #include "database.h" #include "describe.h" #include "files.h" #include "initfile.h" #include "invent.h" #include "itemname.h" #include "item_use.h" #include "items.h" #include "libutil.h" #include "menu.h" #include "message.h" #include "mon-pick.h" #include "mon-util.h" #include "ouch.h" #include "place.h" #include "player.h" #include "quiver.h" #include "religion.h" #include "skills2.h" #include "spl-cast.h" #include "spl-util.h" #include "state.h" #include "stuff.h" #include "terrain.h" #include "transfor.h" #include "version.h" #include "view.h" static void _adjust_item(void); static void _adjust_spells(void); static void _adjust_ability(void); static void _list_wizard_commands(); static const char *features[] = { "Stash-tracking", #ifdef CLUA_BINDINGS "Lua user scripts", #endif #ifdef USE_TILE "Tile support", #endif #ifdef WIZARD "Wizard mode", #endif #ifdef DEBUG "Debug mode", #endif #if defined(REGEX_POSIX) "POSIX regexps", #elif defined(REGEX_PCRE) "PCRE regexps", #else "Glob patterns", #endif #if defined(SOUND_PLAY_COMMAND) || defined(WINMM_PLAY_SOUNDS) "Sound support", #endif #ifdef DGL_MILESTONES "Milestones", #endif #ifdef UNICODE_GLYPHS "Unicode glyphs", #endif }; static std::string _get_version_information(void) { std::string result = "This is "; result += CRAWL " " VERSION " (" VERSION_DETAIL ")."; result += "\n\n"; return (result); } static std::string _get_version_features(void) { std::string result = "Features" EOL; result += "--------" EOL; for (unsigned int i = 0; i < ARRAYSZ(features); i++) { result += " * "; result += features[i]; result += EOL; } result += "\n"; return (result); } static void _add_file_to_scroller(FILE* fp, formatted_scroller& m, int first_hotkey = 0, bool auto_hotkeys = false); static std::string _get_version_changes(void) { // Attempts to print "Highlights" of the latest version. FILE* fp = fopen(datafile_path("changes.stone_soup", false).c_str(), "r"); if (!fp) return ""; std::string result = ""; std::string help; char buf[200]; bool start = false; bool skip_lines = true; while (fgets(buf, sizeof buf, fp)) { // Remove trailing spaces. for (int i = strlen(buf) - 1; i >= 0; i++) { if (isspace( buf[i] )) buf[i] = 0; else break; } help = buf; // Give up if you encountered the second set of underliners // and still haven't encountered the keyword "Highlights". if (help.find("---") != std::string::npos) { if (skip_lines) { skip_lines = false; continue; } else if (!start) break; } if (help.find("Highlights") != std::string::npos) { // Highlight the Highlights, so to speak. std::string text = ""; text += buf; text += ""; text += EOL; result += text; // And start printing from now on. start = true; } else if (!start) continue; else if (buf[0] == 0) { // Stop reading and copying text with the first empty line // following the Highlights section. break; } else { result += buf; result += EOL; } } fclose(fp); // Did we ever get to print the Highlights? if (start) { result += EOL; result += "For a more complete list of changes, see changes.stone_soup " "in the " EOL "/docs folder."; } else { result += "For a list of changes, see changes.stone_soup in the /docs " "folder."; } result += "\n\n"; return (result); } static void _print_version(void) { formatted_scroller cmd_version; // Set flags. int flags = MF_NOSELECT | MF_ALWAYS_SHOW_MORE | MF_NOWRAP | MF_EASY_EXIT; cmd_version.set_flags(flags, false); cmd_version.set_tag("version"); // FIXME: Allow for hiding Page down when at the end of the listing, ditto // for page up at start of listing. cmd_version.set_more( formatted_string::parse_string( "[ + : Page down. - : Page up." " Esc exits.]") ); cmd_version.add_text(_get_version_information()); cmd_version.add_text(_get_version_features()); cmd_version.add_text(_get_version_changes()); // Read in information about changes in comparison to the latest version. FILE* fp = fopen(datafile_path("034_changes.txt", false).c_str(), "r"); if (fp) { char buf[200]; bool first = true; while (fgets(buf, sizeof buf, fp)) { // Remove trailing spaces. for (int i = strlen(buf) - 1; i >= 0; i++) { if (isspace( buf[i] )) buf[i] = 0; else break; } if (first) { // Highlight the first line (title). std::string text = ""; text += buf; text += ""; cmd_version.add_text(text); first = false; } else cmd_version.add_text(buf); } fclose(fp); } cmd_version.show(); } void adjust(void) { mpr( "Adjust (i)tems, (s)pells, or (a)bilities?", MSGCH_PROMPT ); const int keyin = tolower( get_ch() ); if (keyin == 'i') _adjust_item(); else if (keyin == 's') _adjust_spells(); else if (keyin == 'a') _adjust_ability(); else if (keyin == ESCAPE) canned_msg( MSG_OK ); else canned_msg( MSG_HUH ); } // end adjust() void swap_inv_slots(int from_slot, int to_slot, bool verbose) { // swap items item_def tmp = you.inv[to_slot]; you.inv[to_slot] = you.inv[from_slot]; you.inv[from_slot] = tmp; // slot switching tmp.slot = you.inv[to_slot].slot; you.inv[to_slot].slot = you.inv[from_slot].slot; you.inv[from_slot].slot = tmp.slot; you.inv[from_slot].link = from_slot; you.inv[to_slot].link = to_slot; for (int i = 0; i < NUM_EQUIP; i++) { if (you.equip[i] == from_slot) you.equip[i] = to_slot; else if (you.equip[i] == to_slot) you.equip[i] = from_slot; } if (verbose) { mpr( you.inv[to_slot].name(DESC_INVENTORY_EQUIP).c_str() ); if (is_valid_item( you.inv[from_slot] )) mpr( you.inv[from_slot].name(DESC_INVENTORY_EQUIP).c_str() ); } if (to_slot == you.equip[EQ_WEAPON] || from_slot == you.equip[EQ_WEAPON]) { you.wield_change = true; you.m_quiver->on_weapon_changed(); } } static void _adjust_item(void) { int from_slot, to_slot; if (inv_count() < 1) { canned_msg(MSG_NOTHING_CARRIED); return; } from_slot = prompt_invent_item( "Adjust which item?", MT_INVLIST, -1 ); if (prompt_failed(from_slot)) return; mpr(you.inv[from_slot].name(DESC_INVENTORY_EQUIP).c_str()); to_slot = prompt_invent_item( "Adjust to which letter?", MT_INVLIST, -1, false, false ); if (to_slot == PROMPT_ABORT) { canned_msg( MSG_OK ); return; } swap_inv_slots(from_slot, to_slot, true); } static void _adjust_spells(void) { if (!you.spell_no) { mpr("You don't know any spells."); return; } // Select starting slot mpr("Adjust which spell?", MSGCH_PROMPT); int keyin = 0; if (Options.auto_list) keyin = list_spells(); else { keyin = get_ch(); if (keyin == '?' || keyin == '*') keyin = list_spells(); } if (!isalpha(keyin)) { canned_msg( MSG_OK ); return; } const int input_1 = keyin; const int index_1 = letter_to_index( input_1 ); spell_type spell = get_spell_by_letter( input_1 ); if (spell == SPELL_NO_SPELL) { mpr("You don't know that spell."); return; } // Print targeted spell. mprf( "%c - %s", keyin, spell_title( spell ) ); // Select target slot. keyin = 0; while ( !isalpha(keyin) ) { mpr( "Adjust to which letter?", MSGCH_PROMPT ); keyin = get_ch(); if (keyin == ESCAPE) { canned_msg( MSG_OK ); return; } if (keyin == '?' || keyin == '*') keyin = list_spells(); } const int input_2 = keyin; const int index_2 = letter_to_index( keyin ); // swap references in the letter table: const int tmp = you.spell_letter_table[index_2]; you.spell_letter_table[index_2] = you.spell_letter_table[index_1]; you.spell_letter_table[index_1] = tmp; // print out spell in new slot mprf("%c - %s", input_2, spell_title(get_spell_by_letter(input_2))); // print out other spell if one was involved (now at input_1) spell = get_spell_by_letter( input_1 ); if (spell != SPELL_NO_SPELL) mprf("%c - %s", input_1, spell_title(spell) ); } // end _adjust_spells() static void _adjust_ability(void) { const std::vector talents = your_talents(false); if ( talents.empty() ) { mpr("You don't currently have any abilities."); return; } int selected = -1; while ( selected < 0 ) { msg::streams(MSGCH_PROMPT) << "Adjust which ability? (? or * to list)" << std::endl; const int keyin = get_ch(); if ( keyin == '?' || keyin == '*' ) { selected = choose_ability_menu(talents); } else if (keyin == ESCAPE || keyin == ' ' || keyin == '\r' || keyin == '\n') { canned_msg( MSG_OK ); return; } else if (isalpha(keyin)) { // Try to find the hotkey. for (unsigned int i = 0; i < talents.size(); ++i) { if ( talents[i].hotkey == keyin ) { selected = static_cast(i); break; } } // If we can't, cancel out. if ( selected < 0 ) { mpr("No such ability."); return; } } } msg::stream << static_cast(talents[selected].hotkey) << " - " << ability_name(talents[selected].which) << std::endl; const int index1 = letter_to_index(talents[selected].hotkey); msg::streams(MSGCH_PROMPT) << "Adjust to which letter?" << std::endl; const int keyin = get_ch(); if ( !isalpha(keyin) ) { canned_msg(MSG_HUH); return; } const int index2 = letter_to_index(keyin); if ( index1 == index2 ) { mpr("That would be singularly pointless."); return; } // See if we moved something out. bool printed_message = false; for ( unsigned int i = 0; i < talents.size(); ++i ) { if ( talents[i].hotkey == keyin ) { msg::stream << "Swapping with: " << static_cast(keyin) << " - " << ability_name(talents[i].which) << std::endl; printed_message = true; break; } } if (!printed_message) msg::stream << "Moving to: " << static_cast(keyin) << " - " << ability_name(talents[selected].which) << std::endl; // Swap references in the letter table. ability_type tmp = you.ability_letter_table[index2]; you.ability_letter_table[index2] = you.ability_letter_table[index1]; you.ability_letter_table[index1] = tmp; } // end _adjust_ability() void list_armour() { std::ostringstream estr; for (int i = EQ_CLOAK; i <= EQ_BODY_ARMOUR; i++) { const int armour_id = you.equip[i]; int colour = MSGCOL_BLACK; estr.str(""); estr.clear(); estr << ((i == EQ_CLOAK) ? "Cloak " : (i == EQ_HELMET) ? "Helmet " : (i == EQ_GLOVES) ? "Gloves " : (i == EQ_SHIELD) ? "Shield " : (i == EQ_BODY_ARMOUR) ? "Armour " : (i == EQ_BOOTS) ? ((you.species == SP_CENTAUR || you.species == SP_NAGA) ? "Barding" : "Boots ") : "unknown") << " : "; if (armour_id != -1) { estr << you.inv[armour_id].name(DESC_INVENTORY); colour = menu_colour(estr.str(), menu_colour_item_prefix(you.inv[armour_id]), "equip"); } else if (!you_can_wear(i,true)) estr << " (unavailable)"; else if (!you_tran_can_wear(i, true)) estr << " (currently unavailable)"; else if (!you_can_wear(i)) estr << " (restricted)"; else estr << " none"; if (colour == MSGCOL_BLACK) colour = menu_colour(estr.str(), "", "equip"); mpr( estr.str().c_str(), MSGCH_EQUIPMENT, colour); } } void list_jewellery(void) { std::ostringstream jstr; for (int i = EQ_LEFT_RING; i <= EQ_AMULET; i++) { const int jewellery_id = you.equip[i]; int colour = MSGCOL_BLACK; jstr.str(""); jstr.clear(); jstr << ((i == EQ_LEFT_RING) ? "Left ring " : (i == EQ_RIGHT_RING) ? "Right ring" : (i == EQ_AMULET) ? "Amulet " : "unknown ") << " : "; if (jewellery_id != -1) { jstr << you.inv[jewellery_id].name(DESC_INVENTORY); std::string prefix = menu_colour_item_prefix(you.inv[jewellery_id]); colour = menu_colour(jstr.str(), prefix, "equip"); } else if (!you_tran_can_wear(i)) jstr << " (currently unavailable)"; else jstr << " none"; if (colour == MSGCOL_BLACK) colour = menu_colour(jstr.str(), "", "equip"); mpr( jstr.str().c_str(), MSGCH_EQUIPMENT, colour); } } void list_weapons(void) { const int weapon_id = you.equip[EQ_WEAPON]; // Output the current weapon // // Yes, this is already on the screen... I'm outputing it // for completeness and to avoid confusion. std::string wstring = "Current : "; int colour; if (weapon_id != -1) { wstring += you.inv[weapon_id].name(DESC_INVENTORY_EQUIP); colour = menu_colour(wstring, menu_colour_item_prefix(you.inv[weapon_id]), "equip"); } else { if (you.attribute[ATTR_TRANSFORMATION] == TRAN_BLADE_HANDS) wstring += " blade hands"; else if (!you_tran_can_wear(EQ_WEAPON)) wstring += " (currently unavailable)"; else wstring += " empty hands"; colour = menu_colour(wstring, "", "equip"); } mpr(wstring.c_str(), MSGCH_EQUIPMENT, colour); // Print out the swap slots. for (int i = 0; i <= 1; i++) { // We'll avoid repeating the current weapon for these slots, // in order to keep things clean. if (weapon_id == i) continue; if ( i == 0 ) wstring = "Primary : "; else wstring = "Secondary : "; colour = MSGCOL_BLACK; if (is_valid_item( you.inv[i]) && (you.inv[i].base_type == OBJ_WEAPONS || you.inv[i].base_type == OBJ_STAVES || you.inv[i].base_type == OBJ_MISCELLANY)) { wstring += you.inv[i].name(DESC_INVENTORY_EQUIP); colour = menu_colour(wstring, menu_colour_item_prefix(you.inv[i]), "equip"); } else wstring += " none"; if (colour == MSGCOL_BLACK) colour = menu_colour(wstring, "", "equip"); mpr(wstring.c_str(), MSGCH_EQUIPMENT, colour); } // Now we print out the current default fire weapon. wstring = "Firing : "; std::string error_reason; int slot = you.m_quiver->get_fire_item(&error_reason); colour = MSGCOL_BLACK; if (slot == -1) { const item_def* item; you.m_quiver->get_desired_item(&item, &slot); if (!is_valid_item(*item)) { wstring += " nothing"; } else { wstring += " - "; wstring += item->name(DESC_NOCAP_A); wstring += " (empty)"; } } else { wstring += you.inv[slot].name(DESC_INVENTORY_EQUIP); colour = menu_colour(wstring, menu_colour_item_prefix(you.inv[slot]), "equip"); } if (colour == MSGCOL_BLACK) colour = menu_colour(wstring, "", "equip"); mpr( wstring.c_str(), MSGCH_EQUIPMENT, colour ); } // end list_weapons() static bool _cmdhelp_textfilter(const std::string &tag) { #ifdef WIZARD if (tag == "wiz") return (true); #endif return (false); } static const char *targeting_help_1 = "Examine surroundings ('x' in main):\n" "Esc : cancel (also Space)\n" "Dir.: move cursor in that direction\n" ". : move to cursor (also Enter, Del)\n" "v : describe monster under cursor\n" "+ : cycle monsters forward (also =)\n" "- : cycle monsters backward\n" "* : cycle objects forward\n" "/ : cycle objects backward (also ;)\n" "^ : cycle through traps\n" "_ : cycle through altars\n" "<</> : cycle through up/down stairs\n" "Tab : cycle through shops and portals\n" "Ctrl-F : change monster targeting mode\n" #ifndef USE_TILE "Ctrl-L : toggle targetting via monster list\n" #endif "Ctrl-P : repeat prompt\n" " \n" "Targeting (zapping wands, casting spells, etc.):\n" "The keys from examine surroundings also work here.\n" "In addition, you can use:\n" "! : fire at target (Enter, Del, Space)\n" ". : fire at target and stop there (may hit submerged creatures)\n" "p : fire at Previous target (also f)\n" ": : show/hide beam path\n" "Shift-Dir : shoot straight-line beam\n" #ifdef WIZARD " \n" "Wizard targeting comands:\n" "g: give item to monster\n" "s: force monster to shout or speak\n" "F: cycle monster friendly/good neutral/neutral/hostile\n" "P: apply divine blessing to monster\n" "m: move monster or player\n" "w: calculate shortest path to any point on the map\n" #endif ; static const char *targeting_help_2 = "Firing or throwing a missile:\n" "( : cycle to next suitable missile.\n" ") : cycle to previous suitable missile.\n" "i : choose from inventory.\n" ; // Add the contents of the file fp to the scroller menu m. // If first_hotkey is nonzero, that will be the hotkey for the // start of the contents of the file. // If auto_hotkeys is true, the function will try to identify // sections and add appropriate hotkeys. static void _add_file_to_scroller(FILE* fp, formatted_scroller& m, int first_hotkey, bool auto_hotkeys) { bool next_is_hotkey = false; bool is_first = true; char buf[200]; // Bracket with MEL_TITLES, so that you won't scroll into it or above it. m.add_entry(new MenuEntry(std::string(), MEL_TITLE)); for (int i = 0; i < get_number_of_lines(); ++i) m.add_entry(new MenuEntry(std::string())); m.add_entry(new MenuEntry(std::string(), MEL_TITLE)); while (fgets(buf, sizeof buf, fp)) { MenuEntry* me = new MenuEntry(buf); if (next_is_hotkey && (isupper(buf[0]) || isdigit(buf[0])) || is_first && first_hotkey) { int hotkey = (is_first ? first_hotkey : buf[0]); if (!is_first && buf[0] == 'X' && strlen(buf) >= 3 && isdigit(buf[2])) { // X.# is hotkeyed to the # hotkey = buf[2]; } me->add_hotkey(hotkey); if (isupper(hotkey)) me->add_hotkey(tolower(hotkey)); me->level = MEL_SUBTITLE; me->colour = WHITE; } m.add_entry(me); // FIXME: There must be a better way to identify sections! next_is_hotkey = auto_hotkeys && (strstr(buf, "------------------------------------------" "------------------------------") == buf); is_first = false; } } struct help_file { const char* name; int hotkey; bool auto_hotkey; }; help_file help_files[] = { { "crawl_manual.txt", '*', true }, { "../README.txt", '!', false }, { "aptitudes.txt", '%', false }, { "quickstart.txt", '^', false }, { "macros_guide.txt", '~', false }, { "options_guide.txt", '&', false }, #ifdef USE_TILE { "tiles_help.txt", 'T', false }, #endif { NULL, 0, false } }; static bool _compare_mon_names(MenuEntry *entry_a, MenuEntry* entry_b) { monster_type *a = static_cast( entry_a->data ); monster_type *b = static_cast( entry_b->data ); if (*a == *b) return (false); std::string a_name = mons_type_name(*a, DESC_PLAIN); std::string b_name = mons_type_name(*b, DESC_PLAIN); return (lowercase(a_name) < lowercase(b_name)); } // Compare monsters by location-independant level, or by hitdice if // levels are equal, or by name if both level and hitdice are equal. static bool _compare_mon_toughness(MenuEntry *entry_a, MenuEntry* entry_b) { monster_type *a = static_cast( entry_a->data ); monster_type *b = static_cast( entry_b->data ); if (*a == *b) return (false); int a_toughness = mons_global_level(*a); int b_toughness = mons_global_level(*b); if (a_toughness == b_toughness) { a_toughness = mons_type_hit_dice(*a); b_toughness = mons_type_hit_dice(*b); } if (a_toughness == b_toughness) { std::string a_name = mons_type_name(*a, DESC_PLAIN); std::string b_name = mons_type_name(*b, DESC_PLAIN); return (lowercase(a_name) < lowercase(b_name)); } return (a_toughness < b_toughness); } class DescMenu : public Menu { public: DescMenu( int _flags, bool _show_mon ) : Menu(_flags), sort_alpha(true), showing_monsters(_show_mon) { set_highlighter(NULL); if (_show_mon) toggle_sorting(); set_prompt(); } bool sort_alpha; bool showing_monsters; void set_prompt() { std::string prompt = "Describe which? "; if (showing_monsters) { if (sort_alpha) prompt += "(CTRL-S to sort by monster toughness)"; else prompt += "(CTRL-S to sort by name)"; } set_title(new MenuEntry(prompt, MEL_TITLE)); } void sort() { if (!showing_monsters) return; if (sort_alpha) std::sort(items.begin(), items.end(), _compare_mon_names); else std::sort(items.begin(), items.end(), _compare_mon_toughness); for (unsigned int i = 0, size = items.size(); i < size; i++) { const char letter = index_to_letter(i); items[i]->hotkeys.clear(); items[i]->add_hotkey(letter); } } void toggle_sorting() { if (!showing_monsters) return; sort_alpha = !sort_alpha; sort(); set_prompt(); } }; static std::vector _get_desc_keys(std::string regex, db_find_filter filter) { std::vector key_matches = getLongDescKeysByRegex(regex, filter); if (key_matches.size() == 1) return (key_matches); else if (key_matches.size() > 52) return (key_matches); std::vector body_matches = getLongDescBodiesByRegex(regex, filter); if (key_matches.size() == 0 && body_matches.size() == 0) return (key_matches); else if (key_matches.size() == 0 && body_matches.size() == 1) return (body_matches); // Merge key_matches and body_matches, discarding duplicates. std::vector tmp = key_matches; tmp.insert(tmp.end(), body_matches.begin(), body_matches.end()); std::sort(tmp.begin(), tmp.end()); std::vector all_matches; for (unsigned int i = 0, size = tmp.size(); i < size; i++) if (i == 0 || all_matches[all_matches.size() - 1] != tmp[i]) all_matches.push_back(tmp[i]); return (all_matches); } static std::vector _get_monster_keys(unsigned char showchar) { std::vector mon_keys; for (int i = 0; i < NUM_MONSTERS; i++) { if (i == MONS_PROGRAM_BUG || mons_global_level(i) == 0) continue; monsterentry *me = get_monster_data(i); if (me == NULL || me->name == NULL || me->name[0] == '\0') continue; if (me->mc != i) continue; if (getLongDescription(me->name).empty()) continue; if (me->showchar == showchar) mon_keys.push_back(me->name); } return (mon_keys); } static std::vector _get_god_keys() { std::vector names; for (int i = ((int) GOD_NO_GOD) + 1; i < NUM_GODS; i++) { god_type which_god = static_cast(i); names.push_back(god_name(which_god)); } return names; } static std::vector _get_branch_keys() { std::vector names; for (int i = BRANCH_MAIN_DUNGEON; i < NUM_BRANCHES; i++) { branch_type which_branch = static_cast(i); Branch &branch = branches[which_branch]; // Skip unimplemented branches if (branch.depth < 1 || branch.shortname == NULL) continue; names.push_back(branch.shortname); } /* // Maybe include other level areas, as well. for (int i = LEVEL_LABYRINTH; i < NUM_LEVEL_AREA_TYPES; i++) { names.push_back(place_name( get_packed_place(BRANCH_MAIN_DUNGEON, 1, static_cast(i)), true)); } */ return (names); } static bool _monster_filter(std::string key, std::string body) { int mon_num = get_monster_by_name(key.c_str(), true); return (mon_num == MONS_PROGRAM_BUG || mons_global_level(mon_num) == 0); } static bool _spell_filter(std::string key, std::string body) { return (spell_by_name(key) == SPELL_NO_SPELL); } static bool _item_filter(std::string key, std::string body) { return (item_types_by_name(key).base_type == OBJ_UNASSIGNED); } static bool _skill_filter(std::string key, std::string body) { key = lowercase_string(key); std::string name; for (int i = 0; i < NUM_SKILLS; i++) { // There are a couple of NULL entries in the skill set. if (!skill_name(i)) continue; name = lowercase_string(skill_name(i)); if (name.find(key) != std::string::npos) return (false); } return (true); } static bool _feature_filter(std::string key, std::string body) { return (feat_by_desc(key) == DNGN_UNSEEN); } typedef void (*db_keys_recap)(std::vector&); static void _recap_mon_keys(std::vector &keys) { for (unsigned int i = 0, size = keys.size(); i < size; i++) { monster_type type = get_monster_by_name(keys[i], true); keys[i] = mons_type_name(type, DESC_PLAIN); } } static void _recap_feat_keys(std::vector &keys) { for (unsigned int i = 0, size = keys.size(); i < size; i++) { dungeon_feature_type type = feat_by_desc(keys[i]); if (type == DNGN_ENTER_SHOP) keys[i] = "A shop"; else { keys[i] = feature_description(type, NUM_TRAPS, false, DESC_CAP_A, false); } } } static bool _do_description(std::string key, std::string footer = "") { std::string desc = getLongDescription(key); std::string prefix, suffix; int width = get_number_of_cols(); if (width > 80) width = 80; god_type which_god = string_to_god(key.c_str()); if (which_god != GOD_NO_GOD) { if (is_good_god(which_god)) { suffix = "$$" + god_name(which_god) + " won't accept worship from " "undead or evil beings."; } std::string help = get_god_powers(which_god); if (!help.empty()) { desc += EOL; desc += help; } desc += EOL; desc += get_god_likes(which_god); help = get_god_dislikes(which_god); if (!help.empty()) { desc += EOL EOL; desc += help; } } else { monster_type mon_num = get_monster_by_name(key, true); if (mon_num != MONS_PROGRAM_BUG) { if (mons_genus(mon_num) == MONS_DRACONIAN) { monsters mon; mon.type = mon_num; switch (mon_num) { case MONS_BLACK_DRACONIAN: case MONS_MOTTLED_DRACONIAN: case MONS_YELLOW_DRACONIAN: case MONS_GREEN_DRACONIAN: case MONS_PURPLE_DRACONIAN: case MONS_RED_DRACONIAN: case MONS_WHITE_DRACONIAN: case MONS_PALE_DRACONIAN: mon.base_monster = mon_num; break; default: mon.base_monster = MONS_PROGRAM_BUG; break; } describe_monsters(mon); return (false); } std::string symbol = ""; symbol += get_monster_data(mon_num)->showchar; if (isupper(symbol[0])) symbol = "cap-" + symbol; std::string symbol_prefix = "__"; symbol_prefix += symbol; symbol_prefix += "_prefix"; prefix = getLongDescription(symbol_prefix); std::string symbol_suffix = "__"; symbol_suffix += symbol; symbol_suffix += "_suffix"; suffix += getLongDescription(symbol_suffix); suffix += getLongDescription(symbol_suffix + "_lookup"); } } key = uppercase_first(key); linebreak_string2(footer, width - 1); print_description(desc, key, suffix, prefix, footer); return (true); } static bool _find_description(bool &again, std::string& error_inout) { again = true; clrscr(); viewwindow(true, false); if (! error_inout.empty()) mpr(error_inout.c_str(), MSGCH_PROMPT); mpr("Describe a (M)onster, (S)pell, s(K)ill, (I)tem, (F)eature, (G)od " "or (B)ranch?", MSGCH_PROMPT); int ch = toupper(getch()); std::string type; std::string extra; db_find_filter filter = NULL; db_keys_recap recap = NULL; bool want_regex = true; bool want_sort = true; bool doing_mons = false; bool doing_items = false; bool doing_gods = false; bool doing_branches = false; switch(ch) { case 'M': type = "monster"; extra = " Enter a single letter to list monsters displayed by " "that symbol."; filter = _monster_filter; recap = _recap_mon_keys; doing_mons = true; break; case 'S': type = "spell"; filter = _spell_filter; break; case 'K': type = "skill"; filter = _skill_filter; break; case 'I': type = "item"; extra = " Enter a single letter to list items displayed by " "that symbol."; filter = _item_filter; doing_items = true; break; case 'F': type = "feature"; filter = _feature_filter; recap = _recap_feat_keys; break; case 'G': type = "god"; filter = NULL; want_regex = false; doing_gods = true; break; case 'B': type = "branch"; filter = NULL; want_regex = false; want_sort = false; doing_branches = true; break; default: error_inout = "Okay, then."; again = false; return (false); } std::string regex = ""; if (want_regex) { mprf(MSGCH_PROMPT, "Describe a %s; partial names and regexps are fine.%s", type.c_str(), extra.c_str()); mpr("Describe what? ", MSGCH_PROMPT); char buf[80]; if (cancelable_get_line(buf, sizeof(buf)) || buf[0] == '\0') { error_inout = "Okay, then."; return (false); } if (strlen(buf) == 1) regex = buf; else regex = trimmed_string(buf); if (regex.empty()) { error_inout = "Description must contain at least " "one non-space."; return (false); } } bool by_mon_symbol = (doing_mons && regex.size() == 1); bool by_item_symbol = (doing_items && regex.size() == 1); if (by_mon_symbol) want_regex = false; bool exact_match = false; if (want_regex && !(*filter)(regex, "")) { // Try to get an exact match first. std::string desc = getLongDescription(regex); if (!desc.empty()) exact_match = true; } std::vector key_list; if (by_mon_symbol) key_list = _get_monster_keys(regex[0]); else if (by_item_symbol) key_list = item_name_list_for_glyph(regex[0]); else if (doing_gods) key_list = _get_god_keys(); else if (doing_branches) key_list = _get_branch_keys(); else key_list = _get_desc_keys(regex, filter); if (recap != NULL) (*recap)(key_list); if (key_list.size() == 0) { if (by_mon_symbol) { error_inout = "No monsters with symbol '"; error_inout += regex; error_inout += "'."; } else if (by_item_symbol) { error_inout = "No items with symbol '"; error_inout += regex; error_inout += "'."; } else { error_inout = "No matching "; error_inout += type; error_inout += "s."; } return (false); } else if (key_list.size() > 52) { if (by_mon_symbol) { error_inout = "Too many monsters with symbol '"; error_inout += regex; error_inout += "' to display"; } else { std::ostringstream os; os << "Too many matching " << type << "s (" << key_list.size() << ") to display."; error_inout = os.str(); } return (false); } else if (key_list.size() == 1) return _do_description(key_list[0]); if (exact_match) { std::string footer = "This entry is an exact match for '"; footer += regex; footer += "'. To see non-exact matches, press space."; if (!_do_description(regex, footer)) { DEBUGSTR("do_description() returned false for exact_match"); return (false); } if (getch() != ' ') return (false); } if (want_sort) std::sort(key_list.begin(), key_list.end()); DescMenu desc_menu(MF_SINGLESELECT | MF_ANYPRINTABLE | MF_ALWAYS_SHOW_MORE | MF_ALLOW_FORMATTING, doing_mons); desc_menu.set_tag("description"); std::list monster_types; for (unsigned int i = 0, size = key_list.size(); i < size; i++) { const char letter = index_to_letter(i); std::string str = uppercase_first(key_list[i]); MenuEntry *me = new MenuEntry(uppercase_first(key_list[i]), MEL_ITEM, 1, letter); if (doing_mons) { monster_type mon = get_monster_by_name(str, true); unsigned char colour = mons_class_colour(mon); monster_types.push_back(mon); if (colour == BLACK) colour = LIGHTGREY; std::string prefix = "(<"; prefix += colour_to_str(colour); prefix += ">"; prefix += stringize_glyph(mons_char(mon)); prefix += ") "; me->text = prefix + str; me->data = &*monster_types.rbegin(); } else me->data = (void*) &key_list[i]; desc_menu.add_entry(me); } desc_menu.sort(); while (true) { std::vector sel = desc_menu.show(); redraw_screen(); if ( sel.empty() ) { if (doing_mons && desc_menu.getkey() == CONTROL('S')) desc_menu.toggle_sorting(); else return (false); } else { ASSERT(sel.size() == 1); ASSERT(sel[0]->hotkeys.size() == 1); std::string key; if (doing_mons) key = mons_type_name(*(monster_type*) sel[0]->data, DESC_PLAIN); else key = *((std::string*) sel[0]->data); if (_do_description(key)) { if (getch() == 0) getch(); } } } return (false); } static int _keyhelp_keyfilter(int ch) { switch (ch) { case ':': // If the game has begun, show notes. if (crawl_state.need_save) { display_notes(); return -1; } break; case '/': { bool again = false; std::string error; do { // resets 'again' if (_find_description(again, error) && getch() == 0) getch(); if (again) mesclr(true); } while (again); viewwindow(true, false); return -1; } case 'v': case 'V': { _print_version(); return -1; } } return ch; } /////////////////////////////////////////////////////////////////////////// // Manual menu highlighter. class help_highlighter : public MenuHighlighter { public: help_highlighter(); int entry_colour(const MenuEntry *entry) const; private: text_pattern pattern; std::string get_species_key() const; }; help_highlighter::help_highlighter() : pattern(get_species_key()) { } int help_highlighter::entry_colour(const MenuEntry *entry) const { return !pattern.empty() && pattern.matches(entry->text)? WHITE : -1; } // to highlight species in aptitudes list ('?%') std::string help_highlighter::get_species_key() const { if (player_genus(GENPC_DRACONIAN) && you.experience_level < 7) return ""; std::string result = species_name(you.species, you.experience_level); if (player_genus(GENPC_DRACONIAN)) { strip_tag(result, species_name(you.species, you.experience_level, true)); } result += " "; return (result); } //////////////////////////////////////////////////////////////////////////// static void _show_keyhelp_menu(const std::vector &lines, bool with_manual, bool easy_exit = false, int hotkey = 0) { formatted_scroller cmd_help; // Set flags, and use easy exit if necessary. int flags = MF_NOSELECT | MF_ALWAYS_SHOW_MORE | MF_NOWRAP; if (easy_exit) flags |= MF_EASY_EXIT; cmd_help.set_flags(flags, false); cmd_help.set_tag("help"); // FIXME: Allow for hiding Page down when at the end of the listing, ditto // for page up at start of listing. cmd_help.set_more( formatted_string::parse_string( "[ + : Page down. - : Page up." " Esc exits.]")); if (with_manual) { cmd_help.set_highlighter(new help_highlighter); cmd_help.f_keyfilter = _keyhelp_keyfilter; column_composer cols(2, 40); cols.add_formatted( 0, "Dungeon Crawl Help\n" "\n" "Press one of the following keys to\n" "obtain more information on a certain\n" "aspect of Dungeon Crawl.\n" "?: List of keys\n" "!: Read Me!\n" "^: Quickstart Guide\n" ":: Browse character notes\n" "~: Macros help\n" "&: Options help\n" "%: Table of aptitudes\n" "/: Lookup description\n" #ifdef USE_TILE "T: Tiles key help\n" #endif "V: Version information\n" "Home: This screen\n", true, true, _cmdhelp_textfilter); cols.add_formatted( 1, "Manual Contents\n\n" "* Table of contents\n" "A. Overview\n" "B. Starting Screen\n" "C. Attributes and Stats\n" "D. Dungeon Exploration\n" "E. Experience and Skills\n" "F. Monsters\n" "G. Items\n" "H. Spellcasting\n" "I. Targeting\n" "J. Religion\n" "K. Mutations\n" "L. Licence, Contact, History\n" "M. Keymaps, Macros, Options\n" "N. Philosophy\n" "1. List of Species\n" "2. List of Classes\n" "3. List of Skills\n" "4. Keys and Commands\n" "5. List of Enchantments\n" "6. Inscriptions\n", true, true, _cmdhelp_textfilter); std::vector blines = cols.formatted_lines(); unsigned i; for (i = 0; i < blines.size(); ++i) cmd_help.add_item_formatted_string(blines[i]); while (static_cast(++i) < get_number_of_lines()) cmd_help.add_item_string(""); // unscrollable cmd_help.add_entry(new MenuEntry(std::string(), MEL_TITLE)); } for (unsigned i = 0; i < lines.size(); ++i) cmd_help.add_item_formatted_string(lines[i], (i == 0 ? '?' : 0) ); if (with_manual) { for (int i = 0; help_files[i].name != NULL; ++i) { // Attempt to open this file, skip it if unsuccessful. FILE* fp = fopen(datafile_path(help_files[i].name, false).c_str(), "r"); if (!fp) continue; // Put in a separator... cmd_help.add_item_string(""); cmd_help.add_item_string(std::string(get_number_of_cols()-1,'=')); cmd_help.add_item_string(""); // ...and the file itself. _add_file_to_scroller(fp, cmd_help, help_files[i].hotkey, help_files[i].auto_hotkey); // Done with this file. fclose(fp); } } if (hotkey) cmd_help.jump_to_hotkey(hotkey); cmd_help.show(); } void show_specific_help( const std::string &help ) { std::vector lines = split_string("\n", help, false, true); std::vector formatted_lines; for (int i = 0, size = lines.size(); i < size; ++i) { formatted_lines.push_back( formatted_string::parse_string( lines[i], true, _cmdhelp_textfilter)); } _show_keyhelp_menu(formatted_lines, false, true); } void show_levelmap_help() { show_specific_help( getHelpString("level-map") ); } void show_targeting_help() { column_composer cols(2, 41); // Page size is number of lines - one line for --more-- prompt. cols.set_pagesize(get_number_of_lines() - 1); cols.add_formatted(0, targeting_help_1, true, true); cols.add_formatted(1, targeting_help_2, true, true); _show_keyhelp_menu(cols.formatted_lines(), false, true); } void show_interlevel_travel_branch_help() { show_specific_help( getHelpString("interlevel-travel.branch.prompt") ); } void show_interlevel_travel_depth_help() { show_specific_help( getHelpString("interlevel-travel.depth.prompt") ); } void show_stash_search_help() { show_specific_help( getHelpString("stash-search.prompt") ); } void show_butchering_help() { show_specific_help( getHelpString("butchering") ); } static void _add_formatted_keyhelp(column_composer &cols) { cols.add_formatted( 0, "Movement:\n" "To move in a direction or to attack, \n" "use the numpad (try Numlock off and \n" "on) or vi keys:\n" " 1 2 3 y k u\n" " \\|/ \\|/\n" " 4-5-6" " h-.-l\n" " /|\\ /|\\\n" " 7 8 9 b j n\n", true, true, _cmdhelp_textfilter); cols.add_formatted( 0, "Rest/Search:\n" "s : wait a turn; searches adjacent\n" " squares (also numpad-5, ., Del)\n" "5 : rest and long search; stops when\n" " Health or Magic become full,\n" " something is detected, or after\n" " 100 turns over (Shift-numpad-5)\n", true, true, _cmdhelp_textfilter); cols.add_formatted( 0, "Extended Movement:\n" "o : auto-explore\n" "Ctrl-G : interlevel travel (also G)\n" "Ctrl-F : Find items\n" "Ctrl-W : set Waypoint\n" "Ctrl-E : Exclude square from searches\n" "/ Dir., Shift-Dir.: long walk\n" "* Dir., Ctrl-Dir. : open/close door, \n" " untrap, attack without move\n", true, true, _cmdhelp_textfilter); unsigned ch; unsigned short colour; std::string item_types = "Item types (and common commands)\n" ") : hand weapons (wield)\n" "( : missiles (Quiver, fire, ( cycle)\n" "[ : armour (Wear and Take off)\n" "% : corpses and food (chop up and eat)\n" "? : scrolls (read)\n" "! : potions (quaff)\n" "= : rings (Put on and Remove)\n" "\" : amulets (Put on and Remove)\n" "/ : wands (Zap)\n" ""; get_item_symbol(DNGN_ITEM_BOOK, &ch, &colour); item_types += static_cast(ch); item_types += " : books (read, Memorise and zap)\n" "\\ : staves and rods (wield and evoke)\n" "} : miscellaneous items (evoke)\n" "0 : the Orb of Zot (Carry the Orb \n" " to the surface and win!)\n" "$ : gold\n", cols.add_formatted( 0, item_types, true, true, _cmdhelp_textfilter); cols.add_formatted( 0, "Other Gameplay Actions:\n" "a : use special Ability\n" "p : Pray\n" "z : cast a spell\n" "I : list all spells\n" "t : tell allies (tt to shout)\n" "` : re-do previous command\n" "0 : repeat next command # of times\n", true, true, _cmdhelp_textfilter); cols.add_formatted( 0, "Non-Gameplay Commands / Info\n" "Ctrl-P : show Previous messages\n" "Ctrl-R : Redraw screen\n" "Ctrl-C : Clear main and level maps\n" "! : annotate the dungeon level\n" "# : dump character to file\n" ": : add note (use ?: to read notes)\n" "~ : add macro (also Ctrl-D)\n" "= : reassign inventory/spell letters\n" "_ : read messages (online play only)" " \n", true, true, _cmdhelp_textfilter); cols.add_formatted( 1, "Game Saving and Quitting:\n" "S : Save game and exit\n" "Ctrl-X : save and eXit without query\n" "Ctrl-Q : Quit without saving\n", true, true, _cmdhelp_textfilter); cols.add_formatted( 1, "Player Character Information:\n" "@ : display character status\n" "m : show skill screen\n" "% : show resistances\n" "^ : show religion screen\n" "A : show Abilities/mutations\n" "\\ : show item knowledge\n" "[ : display worn armour\n" ") : display current weapons\n" "\" : display worn jewellery\n" "E : display experience info\n" "V : list monsters in sight\n", true, true, _cmdhelp_textfilter); cols.add_formatted( 1, "Dungeon Interaction and Information:\n" "O/C : Open/Close door\n" "<</> : use staircase (<< enter shop)\n" "; : examine occupied tile\n" "x : eXamine surroundings/targets\n" "X : eXamine level map\n" "Ctrl-O : show dungeon Overview\n" "Ctrl-A : toggle auto-pickup\n" "Ctrl-T : change ally pickup behaviour\n", true, true, _cmdhelp_textfilter); cols.add_formatted( 1, "Item Interaction (inventory):\n" "i : show Inventory list\n" "] : show inventory of equipped items\n" "{ : inscribe item\n" "f : Fire next appropriate item\n" "F : select an item and Fire it\n" "( : cycle current ammunition\n" "e : Eat food (but tries floor first)\n" "q : Quaff a potion\n" "Z : Zap a wand\n" "r : Read a scroll or book\n" "M : Memorise a spell from a book\n" "w : Wield an item ( - for none)\n" "' : wield item a, or switch to b\n" "v : eVoke power of wielded item\n" "W/T : Wear or Take off armour\n" "P/R : Put on or Remove jewellery\n", true, true, _cmdhelp_textfilter); cols.add_formatted( 1, "Item Interaction (floor):\n" ", : pick up items (also g) \n" " (press twice for pick up menu) \n" "d : Drop an item\n" "d#: Drop exact number of items \n" "c : Chop up a corpse \n" "e : Eat food from floor \n", true, true, _cmdhelp_textfilter); cols.add_formatted( 1, "Additional help:\n" "Many commands have context sensitive \n" "help, among them X, x, f (or any \n" "form of targeting), Ctrl-G or G, and \n" "Ctrl-F.\n", true, true, _cmdhelp_textfilter); } static void _add_formatted_tutorial_help(column_composer &cols) { unsigned ch; unsigned short colour; std::ostringstream text; text << "Item types (and common commands)\n" ") : hand weapons (wield)\n" "( : missiles (Quiver, fire, ( to cycle ammo)\n" "[ : armour (Wear and Take off)\n" "% : corpses and food (chop up and eat)\n" "? : scrolls (read)\n" "! : potions (quaff)\n" "= : rings (Put on and Remove)\n" "\" : amulets (Put on and Remove)\n" "/ : wands (Zap)\n" ""; get_item_symbol(DNGN_ITEM_BOOK, &ch, &colour); text << static_cast(ch); text << " : books (read, Memorise and " "zap)\n" ""; get_item_symbol(DNGN_ITEM_STAVE, &ch, &colour); text << static_cast(ch); text << " : staves, rods (wield and evoke)\n" "\n" "Movement and attacking\n" "Use the numpad for movement (try both\n" "Numlock on and off). You can also use\n" " hjkl : left, down, up, right and\n" " yubn : diagonal movement.\n" "Walking into a monster will attack it\n" "with the wielded weapon or barehanded.\n" "For ranged attacks use either\n" "f to launch missiles (like arrows)\n" "z to cast spells (z? lists spells).\n", cols.add_formatted( 0, text.str(), true, true, _cmdhelp_textfilter); cols.add_formatted( 1, "Additional important commands\n" "S : Save the game and exit\n" "s : search for one turn (also . and Del)\n" "5 : rest full/search longer (Shift-Num 5)\n" "x : examine surroundings\n" "i : list inventory (select item to view it)\n" "g : pick up item from ground (also ,)\n" "d : drop item\n" "X : show map of the whole level\n" "<< or > : ascend/descend the stairs\n" "Ctrl-P : show previous messages\n" "\n" "Targeting (for spells and missiles)\n" "Use + (or =) and - to cycle between\n" "hostile monsters. Enter or . or Del\n" "all fire at the selected target.\n" "If the previous target is still alive\n" "and in sight, one of f or p fires at it\n" "again (without selecting anything).\n", true, true, _cmdhelp_textfilter, 40); } void list_commands(bool wizard, int hotkey, bool do_redraw_screen) { if (wizard) { _list_wizard_commands(); if (do_redraw_screen) redraw_screen(); return; } // 2 columns, split at column 40. column_composer cols(2, 41); // Page size is number of lines - one line for --more-- prompt. cols.set_pagesize(get_number_of_lines() - 1); if (Options.tutorial_left) _add_formatted_tutorial_help(cols); else _add_formatted_keyhelp(cols); _show_keyhelp_menu(cols.formatted_lines(), true, false, hotkey); if (do_redraw_screen) { clrscr(); redraw_screen(); } } static void _list_wizard_commands() { #ifdef WIZARD // 2 columns column_composer cols(2, 43); // Page size is number of lines - one line for --more-- prompt. cols.set_pagesize(get_number_of_lines()); cols.add_formatted(0, "Player stats\n" "A : set all skills to level\n" "g : add a skill\n" "r : change character's species\n" "s : gain 20000 skill points\n" "S : set skill to level\n" "x : gain an experience level\n" "Ctrl-X : change experience level\n" "$ : get 1000 gold\n" "] : get a mutation\n" "[ : get a demonspawn mutation\n" "^ : gain piety\n" "_ : gain religion\n" "@ : set Str Int Dex\n" "\n" "Other player related effects\n" "c : card effect\n" "Ctrl-G : save ghost (bones file)\n" "h/H : heal yourself (super-Heal)\n" "X : make Xom do something now\n" "z/Z : cast spell by number/name\n" "\n" "Item related commands\n" "a : acquirement\n" "C : (un)curse item\n" "i/I : identify/unidentify inventory\n" "o/% : create an object\n" "t : tweak object properties\n" "v : show gold value of an item\n" "| : create all unrand/fixed artefacts\n" "+ : make randart from item\n" "' : list items\n", true, true); cols.add_formatted(1, "Monster related commands\n" "G : banish all monsters\n" "m/M : create monster by number/name\n" "\" : list monsters\n" "\n" "Create level features\n" "l : make entrance to labyrinth\n" "L : place a vault by name\n" "p : make entrance to pandemonium\n" "P : make a portal (i.e., bazaars)\n" "T : make a trap\n" "<</> : create up/down staircase\n" "(/) : make feature by number/name\n" "\\ : make a shop\n" "\n" "Other level related commands\n" "Ctrl-A : generate new Abyss area\n" "b : controlled blink\n" "B : banish yourself to the Abyss\n" "u/d : shift up/down one level\n" "~ : go to a specific level\n" ": : find branches in the dungeon\n" "{ : magic mapping\n" "\n" "Debugging commands\n" "f : player combat damage stats\n" "F : combat stats with fsim_kit\n" "Ctrl-F : combat stats (monster vs PC)\n" "Ctrl-I : item generation stats\n" "\n" "\n" "? : list wizard commands\n", true, true); _show_keyhelp_menu(cols.formatted_lines(), false, true); #endif }