78f1c8af02fe1b18909275c891c75080e9f0406c
[crawl.git] / crawl-ref / source / notes.cc
1 /**
2  * @file
3  * @brief Notetaking stuff
4 **/
5
6 #include "AppHdr.h"
7
8 #include <vector>
9 #include <sstream>
10 #include <iomanip>
11
12 #include "notes.h"
13
14 #include "branch.h"
15 #include "files.h"
16 #include "kills.h"
17 #include "hiscores.h"
18 #include "message.h"
19 #include "mutation.h"
20 #include "options.h"
21 #include "place.h"
22 #include "religion.h"
23 #include "skills2.h"
24 #include "spl-util.h"
25 #include "state.h"
26 #include "tags.h"
27
28 #define NOTES_VERSION_NUMBER 1002
29
30 std::vector<Note> note_list;
31
32 // return the real number of the power (casting out nonexistent powers),
33 // starting from 0, or -1 if the power doesn't exist
34 static int _real_god_power(int religion, int idx)
35 {
36     if (god_gain_power_messages[religion][idx][0] == 0)
37         return -1;
38
39     int count = 0;
40     for (int j = 0; j < idx; ++j)
41         if (god_gain_power_messages[religion][j][0])
42             ++count;
43
44     return count;
45 }
46
47 static bool _is_noteworthy_skill_level(int level)
48 {
49     for (unsigned int i = 0; i < Options.note_skill_levels.size(); ++i)
50         if (level == Options.note_skill_levels[i])
51             return true;
52
53     return false;
54 }
55
56 static bool _is_highest_skill(int skill)
57 {
58     for (int i = 0; i < NUM_SKILLS; ++i)
59     {
60         if (i == skill)
61             continue;
62         if (you.skills[i] >= you.skills[skill])
63             return false;
64     }
65     return true;
66 }
67
68 static bool _is_noteworthy_hp(int hp, int maxhp)
69 {
70     return (hp > 0 && Options.note_hp_percent
71             && hp <= (maxhp * Options.note_hp_percent) / 100);
72 }
73
74 static int _dungeon_branch_depth(uint8_t branch)
75 {
76     if (branch >= NUM_BRANCHES)
77         return -1;
78     return brdepth[branch];
79 }
80
81 static bool _is_noteworthy_dlevel(unsigned short place)
82 {
83     const uint8_t branch = (place >> 8) & 0xFF;
84     const int lev = (place & 0xFF);
85
86     // The Abyss is noted a different way (since we care mostly about the cause).
87     if (branch == BRANCH_ABYSS)
88         return false;
89
90     // Other portal levels are always interesting.
91     if (!is_connected_branch(static_cast<branch_type>(branch)))
92         return true;
93
94     if (lev == _dungeon_branch_depth(branch)
95         || branch == BRANCH_MAIN_DUNGEON && (lev % 5) == 0
96         || branch == BRANCH_MAIN_DUNGEON && lev == 14
97         || branch != BRANCH_MAIN_DUNGEON && lev == 1)
98     {
99         return true;
100     }
101
102     return false;
103 }
104
105 // Is a note worth taking?
106 // This function assumes that game state has not changed since
107 // the note was taken, e.g. you.* is valid.
108 static bool _is_noteworthy(const Note& note)
109 {
110     // Always noteworthy.
111     if (note.type == NOTE_XP_LEVEL_CHANGE
112         || note.type == NOTE_LEARN_SPELL
113         || note.type == NOTE_GET_GOD
114         || note.type == NOTE_GOD_GIFT
115         || note.type == NOTE_GET_MUTATION
116         || note.type == NOTE_LOSE_MUTATION
117         || note.type == NOTE_GET_ITEM
118         || note.type == NOTE_ID_ITEM
119         || note.type == NOTE_BUY_ITEM
120         || note.type == NOTE_DONATE_MONEY
121         || note.type == NOTE_SEEN_MONSTER
122         || note.type == NOTE_DEFEAT_MONSTER
123         || note.type == NOTE_POLY_MONSTER
124         || note.type == NOTE_USER_NOTE
125         || note.type == NOTE_MESSAGE
126         || note.type == NOTE_LOSE_GOD
127         || note.type == NOTE_PENANCE
128         || note.type == NOTE_MOLLIFY_GOD
129         || note.type == NOTE_DEATH
130         || note.type == NOTE_XOM_REVIVAL
131         || note.type == NOTE_SEEN_FEAT
132         || note.type == NOTE_PARALYSIS
133         || note.type == NOTE_NAMED_ALLY
134         || note.type == NOTE_ALLY_DEATH
135         || note.type == NOTE_FEAT_MIMIC)
136     {
137         return true;
138     }
139
140     // Never noteworthy, hooked up for fun or future use.
141     if (note.type == NOTE_MP_CHANGE
142         || note.type == NOTE_MAXHP_CHANGE
143         || note.type == NOTE_MAXMP_CHANGE)
144     {
145         return false;
146     }
147
148     // Xom effects are only noteworthy if the option is true.
149     if (note.type == NOTE_XOM_EFFECT)
150         return Options.note_xom_effects;
151
152     // God powers might be noteworthy if it's an actual power.
153     if (note.type == NOTE_GOD_POWER
154         && _real_god_power(note.first, note.second) == -1)
155     {
156         return false;
157     }
158
159     // HP noteworthiness is handled in its own function.
160     if (note.type == NOTE_HP_CHANGE
161         && !_is_noteworthy_hp(note.first, note.second))
162     {
163         return false;
164     }
165
166     // Skills are noteworthy if in the skill value list or if
167     // it's a new maximal skill (depending on options).
168     if (note.type == NOTE_GAIN_SKILL || note.type == NOTE_LOSE_SKILL)
169     {
170         if (Options.note_all_skill_levels
171             || _is_noteworthy_skill_level(note.second)
172             || Options.note_skill_max && _is_highest_skill(note.first))
173         {
174             return true;
175         }
176         return false;
177     }
178
179     if (note.type == NOTE_DUNGEON_LEVEL_CHANGE)
180         return _is_noteworthy_dlevel(note.packed_place);
181
182     for (unsigned i = 0; i < note_list.size(); ++i)
183     {
184         if (note_list[i].type != note.type)
185             continue;
186
187         const Note& rnote(note_list[i]);
188         switch (note.type)
189         {
190         case NOTE_GOD_POWER:
191             if (rnote.first == note.first && rnote.second == note.second)
192                 return false;
193             break;
194
195         case NOTE_HP_CHANGE:
196             // Not if we have a recent warning
197             // unless we've lost half our HP since then.
198             if (note.turn - rnote.turn < 5
199                 && note.first * 2 >= rnote.first)
200             {
201                 return false;
202             }
203             break;
204
205         default:
206             mpr("Buggy note passed: unknown note type");
207             // Return now, rather than give a "Buggy note passed" message
208             // for each note of the matching type in the note list.
209             return true;
210         }
211     }
212     return true;
213 }
214
215 static const char* _number_to_ordinal(int number)
216 {
217     const char* ordinals[5] = { "first", "second", "third", "fourth", "fifth" };
218
219     if (number < 1)
220         return "[unknown ordinal (too small)]";
221     if (number > 5)
222         return "[unknown ordinal (too big)]";
223     return ordinals[number-1];
224 }
225
226 std::string Note::describe(bool when, bool where, bool what) const
227 {
228     std::ostringstream result;
229
230     if (when)
231         result << std::setw(6) << turn << " ";
232
233     if (where)
234     {
235         result << "| " << chop_string(short_place_name(packed_place),
236                                       MAX_NOTE_PLACE_LEN) << " | ";
237     }
238
239     if (what)
240     {
241         switch (type)
242         {
243         case NOTE_HP_CHANGE:
244             // [ds] Shortened HP change note from "Had X hitpoints" to
245             // accommodate the cause for the loss of hitpoints.
246             result << "HP: " << first << "/" << second
247                    << " [" << name << "]";
248             break;
249         case NOTE_XOM_REVIVAL:
250             result << "Xom revived you";
251             break;
252         case NOTE_MP_CHANGE:
253             result << "Mana: " << first << "/" << second;
254             break;
255         case NOTE_MAXHP_CHANGE:
256             result << "Reached " << first << " max hit points";
257             break;
258         case NOTE_MAXMP_CHANGE:
259             result << "Reached " << first << " max mana";
260             break;
261         case NOTE_XP_LEVEL_CHANGE:
262             result << "Reached XP level " << first << ". " << name;
263             break;
264         case NOTE_DUNGEON_LEVEL_CHANGE:
265             if (!desc.empty())
266                 result << desc;
267             else
268                 result << "Entered " << place_name(packed_place, true, true);
269             break;
270         case NOTE_LEARN_SPELL:
271             result << "Learned a level "
272                    << spell_difficulty(static_cast<spell_type>(first))
273                    << " spell: "
274                    << spell_title(static_cast<spell_type>(first));
275             break;
276         case NOTE_GET_GOD:
277             result << "Became a worshipper of "
278                    << god_name(static_cast<god_type>(first), true);
279             break;
280         case NOTE_LOSE_GOD:
281             result << "Fell from the grace of "
282                    << god_name(static_cast<god_type>(first));
283             break;
284         case NOTE_PENANCE:
285             result << "Was placed under penance by "
286                    << god_name(static_cast<god_type>(first));
287             break;
288         case NOTE_MOLLIFY_GOD:
289             result << "Was forgiven by "
290                    << god_name(static_cast<god_type>(first));
291             break;
292         case NOTE_GOD_GIFT:
293             result << "Received a gift from "
294                    << god_name(static_cast<god_type>(first));
295             break;
296         case NOTE_ID_ITEM:
297             result << "Identified " << name;
298             if (!desc.empty())
299                 result << " (" << desc << ")";
300             break;
301         case NOTE_GET_ITEM:
302             result << "Got " << name;
303             break;
304         case NOTE_BUY_ITEM:
305             result << "Bought " << name << " for " << first << " gold piece"
306                    << (first == 1 ? "" : "s");
307             break;
308         case NOTE_DONATE_MONEY:
309             result << "Donated " << first << " gold piece"
310                    << (first == 1 ? "" : "s") << " to Zin";
311             break;
312         case NOTE_GAIN_SKILL:
313             result << "Reached skill level " << second
314                    << " in " << skill_name(static_cast<skill_type>(first));
315             break;
316         case NOTE_LOSE_SKILL:
317             result << "Reduced skill "
318                    << skill_name(static_cast<skill_type>(first))
319                    << " to level " << second;
320             break;
321         case NOTE_SEEN_MONSTER:
322             result << "Noticed " << name;
323             break;
324         case NOTE_DEFEAT_MONSTER:
325             if (second)
326                 result << name << " (ally) was " << desc;
327             else
328                 result << uppercase_first(desc) << " " << name;
329             break;
330         case NOTE_POLY_MONSTER:
331             result << name << " changed into " << desc;
332             break;
333         case NOTE_GOD_POWER:
334             result << "Acquired "
335                    << god_name(static_cast<god_type>(first)) << "'s "
336                    << _number_to_ordinal(_real_god_power(first, second)+1)
337                    << " power";
338             break;
339         case NOTE_GET_MUTATION:
340             result << "Gained mutation: "
341                    << mutation_name(static_cast<mutation_type>(first),
342                                     second == 0 ? 1 : second);
343             if (!name.empty())
344                 result << " [" << name << "]";
345             break;
346         case NOTE_LOSE_MUTATION:
347             result << "Lost mutation: "
348                    << mutation_name(static_cast<mutation_type>(first),
349                                     second == 3 ? 3 : second+1);
350             if (!name.empty())
351                 result << " [" << name << "]";
352             break;
353         case NOTE_DEATH:
354             result << name;
355             break;
356         case NOTE_USER_NOTE:
357             result << Options.user_note_prefix << name;
358             break;
359         case NOTE_MESSAGE:
360             result << name;
361             break;
362         case NOTE_SEEN_FEAT:
363             result << "Found " << name;
364             break;
365         case NOTE_FEAT_MIMIC:
366             result << name <<" was a mimic.";
367             break;
368         case NOTE_XOM_EFFECT:
369             result << "XOM: " << name;
370 #if defined(DEBUG_XOM) || defined(NOTE_DEBUG_XOM)
371             // If debugging, also take note of piety and tension.
372             result << " (piety: " << first;
373             if (second >= 0)
374                 result << ", tension: " << second;
375             result << ")";
376 #endif
377             break;
378         case NOTE_PARALYSIS:
379             result << "Paralysed by " << name << " for " << first << " turns";
380             break;
381         case NOTE_NAMED_ALLY:
382             result << "Gained " << name << " as an ally";
383             break;
384         case NOTE_ALLY_DEATH:
385             result << "Your ally " << name << " died";
386             break;
387         default:
388             result << "Buggy note description: unknown note type";
389             break;
390         }
391     }
392
393     if (type == NOTE_SEEN_MONSTER || type == NOTE_DEFEAT_MONSTER)
394     {
395         if (what && first == MONS_PANDEMONIUM_LORD)
396             result << " the pandemonium lord";
397     }
398     return result.str();
399 }
400
401 Note::Note()
402 {
403     turn         = you.num_turns;
404     packed_place = get_packed_place();
405 }
406
407 Note::Note(NOTE_TYPES t, int f, int s, const char* n, const char* d) :
408     type(t), first(f), second(s)
409 {
410     if (n)
411         name = std::string(n);
412     if (d)
413         desc = std::string(d);
414
415     turn         = you.num_turns;
416     packed_place = get_packed_place();
417 }
418
419 void Note::check_milestone() const
420 {
421     if (crawl_state.game_is_arena())
422         return;
423
424     if (type == NOTE_DUNGEON_LEVEL_CHANGE)
425     {
426         const int br = place_branch(packed_place),
427                  dep = place_depth(packed_place);
428
429         // Wizlabs report their milestones on their own.
430         if (br != -1 && br != BRANCH_WIZLAB)
431         {
432             ASSERT(br >= 0 && br < NUM_BRANCHES);
433             std::string branch = place_name(packed_place, true, false).c_str();
434             if (branch.find("The ") == 0)
435                 branch[0] = tolower(branch[0]);
436
437             if (dep == 1)
438             {
439                 mark_milestone(br == BRANCH_ZIGGURAT ? "zig.enter" : "br.enter",
440                                "entered " + branch + ".", true);
441             }
442             else if (dep == _dungeon_branch_depth(br) || dep == 14
443                      || br == BRANCH_ZIGGURAT)
444             {
445                 std::string level = place_name(packed_place, true, true);
446                 if (level.find("Level ") == 0)
447                     level[0] = tolower(level[0]);
448
449                 std::ostringstream branch_finale;
450                 branch_finale << "reached " << level << ".";
451                 mark_milestone(br == BRANCH_ZIGGURAT ? "zig" :
452                                dep == 14 ? "br.mid" : "br.end",
453                                branch_finale.str());
454             }
455         }
456     }
457 }
458
459 void Note::save(writer& outf) const
460 {
461     marshallInt(outf, type);
462     marshallInt(outf, turn);
463     marshallShort(outf, packed_place);
464     marshallInt(outf, first);
465     marshallInt(outf, second);
466     marshallString4(outf, name);
467     marshallString4(outf, desc);
468 }
469
470 void Note::load(reader& inf)
471 {
472     type = static_cast<NOTE_TYPES>(unmarshallInt(inf));
473     turn = unmarshallInt(inf);
474     packed_place = unmarshallShort(inf);
475     first  = unmarshallInt(inf);
476     second = unmarshallInt(inf);
477     unmarshallString4(inf, name);
478     unmarshallString4(inf, desc);
479 }
480
481 static bool notes_active = false;
482
483 bool notes_are_active()
484 {
485     return notes_active;
486 }
487
488 void take_note(const Note& note, bool force)
489 {
490     if (notes_active && (force || _is_noteworthy(note)))
491     {
492         note_list.push_back(note);
493         note.check_milestone();
494     }
495 }
496
497 void activate_notes(bool active)
498 {
499     notes_active = active;
500 }
501
502 void save_notes(writer& outf)
503 {
504     marshallInt(outf, NOTES_VERSION_NUMBER);
505     marshallInt(outf, note_list.size());
506     for (unsigned i = 0; i < note_list.size(); ++i)
507         note_list[i].save(outf);
508 }
509
510 void load_notes(reader& inf)
511 {
512     if (unmarshallInt(inf) != NOTES_VERSION_NUMBER)
513         return;
514
515     const int num_notes = unmarshallInt(inf);
516     for (int i = 0; i < num_notes; ++i)
517     {
518         Note new_note;
519         new_note.load(inf);
520         note_list.push_back(new_note);
521     }
522 }
523
524 void make_user_note()
525 {
526     char buf[400];
527     bool validline = !msgwin_get_line("Enter note: ", buf, sizeof(buf));
528     if (!validline || (!*buf))
529         return;
530     Note unote(NOTE_USER_NOTE);
531     unote.name = buf;
532     take_note(unote);
533 }