Apply the new ground_level method.
[crawl.git] / crawl-ref / source / notes.cc
blob011fe44d7e4a901db50c7525b8f1b3b0c4cfabf4
1 /*
2 * File: notes.cc
3 * Summary: Notetaking stuff
4 * Written by: Haran Pilpel
5 */
7 #include "AppHdr.h"
9 #include <vector>
10 #include <sstream>
11 #include <iomanip>
13 #include "notes.h"
15 #include "branch.h"
16 #include "cio.h"
17 #include "describe.h"
18 #include "files.h"
19 #include "kills.h"
20 #include "hiscores.h"
21 #include "message.h"
22 #include "mutation.h"
23 #include "options.h"
24 #include "place.h"
25 #include "religion.h"
26 #include "skills2.h"
27 #include "spl-util.h"
28 #include "tags.h"
30 #define NOTES_VERSION_NUMBER 1002
32 std::vector<Note> note_list;
34 // return the real number of the power (casting out nonexistent powers),
35 // starting from 0, or -1 if the power doesn't exist
36 static int _real_god_power(int religion, int idx)
38 if (god_gain_power_messages[religion][idx][0] == 0)
39 return -1;
41 int count = 0;
42 for (int j = 0; j < idx; ++j)
43 if (god_gain_power_messages[religion][j][0])
44 ++count;
46 return count;
49 static bool _is_noteworthy_skill_level(int level)
51 for (unsigned int i = 0; i < Options.note_skill_levels.size(); ++i)
52 if (level == Options.note_skill_levels[i])
53 return (true);
55 return (false);
58 static bool _is_highest_skill(int skill)
60 for (int i = 0; i < NUM_SKILLS; ++i)
62 if (i == skill)
63 continue;
64 if (you.skills[i] >= you.skills[skill])
65 return (false);
67 return (true);
70 static bool _is_noteworthy_hp(int hp, int maxhp)
72 return (hp > 0 && Options.note_hp_percent
73 && hp <= (maxhp * Options.note_hp_percent) / 100);
76 static int _dungeon_branch_depth(uint8_t branch)
78 if (branch >= NUM_BRANCHES)
79 return -1;
80 return branches[branch].depth;
83 static bool _is_noteworthy_dlevel(unsigned short place)
85 const uint8_t branch = (place >> 8) & 0xFF;
86 const int lev = (place & 0xFF);
88 // Special levels (Abyss, etc.) are always interesting.
89 if (lev == 0xFF)
90 return (true);
92 if (lev == _dungeon_branch_depth(branch)
93 || branch == BRANCH_MAIN_DUNGEON && (lev % 5) == 0
94 || branch != BRANCH_MAIN_DUNGEON && lev == 1)
96 return (true);
99 return (false);
102 // Is a note worth taking?
103 // This function assumes that game state has not changed since
104 // the note was taken, e.g. you.* is valid.
105 static bool _is_noteworthy(const Note& note)
107 // Always noteworthy.
108 if (note.type == NOTE_XP_LEVEL_CHANGE
109 || note.type == NOTE_GET_GOD
110 || note.type == NOTE_GOD_GIFT
111 || note.type == NOTE_GET_MUTATION
112 || note.type == NOTE_LOSE_MUTATION
113 || note.type == NOTE_GET_ITEM
114 || note.type == NOTE_ID_ITEM
115 || note.type == NOTE_BUY_ITEM
116 || note.type == NOTE_DONATE_MONEY
117 || note.type == NOTE_SEEN_MONSTER
118 || note.type == NOTE_KILL_MONSTER
119 || note.type == NOTE_POLY_MONSTER
120 || note.type == NOTE_USER_NOTE
121 || note.type == NOTE_MESSAGE
122 || note.type == NOTE_LOSE_GOD
123 || note.type == NOTE_PENANCE
124 || note.type == NOTE_MOLLIFY_GOD
125 || note.type == NOTE_DEATH
126 || note.type == NOTE_XOM_REVIVAL
127 || note.type == NOTE_SEEN_FEAT)
129 return (true);
132 // Never noteworthy, hooked up for fun or future use.
133 if (note.type == NOTE_MP_CHANGE
134 || note.type == NOTE_MAXHP_CHANGE
135 || note.type == NOTE_MAXMP_CHANGE)
137 return (false);
140 // Xom effects are only noteworthy if the option is true.
141 if (note.type == NOTE_XOM_EFFECT)
142 return (Options.note_xom_effects);
144 // God powers might be noteworthy if it's an actual power.
145 if (note.type == NOTE_GOD_POWER
146 && _real_god_power(note.first, note.second) == -1)
148 return (false);
151 // HP noteworthiness is handled in its own function.
152 if (note.type == NOTE_HP_CHANGE
153 && !_is_noteworthy_hp(note.first, note.second))
155 return (false);
158 // Skills are noteworthy if in the skill value list or if
159 // it's a new maximal skill (depending on options).
160 if (note.type == NOTE_GAIN_SKILL || note.type == NOTE_LOSE_SKILL)
162 if (Options.note_all_skill_levels
163 || _is_noteworthy_skill_level(note.second)
164 || Options.note_skill_max && _is_highest_skill(note.first))
166 return (true);
168 return (false);
171 if (note.type == NOTE_DUNGEON_LEVEL_CHANGE)
173 if (!_is_noteworthy_dlevel(note.packed_place))
174 return (false);
176 // Labyrinths and portal vaults are always interesting.
177 if ((note.packed_place & 0xFF) == 0xFF
178 && ((note.packed_place >> 8) == LEVEL_LABYRINTH
179 || (note.packed_place >> 8) == LEVEL_PORTAL_VAULT))
181 return (true);
185 // Learning a spell is always noteworthy if note_all_spells is set.
186 if (note.type == NOTE_LEARN_SPELL && Options.note_all_spells)
187 return (true);
189 for (unsigned i = 0; i < note_list.size(); ++i)
191 if (note_list[i].type != note.type)
192 continue;
194 const Note& rnote(note_list[i]);
195 switch (note.type)
197 case NOTE_DUNGEON_LEVEL_CHANGE:
198 if (rnote.packed_place == note.packed_place)
199 return (false);
200 break;
202 case NOTE_LEARN_SPELL:
203 if (spell_difficulty(static_cast<spell_type>(rnote.first))
204 >= spell_difficulty(static_cast<spell_type>(note.first)))
206 return (false);
208 break;
210 case NOTE_GOD_POWER:
211 if (rnote.first == note.first && rnote.second == note.second)
212 return (false);
213 break;
215 case NOTE_HP_CHANGE:
216 // Not if we have a recent warning
217 // unless we've lost half our HP since then.
218 if (note.turn - rnote.turn < 5
219 && note.first * 2 >= rnote.first)
221 return (false);
223 break;
225 default:
226 mpr("Buggy note passed: unknown note type");
227 // Return now, rather than give a "Buggy note passed" message
228 // for each note of the matching type in the note list.
229 return (true);
232 return (true);
235 static const char* _number_to_ordinal(int number)
237 const char* ordinals[5] = { "first", "second", "third", "fourth", "fifth" };
239 if (number < 1)
240 return "[unknown ordinal (too small)]";
241 if (number > 5)
242 return "[unknown ordinal (too big)]";
243 return ordinals[number-1];
246 std::string Note::describe(bool when, bool where, bool what) const
249 std::ostringstream result;
251 if (when)
252 result << std::setw(6) << turn << " ";
254 if (where)
256 if (!place_abbrev.empty())
257 result << "| " << std::setw(MAX_NOTE_PLACE_LEN) << std::left
258 << place_abbrev << " | ";
259 else
260 result << "| " << std::setw(MAX_NOTE_PLACE_LEN) << std::left
261 << short_place_name(packed_place) << " | ";
264 if (what)
266 switch (type)
268 case NOTE_HP_CHANGE:
269 // [ds] Shortened HP change note from "Had X hitpoints" to
270 // accommodate the cause for the loss of hitpoints.
271 result << "HP: " << first << "/" << second
272 << " [" << name << "]";
273 break;
274 case NOTE_XOM_REVIVAL:
275 result << "Xom revived you";
276 break;
277 case NOTE_MP_CHANGE:
278 result << "Mana: " << first << "/" << second;
279 break;
280 case NOTE_MAXHP_CHANGE:
281 result << "Reached " << first << " max hit points";
282 break;
283 case NOTE_MAXMP_CHANGE:
284 result << "Reached " << first << " max mana";
285 break;
286 case NOTE_XP_LEVEL_CHANGE:
287 result << "Reached XP level " << first << ". " << name;
288 break;
289 case NOTE_DUNGEON_LEVEL_CHANGE:
290 if (!desc.empty())
291 result << desc;
292 else
293 result << "Entered " << place_name(packed_place, true, true);
294 break;
295 case NOTE_LEARN_SPELL:
296 result << "Learned a level "
297 << spell_difficulty(static_cast<spell_type>(first))
298 << " spell: "
299 << spell_title(static_cast<spell_type>(first));
300 break;
301 case NOTE_GET_GOD:
302 result << "Became a worshipper of "
303 << god_name(static_cast<god_type>(first), true);
304 break;
305 case NOTE_LOSE_GOD:
306 result << "Fell from the grace of "
307 << god_name(static_cast<god_type>(first));
308 break;
309 case NOTE_PENANCE:
310 result << "Was placed under penance by "
311 << god_name(static_cast<god_type>(first));
312 break;
313 case NOTE_MOLLIFY_GOD:
314 result << "Was forgiven by "
315 << god_name(static_cast<god_type>(first));
316 break;
317 case NOTE_GOD_GIFT:
318 result << "Received a gift from "
319 << god_name(static_cast<god_type>(first));
320 break;
321 case NOTE_ID_ITEM:
322 result << "Identified " << name;
323 if (!desc.empty())
324 result << " (" << desc << ")";
325 break;
326 case NOTE_GET_ITEM:
327 result << "Got " << name;
328 break;
329 case NOTE_BUY_ITEM:
330 result << "Bought " << name << " for " << first << " gold piece"
331 << (first == 1 ? "" : "s");
332 break;
333 case NOTE_DONATE_MONEY:
334 result << "Donated " << first << " gold piece"
335 << (first == 1 ? "" : "s") << " to Zin";
336 break;
337 case NOTE_GAIN_SKILL:
338 result << "Reached skill level " << second
339 << " in " << skill_name(static_cast<skill_type>(first));
340 break;
341 case NOTE_LOSE_SKILL:
342 result << "Reduced skill "
343 << skill_name(static_cast<skill_type>(first))
344 << " to level " << second;
345 break;
346 case NOTE_SEEN_MONSTER:
347 result << "Noticed " << name;
348 break;
349 case NOTE_KILL_MONSTER:
350 if (second)
351 result << name << " (ally) was defeated";
352 else
353 result << "Defeated " << name;
354 break;
355 case NOTE_POLY_MONSTER:
356 result << name << " changed into " << desc;
357 break;
358 case NOTE_GOD_POWER:
359 result << "Acquired "
360 << god_name(static_cast<god_type>(first)) << "'s "
361 << _number_to_ordinal(_real_god_power(first, second)+1)
362 << " power";
363 break;
364 case NOTE_GET_MUTATION:
365 result << "Gained mutation: "
366 << mutation_name(static_cast<mutation_type>(first),
367 second == 0 ? 1 : second);
368 break;
369 case NOTE_LOSE_MUTATION:
370 result << "Lost mutation: "
371 << mutation_name(static_cast<mutation_type>(first),
372 second == 3 ? 3 : second+1);
373 break;
374 case NOTE_DEATH:
375 result << name;
376 break;
377 case NOTE_USER_NOTE:
378 result << Options.user_note_prefix << name;
379 break;
380 case NOTE_MESSAGE:
381 result << name;
382 break;
383 case NOTE_SEEN_FEAT:
384 result << "Found " << name;
385 break;
386 case NOTE_XOM_EFFECT:
387 result << "XOM: " << name;
388 #if defined(DEBUG_XOM) || defined(NOTE_DEBUG_XOM)
389 // If debugging, also take note of piety and tension.
390 result << " (piety: " << first;
391 if (second >= 0)
392 result << ", tension: " << second;
393 result << ")";
394 #endif
395 break;
396 default:
397 result << "Buggy note description: unknown note type";
398 break;
402 if (type == NOTE_SEEN_MONSTER || type == NOTE_KILL_MONSTER)
404 if (what && first == MONS_PANDEMONIUM_DEMON)
405 result << " the pandemonium lord";
407 return result.str();
410 Note::Note()
412 turn = you.num_turns;
413 packed_place = get_packed_place();
415 if (you.level_type == LEVEL_PORTAL_VAULT)
416 place_abbrev = you.level_type_name_abbrev;
419 Note::Note(NOTE_TYPES t, int f, int s, const char* n, const char* d) :
420 type(t), first(f), second(s), place_abbrev("")
422 if (n)
423 name = std::string(n);
424 if (d)
425 desc = std::string(d);
427 turn = you.num_turns;
428 packed_place = get_packed_place();
430 if (you.level_type == LEVEL_PORTAL_VAULT)
431 place_abbrev = you.level_type_name_abbrev;
434 void Note::check_milestone() const
436 if (crawl_state.game_is_arena())
437 return;
439 if (type == NOTE_DUNGEON_LEVEL_CHANGE)
441 const int br = place_branch(packed_place),
442 dep = place_depth(packed_place);
444 if (br != -1)
446 std::string branch = place_name(packed_place, true, false).c_str();
447 if (branch.find("The ") == 0)
448 branch[0] = tolower(branch[0]);
450 if (dep == 1)
451 mark_milestone("br.enter", "entered " + branch + ".", true);
452 else if (dep == _dungeon_branch_depth(br))
454 std::string level = place_name(packed_place, true, true);
455 if (level.find("Level ") == 0)
456 level[0] = tolower(level[0]);
458 std::ostringstream branch_finale;
459 branch_finale << "reached " << level << ".";
460 mark_milestone("br.end", branch_finale.str());
466 void Note::save(writer& outf) const
468 marshallInt(outf, type);
469 marshallInt(outf, turn);
470 marshallShort(outf, packed_place);
471 marshallInt(outf, first);
472 marshallInt(outf, second);
473 marshallString4(outf, name);
474 marshallString4(outf, place_abbrev);
475 marshallString4(outf, desc);
478 void Note::load(reader& inf)
480 type = static_cast<NOTE_TYPES>(unmarshallInt(inf));
481 turn = unmarshallInt(inf);
482 packed_place = unmarshallShort(inf);
483 first = unmarshallInt(inf);
484 second = unmarshallInt(inf);
485 unmarshallString4(inf, name);
486 unmarshallString4(inf, place_abbrev);
487 unmarshallString4(inf, desc);
490 bool notes_active = false;
492 bool notes_are_active()
494 return (notes_active);
497 void take_note(const Note& note, bool force)
499 if (notes_active && (force || _is_noteworthy(note)))
501 note_list.push_back(note);
502 note.check_milestone();
506 void activate_notes(bool active)
508 notes_active = active;
511 void save_notes(writer& outf)
513 marshallInt(outf, NOTES_VERSION_NUMBER);
514 marshallInt(outf, note_list.size());
515 for (unsigned i = 0; i < note_list.size(); ++i)
516 note_list[i].save(outf);
519 void load_notes(reader& inf)
521 if (unmarshallInt(inf) != NOTES_VERSION_NUMBER)
522 return;
524 const int num_notes = unmarshallInt(inf);
525 for (long i = 0; i < num_notes; ++i)
527 Note new_note;
528 new_note.load(inf);
529 note_list.push_back(new_note);
533 void make_user_note()
535 char buf[400];
536 bool validline = !msgwin_get_line("Enter note: ", buf, sizeof(buf));
537 if (!validline || (!*buf))
538 return;
539 Note unote(NOTE_USER_NOTE);
540 unote.name = buf;
541 take_note(unote);