/* * File: notes.cc * Summary: Notetaking stuff * Written by: Haran Pilpel */ #include "AppHdr.h" #include #include #include #include "notes.h" #include "branch.h" #include "cio.h" #include "describe.h" #include "files.h" #include "kills.h" #include "hiscores.h" #include "mutation.h" #include "options.h" #include "place.h" #include "religion.h" #include "skills2.h" #include "spl-util.h" #include "tags.h" #define NOTES_VERSION_NUMBER 1002 std::vector note_list; // return the real number of the power (casting out nonexistent powers), // starting from 0, or -1 if the power doesn't exist static int _real_god_power( int religion, int idx ) { if (god_gain_power_messages[religion][idx][0] == 0) return -1; int count = 0; for (int j = 0; j < idx; ++j) if (god_gain_power_messages[religion][j][0]) ++count; return count; } static bool _is_noteworthy_skill_level( int level ) { for (unsigned int i = 0; i < Options.note_skill_levels.size(); ++i) if (level == Options.note_skill_levels[i]) return (true); return (false); } static bool _is_highest_skill( int skill ) { for (int i = 0; i < NUM_SKILLS; ++i) { if (i == skill) continue; if (you.skills[i] >= you.skills[skill]) return (false); } return (true); } static bool _is_noteworthy_hp( int hp, int maxhp ) { return (hp > 0 && Options.note_hp_percent && hp <= (maxhp * Options.note_hp_percent) / 100); } static int _dungeon_branch_depth( unsigned char branch ) { if (branch >= NUM_BRANCHES) return -1; return branches[branch].depth; } static bool _is_noteworthy_dlevel( unsigned short place ) { const unsigned char branch = static_cast((place >> 8) & 0xFF); const int lev = (place & 0xFF); // Special levels (Abyss, etc.) are always interesting. if (lev == 0xFF) return (true); if (lev == _dungeon_branch_depth(branch) || branch == BRANCH_MAIN_DUNGEON && (lev % 5) == 0 || branch != BRANCH_MAIN_DUNGEON && lev == 1) { return (true); } return (false); } // Is a note worth taking? // This function assumes that game state has not changed since // the note was taken, e.g. you.* is valid. static bool _is_noteworthy( const Note& note ) { // Always noteworthy. if (note.type == NOTE_XP_LEVEL_CHANGE || note.type == NOTE_GET_GOD || note.type == NOTE_GOD_GIFT || note.type == NOTE_GET_MUTATION || note.type == NOTE_LOSE_MUTATION || note.type == NOTE_GET_ITEM || note.type == NOTE_ID_ITEM || note.type == NOTE_BUY_ITEM || note.type == NOTE_DONATE_MONEY || note.type == NOTE_SEEN_MONSTER || note.type == NOTE_KILL_MONSTER || note.type == NOTE_POLY_MONSTER || note.type == NOTE_USER_NOTE || note.type == NOTE_MESSAGE || note.type == NOTE_LOSE_GOD || note.type == NOTE_PENANCE || note.type == NOTE_MOLLIFY_GOD || note.type == NOTE_DEATH || note.type == NOTE_XOM_REVIVAL || note.type == NOTE_SEEN_FEAT) { return (true); } // Never noteworthy, hooked up for fun or future use. if (note.type == NOTE_MP_CHANGE || note.type == NOTE_MAXHP_CHANGE || note.type == NOTE_MAXMP_CHANGE) { return (false); } // Xom effects are only noteworthy if the option is true. if (note.type == NOTE_XOM_EFFECT) return (Options.note_xom_effects); // God powers might be noteworthy if it's an actual power. if (note.type == NOTE_GOD_POWER && _real_god_power(note.first, note.second) == -1) { return (false); } // HP noteworthiness is handled in its own function. if (note.type == NOTE_HP_CHANGE && !_is_noteworthy_hp(note.first, note.second)) { return (false); } // Skills are noteworthy if in the skill value list or if // it's a new maximal skill (depending on options). if (note.type == NOTE_GAIN_SKILL) { if (Options.note_all_skill_levels || _is_noteworthy_skill_level(note.second) || Options.note_skill_max && _is_highest_skill(note.first)) { return (true); } return (false); } if (note.type == NOTE_DUNGEON_LEVEL_CHANGE) { if (!_is_noteworthy_dlevel(note.packed_place)) return (false); // Labyrinths and portal vaults are always interesting. if ((note.packed_place & 0xFF) == 0xFF && ((note.packed_place >> 8) == LEVEL_LABYRINTH || (note.packed_place >> 8) == LEVEL_PORTAL_VAULT)) { return (true); } } // Learning a spell is always noteworthy if note_all_spells is set. if (note.type == NOTE_LEARN_SPELL && Options.note_all_spells) return (true); for (unsigned i = 0; i < note_list.size(); ++i) { if (note_list[i].type != note.type) continue; const Note& rnote( note_list[i] ); switch (note.type) { case NOTE_DUNGEON_LEVEL_CHANGE: if (rnote.packed_place == note.packed_place) return (false); break; case NOTE_LEARN_SPELL: if (spell_difficulty(static_cast(rnote.first)) >= spell_difficulty(static_cast(note.first))) { return (false); } break; case NOTE_GOD_POWER: if (rnote.first == note.first && rnote.second == note.second) return (false); break; case NOTE_HP_CHANGE: // Not if we have a recent warning // unless we've lost half our HP since then. if (note.turn - rnote.turn < 5 && note.first * 2 >= rnote.first) { return (false); } break; default: mpr("Buggy note passed: unknown note type"); // Return now, rather than give a "Buggy note passed" message // for each note of the matching type in the note list. return (true); } } return (true); } static const char* _number_to_ordinal( int number ) { const char* ordinals[5] = { "first", "second", "third", "fourth", "fifth" }; if (number < 1) return "[unknown ordinal (too small)]"; if (number > 5) return "[unknown ordinal (too big)]"; return ordinals[number-1]; } std::string Note::describe( bool when, bool where, bool what ) const { std::ostringstream result; if (when) result << std::setw(6) << turn << " "; if (where) { if (!place_abbrev.empty()) result << "| " << std::setw(MAX_NOTE_PLACE_LEN) << std::left << place_abbrev << " | "; else result << "| " << std::setw(MAX_NOTE_PLACE_LEN) << std::left << short_place_name(packed_place) << " | "; } if (what) { switch ( type ) { case NOTE_HP_CHANGE: // [ds] Shortened HP change note from "Had X hitpoints" to // accommodate the cause for the loss of hitpoints. result << "HP: " << first << "/" << second << " [" << name << "]"; break; case NOTE_XOM_REVIVAL: result << "Xom revived you"; break; case NOTE_MP_CHANGE: result << "Mana: " << first << "/" << second; break; case NOTE_MAXHP_CHANGE: result << "Reached " << first << " max hit points"; break; case NOTE_MAXMP_CHANGE: result << "Reached " << first << " max mana"; break; case NOTE_XP_LEVEL_CHANGE: result << "Reached XP level " << first << ". " << name; break; case NOTE_DUNGEON_LEVEL_CHANGE: if ( !desc.empty() ) result << desc; else result << "Entered " << place_name(packed_place, true, true); break; case NOTE_LEARN_SPELL: result << "Learned a level " << spell_difficulty(static_cast(first)) << " spell: " << spell_title(static_cast(first)); break; case NOTE_GET_GOD: result << "Became a worshipper of " << god_name(static_cast(first), true); break; case NOTE_LOSE_GOD: result << "Fell from the grace of " << god_name(static_cast(first)); break; case NOTE_PENANCE: result << "Was placed under penance by " << god_name(static_cast(first)); break; case NOTE_MOLLIFY_GOD: result << "Was forgiven by " << god_name(static_cast(first)); break; case NOTE_GOD_GIFT: result << "Received a gift from " << god_name(static_cast(first)); break; case NOTE_ID_ITEM: result << "Identified " << name; if (!desc.empty()) result << " (" << desc << ")"; break; case NOTE_GET_ITEM: result << "Got " << name; break; case NOTE_BUY_ITEM: result << "Bought " << name << " for " << first << " gold piece" << (first == 1 ? "" : "s"); break; case NOTE_DONATE_MONEY: result << "Donated " << first << " gold piece" << (first == 1 ? "" : "s") << " to Zin"; break; case NOTE_GAIN_SKILL: result << "Reached skill " << second << " in " << skill_name(first); break; case NOTE_SEEN_MONSTER: result << "Noticed " << name; break; case NOTE_KILL_MONSTER: if (second) result << name << " (ally) was defeated"; else result << "Defeated " << name; break; case NOTE_POLY_MONSTER: result << name << " changed into " << desc; break; case NOTE_GOD_POWER: result << "Acquired " << god_name(static_cast(first)) << "'s " << _number_to_ordinal(_real_god_power(first, second)+1) << " power"; break; case NOTE_GET_MUTATION: result << "Gained mutation: " << mutation_name(static_cast(first), second == 0 ? 1 : second); break; case NOTE_LOSE_MUTATION: result << "Lost mutation: " << mutation_name(static_cast(first), second == 3 ? 3 : second+1); break; case NOTE_DEATH: result << name; break; case NOTE_USER_NOTE: result << Options.user_note_prefix << name; break; case NOTE_MESSAGE: result << name; break; case NOTE_SEEN_FEAT: result << "Found " << name; break; case NOTE_XOM_EFFECT: result << "XOM: " << name; #if defined(DEBUG_XOM) || defined(NOTE_DEBUG_XOM) // If debugging, also take note of piety and tension. result << " (piety: " << first; if (second >= 0) result << ", tension: " << second; result << ")"; #endif break; default: result << "Buggy note description: unknown note type"; break; } } if (type == NOTE_SEEN_MONSTER || type == NOTE_KILL_MONSTER) { if (what && first == MONS_PANDEMONIUM_DEMON) result << " the pandemonium lord"; } return result.str(); } Note::Note() { turn = you.num_turns; packed_place = get_packed_place(); if (you.level_type == LEVEL_PORTAL_VAULT) place_abbrev = you.level_type_name_abbrev; } Note::Note( NOTE_TYPES t, int f, int s, const char* n, const char* d ) : type(t), first(f), second(s), place_abbrev("") { if (n) name = std::string(n); if (d) desc = std::string(d); turn = you.num_turns; packed_place = get_packed_place(); if (you.level_type == LEVEL_PORTAL_VAULT) place_abbrev = you.level_type_name_abbrev; } void Note::check_milestone() const { #ifdef DGL_MILESTONES if (crawl_state.arena) return; if (type == NOTE_DUNGEON_LEVEL_CHANGE) { const int br = place_branch(packed_place), dep = place_depth(packed_place); if (br != -1) { std::string branch = place_name(packed_place, true, false).c_str(); if (branch.find("The ") == 0) branch[0] = tolower(branch[0]); if (dep == 1) mark_milestone("enter", "entered " + branch + "."); else if (dep == _dungeon_branch_depth(br) && br != BRANCH_HIVE) { std::string level = place_name(packed_place, true, true); if (level.find("Level ") == 0) level[0] = tolower(level[0]); std::ostringstream branch_finale; branch_finale << "reached " << level << "."; mark_milestone("branch-finale", branch_finale.str()); } } } #endif } void Note::save(writer& outf) const { marshallLong( outf, type ); marshallLong( outf, turn ); marshallShort( outf, packed_place ); marshallLong( outf, first ); marshallLong( outf, second ); marshallString4( outf, name ); marshallString4( outf, place_abbrev ); marshallString4( outf, desc ); } void Note::load(reader& inf) { type = static_cast(unmarshallLong( inf )); turn = unmarshallLong( inf ); packed_place = unmarshallShort( inf ); first = unmarshallLong( inf ); second = unmarshallLong( inf ); unmarshallString4( inf, name ); unmarshallString4( inf, place_abbrev ); unmarshallString4( inf, desc ); } bool notes_active = false; bool notes_are_active() { return (notes_active); } void take_note( const Note& note, bool force ) { if (notes_active && (force || _is_noteworthy(note))) { note_list.push_back( note ); note.check_milestone(); } } void activate_notes( bool active ) { notes_active = active; } void save_notes(writer& outf) { marshallLong( outf, NOTES_VERSION_NUMBER ); marshallLong( outf, note_list.size() ); for (unsigned i = 0; i < note_list.size(); ++i) note_list[i].save(outf); } void load_notes(reader& inf) { if ( unmarshallLong(inf) != NOTES_VERSION_NUMBER ) return; const long num_notes = unmarshallLong(inf); for ( long i = 0; i < num_notes; ++i ) { Note new_note; new_note.load(inf); note_list.push_back(new_note); } } void make_user_note() { mpr("Enter note: ", MSGCH_PROMPT); char buf[400]; bool validline = !cancelable_get_line(buf, sizeof(buf)); if (!validline || (!*buf)) return; Note unote(NOTE_USER_NOTE); unote.name = buf; take_note(unote); }