/** * @file * @brief Player related debugging functions. **/ #include "AppHdr.h" #include "wiz-you.h" #include #include #include "abyss.h" #include "chardump.h" #include "cio.h" #include "dbg-util.h" #include "env.h" #include "food.h" #include "godprayer.h" #include "godwrath.h" #include "itemname.h" #include "items.h" #include "jobs.h" #include "libutil.h" #include "macro.h" #include "makeitem.h" #include "message.h" #include "mutation.h" #include "newgame.h" #include "ng-setup.h" #include "output.h" #include "player.h" #include "prompt.h" #include "religion.h" #include "skills.h" #include "skills2.h" #include "spl-book.h" #include "spl-cast.h" #include "spl-util.h" #include "state.h" #include "status.h" #include "strings.h" #include "terrain.h" #include "transform.h" #include "view.h" #include "unicode.h" #include "xom.h" #ifdef WIZARD static void _swap_equip(equipment_type a, equipment_type b) { swap(you.equip[a], you.equip[b]); bool tmp = you.melded[a]; you.melded.set(a, you.melded[b]); you.melded.set(b, tmp); } job_type find_job_from_string(const string &job) { string spec = lowercase_string(job); job_type j = JOB_UNKNOWN; for (int i = 0; i < NUM_JOBS; ++i) { const job_type ji = static_cast(i); const string name = lowercase_string(get_job_name(ji)); string::size_type pos = name.find(spec); if (pos != string::npos) { if (pos == 0) { // We prefer prefixes over partial matches. j = ji; break; } else j = ji; } } return j; } species_type find_species_from_string(const string &species) { string spec = lowercase_string(species); species_type sp = SP_UNKNOWN; for (int i = 0; i < NUM_SPECIES; ++i) { const species_type si = static_cast(i); const string sp_name = lowercase_string(species_name(si)); string::size_type pos = sp_name.find(spec); if (pos != string::npos) { if (pos == 0) { // We prefer prefixes over partial matches. sp = si; break; } else sp = si; } } return sp; } void wizard_change_species_to(species_type sp) { // Can't use magic cookies or placeholder species. if (!is_valid_species(sp)) { mpr("That species isn't available."); return; } // Re-scale skill-points. for (int i = SK_FIRST_SKILL; i < NUM_SKILLS; ++i) { skill_type sk = static_cast(i); you.skill_points[i] *= species_apt_factor(sk, sp) / species_apt_factor(sk); } species_type old_sp = you.species; you.species = sp; you.species_name = species_name(sp); you.is_undead = get_undead_state(sp); // Change permanent mutations, but preserve non-permanent ones. uint8_t prev_muts[NUM_MUTATIONS]; for (int i = 0; i < NUM_MUTATIONS; ++i) { if (you.innate_mutation[i] > 0) { if (you.innate_mutation[i] > you.mutation[i]) you.mutation[i] = 0; else you.mutation[i] -= you.innate_mutation[i]; you.innate_mutation[i] = 0; } prev_muts[i] = you.mutation[i]; } give_basic_mutations(sp); for (int i = 0; i < NUM_MUTATIONS; ++i) { if (prev_muts[i] > you.innate_mutation[i]) you.innate_mutation[i] = 0; else you.innate_mutation[i] -= prev_muts[i]; } switch (sp) { case SP_RED_DRACONIAN: if (you.experience_level >= 7) perma_mutate(MUT_HEAT_RESISTANCE, 1, "wizard race change"); break; case SP_WHITE_DRACONIAN: if (you.experience_level >= 7) perma_mutate(MUT_COLD_RESISTANCE, 1, "wizard race change"); break; case SP_GREEN_DRACONIAN: if (you.experience_level >= 7) perma_mutate(MUT_POISON_RESISTANCE, 1, "wizard race change"); if (you.experience_level >= 14) perma_mutate(MUT_STINGER, 1, "wizard race change"); break; case SP_YELLOW_DRACONIAN: if (you.experience_level >= 14) perma_mutate(MUT_ACIDIC_BITE, 1, "wizard race change"); break; case SP_GREY_DRACONIAN: if (you.experience_level >= 7) perma_mutate(MUT_UNBREATHING, 1, "wizard race change"); break; case SP_BLACK_DRACONIAN: if (you.experience_level >= 7) perma_mutate(MUT_SHOCK_RESISTANCE, 1, "wizard race change"); if (you.experience_level >= 14) perma_mutate(MUT_BIG_WINGS, 1, "wizard race change"); break; case SP_DEMONSPAWN: { roll_demonspawn_mutations(); for (int i = 0; i < int(you.demonic_traits.size()); ++i) { mutation_type m = you.demonic_traits[i].mutation; if (you.demonic_traits[i].level_gained > you.experience_level) continue; ++you.mutation[m]; ++you.innate_mutation[m]; } break; } case SP_DEEP_DWARF: if (you.experience_level >= 9) perma_mutate(MUT_PASSIVE_MAPPING, 1, "wizard race change"); if (you.experience_level >= 14) perma_mutate(MUT_NEGATIVE_ENERGY_RESISTANCE, 1, "wizard race change"); if (you.experience_level >= 18) perma_mutate(MUT_PASSIVE_MAPPING, 1, "wizard race change"); break; case SP_FELID: if (you.experience_level >= 6) perma_mutate(MUT_SHAGGY_FUR, 1, "wizard race change"); if (you.experience_level >= 12) perma_mutate(MUT_SHAGGY_FUR, 1, "wizard race change"); break; default: break; } if ((old_sp == SP_OCTOPODE) != (sp == SP_OCTOPODE)) { _swap_equip(EQ_LEFT_RING, EQ_RING_ONE); _swap_equip(EQ_RIGHT_RING, EQ_RING_TWO); // All species allow exactly one amulet. When (and knowing you guys, // that's "when" not "if") ettins go in, you'll need handle the Macabre // Finger Necklace on neck 2 here. } // FIXME: this checks only for valid slots, not for suitability of the // item in question. This is enough to make assertions happy, though. for (int i = 0; i < NUM_EQUIP; ++i) if (!you_can_wear(i, true) && you.equip[i] != -1) { mprf("%s falls away.", you.inv[you.equip[i]].name(DESC_YOUR).c_str()); // Unwear items without the usual processing. you.equip[i] = -1; you.melded.set(i, false); } // Sanitize skills. fixup_skills(); calc_hp(); calc_mp(); // The player symbol depends on species. update_player_symbol(); #ifdef USE_TILE init_player_doll(); #endif redraw_screen(); } void wizard_change_job_to(job_type job) { you.char_class = job; you.class_name = get_job_name(job); } void wizard_change_species() { char specs[80]; msgwin_get_line("What species would you like to be now? " , specs, sizeof(specs)); if (specs[0] == '\0') return; species_type sp = find_species_from_string(specs); wizard_change_species_to(sp); } #endif #ifdef WIZARD // Casts a specific spell by number or name. void wizard_cast_spec_spell() { char specs[80], *end; int spell; mprf(MSGCH_PROMPT, "Cast which spell? "); if (cancellable_get_line_autohist(specs, sizeof(specs)) || specs[0] == '\0') { canned_msg(MSG_OK); crawl_state.cancel_cmd_repeat(); return; } spell = strtol(specs, &end, 10); if (spell < 0 || end == specs) { if ((spell = spell_by_name(specs, true)) == SPELL_NO_SPELL) { mpr("Cannot find that spell."); crawl_state.cancel_cmd_repeat(); return; } } if (your_spells(static_cast(spell), 0, false) == SPRET_ABORT) crawl_state.cancel_cmd_repeat(); } void wizard_memorise_spec_spell() { char specs[80], *end; int spell; mprf(MSGCH_PROMPT, "Memorise which spell? "); if (cancellable_get_line_autohist(specs, sizeof(specs)) || specs[0] == '\0') { canned_msg(MSG_OK); crawl_state.cancel_cmd_repeat(); return; } spell = strtol(specs, &end, 10); if (spell < 0 || end == specs) { if ((spell = spell_by_name(specs, true)) == SPELL_NO_SPELL) { mpr("Cannot find that spell."); crawl_state.cancel_cmd_repeat(); return; } } if (!learn_spell(static_cast(spell))) crawl_state.cancel_cmd_repeat(); } #endif void wizard_heal(bool super_heal) { if (super_heal) { // Clear more stuff and give a HP boost. unrot_hp(9999); you.magic_contamination = 0; you.duration[DUR_LIQUID_FLAMES] = 0; you.clear_beholders(); inc_max_hp(10); you.attribute[ATTR_XP_DRAIN] = 0; you.duration[DUR_PETRIFIED] = 0; you.duration[DUR_PETRIFYING] = 0; you.duration[DUR_CORROSION] = 0; } // Clear most status ailments. you.rotting = 0; you.disease = 0; you.duration[DUR_CONF] = 0; you.duration[DUR_POISONING] = 0; you.duration[DUR_EXHAUSTED] = 0; set_hp(you.hp_max); set_mp(you.max_magic_points); set_hunger(HUNGER_VERY_FULL + 100, true); you.redraw_hit_points = true; you.redraw_armour_class = true; you.redraw_evasion = true; } void wizard_set_hunger_state() { string hunger_prompt = "Set hunger state to s(T)arving, (N)ear starving, (H)ungry"; if (you.species == SP_GHOUL) hunger_prompt += " or (S)atiated"; else hunger_prompt += ", (S)atiated, (F)ull or (E)ngorged"; hunger_prompt += "? "; mprf(MSGCH_PROMPT, "%s", hunger_prompt.c_str()); const int c = toalower(getchk()); // Values taken from food.cc. switch (c) { case 't': you.hunger = HUNGER_STARVING / 2; break; case 'n': you.hunger = 1100; break; case 'h': you.hunger = 2300; break; case 's': you.hunger = 4900; break; case 'f': you.hunger = 7900; break; case 'e': you.hunger = HUNGER_MAXIMUM; break; default: canned_msg(MSG_OK); break; } food_change(); if (you.species == SP_GHOUL && you.hunger_state >= HS_SATIATED) mpr("Ghouls can never be full or above!"); } void wizard_set_piety_to(int newpiety, bool force) { if (newpiety < 0 || newpiety > MAX_PIETY) { mprf("Piety needs to be between 0 and %d.", MAX_PIETY); return; } if (you_worship(GOD_XOM)) { you.piety = newpiety; int newinterest; if (!force) { char buf[30]; // For Xom, also allow setting interest. mprf(MSGCH_PROMPT, "Enter new interest (current = %d, Enter for 0): ", you.gift_timeout); if (cancellable_get_line_autohist(buf, sizeof buf)) { canned_msg(MSG_OK); return; } newinterest = atoi(buf); } else newinterest = newpiety; if (newinterest >= 0 && newinterest < 256) you.gift_timeout = newinterest; else mpr("Interest must be between 0 and 255."); mprf("Set piety to %d, interest to %d.", you.piety, newinterest); const string new_xom_favour = describe_xom_favour(); const string msg = "You are now " + new_xom_favour; god_speaks(you.religion, msg.c_str()); return; } if (newpiety < 1 && !force) { if (yesno("Are you sure you want to be excommunicated?", false, 'n')) { you.piety = 0; excommunication(); } else canned_msg(MSG_OK); return; } mprf("Setting piety to %d.", newpiety); set_piety(newpiety); // Automatically reduce penance to 0. if (player_under_penance()) dec_penance(you.penance[you.religion]); } void wizard_set_piety() { if (you_worship(GOD_NO_GOD)) { mpr("You are not religious!"); return; } mprf(MSGCH_PROMPT, "Enter new piety value (current = %d, Enter for 0): ", you.piety); char buf[30]; if (cancellable_get_line_autohist(buf, sizeof buf)) { canned_msg(MSG_OK); return; } wizard_set_piety_to(atoi(buf)); } //--------------------------------------------------------------- // // debug_add_skills // //--------------------------------------------------------------- #ifdef WIZARD void wizard_exercise_skill() { skill_type skill = debug_prompt_for_skill("Which skill (by name)? "); if (skill == SK_NONE) mpr("That skill doesn't seem to exist."); else { mpr("Exercising..."); exercise(skill, 10); } } #endif #ifdef WIZARD void wizard_set_skill_level(skill_type skill) { if (skill == SK_NONE) skill = debug_prompt_for_skill("Which skill (by name)? "); if (skill == SK_NONE) { mpr("That skill doesn't seem to exist."); return; } mpr(skill_name(skill)); double amount = prompt_for_float("To what level? "); if (amount < 0 || amount > 27) { canned_msg(MSG_OK); return; } const int old_amount = you.skills[skill]; set_skill_level(skill, amount); if (amount == 27) { you.train[skill] = 0; you.train_alt[skill] = 0; reset_training(); check_selected_skills(); } redraw_skill(skill); mprf("%s %s to skill level %.1f.", (old_amount < amount ? "Increased" : old_amount > amount ? "Lowered" : "Reset"), skill_name(skill), amount); } #endif #ifdef WIZARD void wizard_set_all_skills() { double amount = prompt_for_float("Set all skills to what level? "); if (amount < 0) // cancel returns -1 -- bwr canned_msg(MSG_OK); else { if (amount > 27) amount = 27; for (int i = SK_FIRST_SKILL; i < NUM_SKILLS; ++i) { skill_type sk = static_cast(i); if (is_invalid_skill(sk) || is_useless_skill(sk)) continue; set_skill_level(sk, amount); if (amount == 27) { you.train[sk] = 0; you.training[sk] = 0; } } you.redraw_title = true; // We're not updating skill cost here since XP hasn't changed. calc_hp(); calc_mp(); you.redraw_armour_class = true; you.redraw_evasion = true; } } #endif #ifdef WIZARD bool wizard_add_mutation() { bool success = false; char specs[80]; if (player_mutation_level(MUT_MUTATION_RESISTANCE) > 0 && !crawl_state.is_replaying_keys()) { const char* msg; if (you.mutation[MUT_MUTATION_RESISTANCE] == 3) msg = "You are immune to mutations; remove immunity?"; else msg = "You are resistant to mutations; remove resistance?"; if (yesno(msg, true, 'n')) { you.mutation[MUT_MUTATION_RESISTANCE] = 0; crawl_state.cancel_cmd_repeat(); } } int answer = yesnoquit("Force mutation to happen?", true, 'n'); if (answer == -1) { canned_msg(MSG_OK); return false; } const bool force = (answer == 1); if (player_mutation_level(MUT_MUTATION_RESISTANCE) == 3 && !force) { mpr("Can't mutate when immune to mutations without forcing it."); crawl_state.cancel_cmd_repeat(); return false; } answer = yesnoquit("Treat mutation as god gift?", true, 'n'); if (answer == -1) { canned_msg(MSG_OK); return false; } const bool god_gift = (answer == 1); msgwin_get_line("Which mutation (name, 'good', 'bad', 'any', " "'xom', 'slime', 'corrupt', 'qazlal')? ", specs, sizeof(specs)); if (specs[0] == '\0') return false; string spec = lowercase_string(specs); mutation_type mutat = NUM_MUTATIONS; if (spec == "good") mutat = RANDOM_GOOD_MUTATION; else if (spec == "bad") mutat = RANDOM_BAD_MUTATION; else if (spec == "any") mutat = RANDOM_MUTATION; else if (spec == "xom") mutat = RANDOM_XOM_MUTATION; else if (spec == "slime") mutat = RANDOM_SLIME_MUTATION; else if (spec == "corrupt") mutat = RANDOM_CORRUPT_MUTATION; else if (spec == "qazlal") mutat = RANDOM_QAZLAL_MUTATION; if (mutat != NUM_MUTATIONS) { int old_resist = player_mutation_level(MUT_MUTATION_RESISTANCE); success = mutate(mutat, "wizard power", true, force, god_gift); if (old_resist < player_mutation_level(MUT_MUTATION_RESISTANCE) && !force) { crawl_state.cancel_cmd_repeat("Your mutation resistance has " "increased."); } return success; } vector partial_matches; for (int i = 0; i < NUM_MUTATIONS; ++i) { mutation_type mut = static_cast(i); const char* wizname = mutation_name(mut); if (!wizname) continue; if (spec == wizname) { mutat = mut; break; } if (strstr(wizname, spec.c_str())) partial_matches.push_back(mut); } // If only one matching mutation, use that. if (mutat == NUM_MUTATIONS && partial_matches.size() == 1) mutat = partial_matches[0]; if (mutat == NUM_MUTATIONS) { crawl_state.cancel_cmd_repeat(); if (partial_matches.empty()) mpr("No matching mutation names."); else { vector matches; for (unsigned int i = 0; i < partial_matches.size(); ++i) matches.push_back(mutation_name(partial_matches[i])); string prefix = "No exact match for mutation '" + spec + "', possible matches are: "; // Use mpr_comma_separated_list() because the list // might be *LONG*. mpr_comma_separated_list(prefix, matches, " and ", ", ", MSGCH_DIAGNOSTICS); } return false; } else { mprf("Found #%d: %s (\"%s\")", (int) mutat, mutation_name(mutat), mutation_desc(mutat, 1, false).c_str()); const int levels = prompt_for_int("How many levels to increase or decrease? ", false); if (levels == 0) { canned_msg(MSG_OK); success = false; } else if (levels > 0) { for (int i = 0; i < levels; ++i) if (mutate(mutat, "wizard power", true, force, god_gift)) success = true; } else { for (int i = 0; i < -levels; ++i) if (delete_mutation(mutat, "wizard power", true, force, god_gift)) success = true; } } return success; } #endif void wizard_set_abyss() { char buf[80]; mprf(MSGCH_PROMPT, "Enter values for X, Y, Z (space separated) or return: "); if (!cancellable_get_line_autohist(buf, sizeof buf)) abyss_teleport(); uint32_t x = 0, y = 0, z = 0; sscanf(buf, "%d %d %d", &x, &y, &z); set_abyss_state(coord_def(x,y), z); } void wizard_set_stats() { char buf[80]; mprf(MSGCH_PROMPT, "Enter values for Str, Int, Dex (space separated): "); if (cancellable_get_line_autohist(buf, sizeof buf)) return; int sstr = you.strength(false), sdex = you.dex(false), sint = you.intel(false); sscanf(buf, "%d %d %d", &sstr, &sint, &sdex); you.base_stats[STAT_STR] = debug_cap_stat(sstr); you.base_stats[STAT_INT] = debug_cap_stat(sint); you.base_stats[STAT_DEX] = debug_cap_stat(sdex); you.stat_loss.init(0); you.redraw_stats.init(true); you.redraw_evasion = true; } void wizard_edit_durations() { vector durs; size_t max_len = 0; for (int i = 0; i < NUM_DURATIONS; ++i) { const duration_type dur = static_cast(i); if (!you.duration[i]) continue; max_len = max(strlen(duration_name(dur)), max_len); durs.push_back(dur); } if (!durs.empty()) { for (size_t i = 0; i < durs.size(); ++i) { const duration_type dur = durs[i]; const char ch = i >= 26 ? ' ' : 'a' + i; mprf_nocap(MSGCH_PROMPT, "%c%c %-*s : %d", ch, ch == ' ' ? ' ' : ')', (int)max_len, duration_name(dur), you.duration[dur]); } mprf(MSGCH_PROMPT, "\nEdit which duration (letter or name)? "); } else mprf(MSGCH_PROMPT, "Edit which duration (name)? "); char buf[80]; if (cancellable_get_line_autohist(buf, sizeof buf) || !*buf) { canned_msg(MSG_OK); return; } if (!strlcpy(buf, lowercase_string(trimmed_string(buf)).c_str(), sizeof(buf))) { canned_msg(MSG_OK); return; } duration_type choice = NUM_DURATIONS; if (strlen(buf) == 1 && isalower(buf[0])) { if (durs.empty()) { mprf(MSGCH_PROMPT, "No existing durations to choose from."); return; } const int dchoice = buf[0] - 'a'; if (dchoice < 0 || dchoice >= (int) durs.size()) { mprf(MSGCH_PROMPT, "Invalid choice."); return; } choice = durs[dchoice]; } else { vector matches; vector match_names; for (int i = 0; i < NUM_DURATIONS; ++i) { const duration_type dur = static_cast(i); if (strcmp(duration_name(dur), buf) == 0) { choice = dur; break; } if (strstr(duration_name(dur), buf) != NULL) { matches.push_back(dur); match_names.push_back(duration_name(dur)); } } if (choice != NUM_DURATIONS) ; else if (matches.size() == 1) choice = matches[0]; else if (matches.empty()) { mprf(MSGCH_PROMPT, "No durations matching '%s'.", buf); return; } else { string prefix = "No exact match for duration '"; prefix += buf; prefix += "', possible matches are: "; mpr_comma_separated_list(prefix, match_names, " and ", ", ", MSGCH_DIAGNOSTICS); return; } } snprintf(buf, sizeof(buf), "Set '%s' to: ", duration_name(choice)); int num = prompt_for_int(buf, false); if (num == 0) { mprf(MSGCH_PROMPT, "Can't set duration directly to 0, setting it to 1 instead."); num = 1; } you.duration[choice] = num; } void wizard_list_props() { mprf(MSGCH_DIAGNOSTICS, "props: %s", you.describe_props().c_str()); } static void debug_uptick_xl(int newxl, bool train) { if (train) { you.exp_available += exp_needed(newxl) - you.experience; train_skills(); } you.experience = exp_needed(newxl); level_change(NON_MONSTER, NULL, true); } /** * Set the player's XL to a new value. * @param newxl The new experience level. */ static void debug_downtick_xl(int newxl) { set_hp(you.hp_max); // boost maxhp so we don't die if heavily rotted you.hp_max_adj_perm += 1000; you.experience = exp_needed(newxl); level_change(); you.skill_cost_level = 0; check_skill_cost_change(); // restore maxhp loss you.hp_max_adj_perm -= 1000; calc_hp(); if (you.hp_max <= 0) { // ... but remove it completely if unviable you.hp_max_adj_temp = max(you.hp_max_adj_temp, 0); you.hp_max_adj_perm = max(you.hp_max_adj_perm, 0); calc_hp(); } set_hp(max(1, you.hp)); } void wizard_set_xl() { mprf(MSGCH_PROMPT, "Enter new experience level: "); char buf[30]; if (cancellable_get_line_autohist(buf, sizeof buf)) { canned_msg(MSG_OK); return; } const int newxl = atoi(buf); if (newxl < 1 || newxl > 27 || newxl == you.experience_level) { canned_msg(MSG_OK); return; } set_xl(newxl, yesno("Train skills?", true, 'n')); mprf("Experience level set to %d.", newxl); } void set_xl(const int newxl, const bool train) { no_messages mx; if (newxl < you.experience_level) debug_downtick_xl(newxl); else debug_uptick_xl(newxl, train); } void wizard_get_god_gift() { if (you_worship(GOD_NO_GOD)) { mpr("You are not religious!"); return; } if (!do_god_gift(true)) mpr("Nothing happens."); } void wizard_toggle_xray_vision() { you.xray_vision = !you.xray_vision; viewwindow(true); } void wizard_god_wrath() { if (you_worship(GOD_NO_GOD)) { mpr("You suffer the terrible wrath of No God."); return; } if (!divine_retribution(you.religion, true, true)) // Currently only dead Jiyva. mpr("You're not eligible for wrath."); } void wizard_god_mollify() { for (int i = GOD_NO_GOD; i < NUM_GODS; ++i) { if (player_under_penance((god_type) i)) dec_penance((god_type) i, you.penance[i]); } } void wizard_transform() { transformation_type form; while (true) { string line; for (int i = 0; i <= LAST_FORM; i++) { #if TAG_MAJOR_VERSION == 34 if (i == TRAN_JELLY) continue; #endif line += make_stringf("[%c] %-10s ", i + 'a', transform_name((transformation_type)i)); if (i % 5 == 4 || i == LAST_FORM) { mprf(MSGCH_PROMPT, "%s", line.c_str()); line.clear(); } } mprf(MSGCH_PROMPT, "Which form (ESC to exit)? "); int keyin = toalower(get_ch()); if (key_is_escape(keyin) || keyin == ' ' || keyin == '\r' || keyin == '\n') { canned_msg(MSG_OK); return; } if (keyin < 'a' || keyin > 'a' + LAST_FORM) continue; #if TAG_MAJOR_VERSION == 34 if ((transformation_type)(keyin - 'a') == TRAN_JELLY) continue; #endif form = (transformation_type)(keyin - 'a'); break; } you.transform_uncancellable = false; if (!transform(200, form) && you.form != form) mpr("Transformation failed."); } void wizard_join_religion() { god_type god = choose_god(); if (god == NUM_GODS) mpr("That god doesn't seem to exist!"); else if (god == GOD_NO_GOD) { if (you_worship(GOD_NO_GOD)) mpr("You already have no god!"); else excommunication(); } else if (you_worship(god)) mpr("You already worship that god!"); else join_religion(god, true); }