Apply the new ground_level method.
[crawl.git] / crawl-ref / source / message.cc
blobd1d9699a5a251865769919a3d8d5f33b1bb2c69d
1 /*
2 * File: message.cc
3 * Summary: Functions used to print messages.
4 */
6 #include "AppHdr.h"
8 #include "message.h"
10 #include "cio.h"
11 #include "colour.h"
12 #include "delay.h"
13 #include "format.h"
14 #include "initfile.h"
15 #include "libutil.h"
16 #include "macro.h"
17 #include "menu.h"
18 #include "mon-stuff.h"
19 #include "notes.h"
20 #include "options.h"
21 #include "player.h"
22 #include "religion.h"
23 #include "showsymb.h"
24 #include "stash.h"
25 #include "state.h"
26 #include "stuff.h"
27 #include "areas.h"
28 #include "tags.h"
29 #include "tagstring.h"
30 #include "travel.h"
31 #include "hints.h"
32 #include "view.h"
33 #include "shout.h"
34 #include "viewgeom.h"
36 #include <sstream>
38 #ifdef WIZARD
39 #include "luaterp.h"
40 #endif
42 static bool _ends_in_punctuation(const std::string& text)
44 switch (text[text.size() - 1])
46 case '.':
47 case '!':
48 case '?':
49 case ',':
50 case ';':
51 case ':':
52 return true;
53 default:
54 return false;
58 static unsigned int msgwin_line_length();
60 struct message_item
62 msg_channel_type channel; // message channel
63 int param; // param for channel (god, enchantment)
64 std::string text; // text of message (tagged string...)
65 int repeats;
66 long turn;
67 bool join; // may this message be joined with
68 // others?
70 message_item() : channel(NUM_MESSAGE_CHANNELS), param(0),
71 text(""), repeats(0), turn(-1), join(true)
75 message_item(std::string msg, msg_channel_type chan, int par, bool jn)
76 : channel(chan), param(par), text(msg), repeats(1),
77 turn(you.num_turns)
79 // Don't join long messages.
80 join = jn && pure_text().length() < 40;
83 // Constructor for restored messages.
84 message_item(std::string msg, msg_channel_type chan, int par,
85 int rep, int trn)
86 : channel(chan), param(par), text(msg), repeats(rep),
87 turn(trn), join(false)
91 operator bool() const
93 return (repeats > 0);
96 std::string pure_text() const
98 return formatted_string::parse_string(text).tostring();
101 std::string with_repeats() const
103 // TODO: colour the repeats indicator?
104 std::string rep = "";
105 if (repeats > 1)
106 rep = make_stringf(" x%d", repeats);
107 return (text + rep);
110 // Tries to condense the argument into this message.
111 // Either *this needs to be an empty item, or it must be the
112 // same as the argument.
113 bool merge(const message_item& other)
115 if (! *this)
117 *this = other;
118 return true;
121 if (!Options.msg_condense_repeats)
122 return false;
123 if (other.channel == channel && other.param == param)
125 if (Options.msg_condense_repeats && other.text == text)
127 repeats += other.repeats;
128 return true;
130 else if (Options.msg_condense_short
131 && turn == other.turn
132 && repeats == 1 && other.repeats == 1
133 && join && other.join
134 && _ends_in_punctuation(pure_text())
135 == _ends_in_punctuation(other.pure_text()))
137 // Note that join stays true.
139 std::string sep = "<lightgrey>";
140 int seplen = 1;
141 if (!_ends_in_punctuation(pure_text()))
143 sep += ";";
144 seplen++;
146 sep += " </lightgrey>";
147 if (pure_text().length() + seplen + other.pure_text().length()
148 > msgwin_line_length())
150 return false;
153 text += sep;
154 text += other.text;
155 return true;
158 return false;
162 static int _mod(int num, int denom)
164 ASSERT(denom > 0);
165 div_t res = div(num, denom);
166 return (res.rem >= 0 ? res.rem : res.rem + denom);
169 template <typename T, int SIZE>
170 class circ_vec
172 T data[SIZE];
174 int end; // first unfilled index
176 static void inc(int* index)
178 ASSERT(*index >= 0 && *index < SIZE);
179 *index = _mod(*index + 1, SIZE);
182 static void dec(int* index)
184 ASSERT(*index >= 0 && *index < SIZE);
185 *index = _mod(*index - 1, SIZE);
188 public:
189 circ_vec() : end(0) {}
191 void clear()
193 end = 0;
194 for (int i = 0; i < SIZE; ++i)
195 data[i] = T();
198 int size() const
200 return SIZE;
203 T& operator[](int i)
205 ASSERT(_mod(i, SIZE) < size());
206 return data[_mod(end + i, SIZE)];
209 const T& operator[](int i) const
211 ASSERT(_mod(i, SIZE) < size());
212 return data[_mod(end + i, SIZE)];
215 void push_back(const T& item)
217 data[end] = item;
218 inc(&end);
221 void roll_back(int n)
223 for (int i = 0; i < n; ++i)
225 dec(&end);
226 data[end] = T();
231 static void readkey_more(bool user_forced=false);
233 // Types of message prefixes.
234 // Higher values override lower.
235 enum prefix_type
237 P_NONE,
238 P_TURN_START,
239 P_TURN_END,
240 P_NEW_CMD, // new command, but no new turn
241 P_NEW_TURN,
242 P_FULL_MORE, // single-character more prompt (full window)
243 P_OTHER_MORE, // the other type of --more-- prompt
246 // Could also go with coloured glyphs.
247 glyph prefix_glyph(prefix_type p)
249 glyph g;
250 switch (p)
252 case P_TURN_START:
253 g.ch = '-';
254 g.col = LIGHTGRAY;
255 break;
256 case P_TURN_END:
257 case P_NEW_TURN:
258 g.ch = '_';
259 g.col = LIGHTGRAY;
260 break;
261 case P_NEW_CMD:
262 g.ch = '_';
263 g.col = DARKGRAY;
264 break;
265 case P_FULL_MORE:
266 g.ch = '+';
267 g.col = channel_to_colour(MSGCH_PROMPT);
268 break;
269 case P_OTHER_MORE:
270 g.ch = '+';
271 g.col = LIGHTRED;
272 break;
273 default:
274 g.ch = ' ';
275 g.col = LIGHTGRAY;
276 break;
278 return (g);
281 static bool _pre_more();
283 static bool _temporary = false;
285 class message_window
287 int next_line;
288 int temp_line; // starting point of temporary messages
289 int input_line; // last line-after-input
290 std::vector<formatted_string> lines;
291 prefix_type prompt; // current prefix prompt
293 int height() const
295 return crawl_view.msgsz.y;
298 int use_last_line() const
300 return (first_col_more());
303 int width() const
305 return crawl_view.msgsz.x;
308 void out_line(const formatted_string& line, int n) const
310 cgotoxy(1, n + 1, GOTO_MSG);
311 line.display();
312 cprintf("%*s", width() - line.length(), "");
315 // Place cursor at end of last non-empty line to handle prompts.
316 // TODO: might get rid of this by clearing the whole window when writing,
317 // and then just writing the actual non-empty lines.
318 void place_cursor() const
320 int i;
321 for (i = lines.size() - 1; i >= 0 && lines[i].length() == 0; --i);
322 if (i >= 0 && (int) lines[i].length() < crawl_view.msgsz.x)
323 cgotoxy(lines[i].length() + 1, i + 1, GOTO_MSG);
326 // Whether to show msgwin-full more prompts.
327 bool more_enabled() const
329 return (crawl_state.show_more_prompt
330 && (Options.clear_messages || Options.show_more));
333 int make_space(int n)
335 int space = out_height() - next_line;
337 if (space >= n)
338 return 0;
340 int s = 0;
341 if (input_line > 0)
343 s = std::min(input_line, n - space);
344 scroll(s);
345 space += s;
348 if (space >= n)
349 return s;
351 if (more_enabled())
352 more(true);
354 // We could consider just scrolling off after --more--;
355 // that would require marking the last message before
356 // the prompt.
357 if (!Options.clear_messages && !more_enabled())
359 scroll(n - space);
360 return (s + n - space);
362 else
364 clear();
365 return (height());
369 void add_line(const formatted_string& line)
371 resize(); // TODO: get rid of this
372 lines[next_line] = line;
373 next_line++;
376 void output_prefix(prefix_type p)
378 if (!use_first_col())
379 return;
380 if (p <= prompt)
381 return;
382 prompt = p;
383 if (next_line > 0)
385 formatted_string line;
386 line.add_glyph(prefix_glyph(prompt));
387 line += lines[next_line-1].substr(1);
388 lines[next_line-1] = line;
390 show();
393 public:
394 message_window()
395 : next_line(0), temp_line(0), input_line(0), prompt(P_NONE)
397 clear_lines(); // initialize this->lines
400 void resize()
402 // XXX: broken (why?)
403 lines.resize(height());
406 unsigned int out_width() const
408 return (width() - (use_first_col() ? 1 : 0));
411 unsigned int out_height() const
413 return (height() - (use_last_line() ? 0 : 1));
416 void clear_lines()
418 lines.clear();
419 lines.resize(height());
422 bool first_col_more() const
424 return (use_first_col() && Options.small_more);
427 bool use_first_col() const
429 return (!Options.clear_messages);
432 void set_starting_line()
434 // TODO: start at end (sometimes?)
435 next_line = 0;
436 input_line = 0;
437 temp_line = 0;
440 void clear()
442 clear_lines();
443 set_starting_line();
444 show();
447 void scroll(int n)
449 ASSERT(next_line >= n);
450 int i;
451 for (i = 0; i < height() - n; ++i)
452 lines[i] = lines[i + n];
453 for (; i < height(); ++i)
454 lines[i].clear();
455 next_line -= n;
456 temp_line -= n;
457 input_line -= n;
460 // write to screen (without refresh)
461 void show() const
463 // XXX: this should not be necessary as formatted_string should
464 // already do it
465 textcolor(LIGHTGREY);
466 for (size_t i = 0; i < lines.size(); ++i)
467 out_line(lines[i], i);
468 place_cursor();
469 #ifdef USE_TILE
470 tiles.set_need_redraw();
471 #endif
474 // temporary: to be overwritten with next item, e.g. new turn
475 // leading dash or prompt without response
476 void add_item(std::string text, prefix_type first_col = P_NONE,
477 bool temporary = false)
479 prompt = P_NONE; // reset prompt
481 std::vector<formatted_string> newlines;
482 linebreak_string2(text, out_width());
483 formatted_string::parse_string_to_multiple(text, newlines);
485 for (size_t i = 0; i < newlines.size(); ++i)
487 temp_line -= make_space(1);
488 formatted_string line;
489 if (use_first_col())
490 line.add_glyph(prefix_glyph(first_col));
491 line += newlines[i];
492 add_line(line);
495 if (!temporary)
496 reset_temp();
498 show();
501 void roll_back()
503 temp_line = std::max(temp_line, 0);
504 for (int i = temp_line; i < next_line; ++i)
505 lines[i].clear();
506 next_line = temp_line;
509 void reset_temp()
511 temp_line = next_line;
514 void got_input()
516 input_line = next_line;
519 void new_cmdturn(bool new_turn)
521 output_prefix(new_turn ? P_NEW_TURN : P_NEW_CMD);
524 bool any_messages()
526 return (next_line > input_line);
530 * Handling of more prompts (both types).
532 void more(bool full, bool user=false)
534 if (_pre_more())
535 return;
537 show();
538 int last_row = crawl_view.msgsz.y;
539 if (first_col_more())
541 cgotoxy(1, last_row, GOTO_MSG);
542 glyph g = prefix_glyph(full ? P_FULL_MORE : P_OTHER_MORE);
543 formatted_string f;
544 f.add_glyph(g);
545 f.display();
546 // Move cursor back for nicer display.
547 cgotoxy(1, last_row, GOTO_MSG);
548 // Need to read_key while cursor_control in scope.
549 cursor_control con(true);
550 readkey_more();
552 else
554 cgotoxy(use_first_col() ? 2 : 1, last_row, GOTO_MSG);
555 textcolor(channel_to_colour(MSGCH_PROMPT));
556 if (crawl_state.game_is_hints())
558 std::string more_str = "--more-- Press Space ";
559 #ifdef USE_TILE
560 more_str += "or click ";
561 #endif
562 more_str += "to continue. You can later reread messages with "
563 "Ctrl-P.";
564 cprintf(more_str.c_str());
566 else
567 cprintf("--more--");
569 readkey_more(user);
574 message_window msgwin;
576 void display_message_window()
578 msgwin.show();
581 void clear_message_window()
583 msgwin = message_window();
586 void scroll_message_window(int n)
588 msgwin.scroll(n);
589 msgwin.show();
592 bool any_messages()
594 return msgwin.any_messages();
597 typedef circ_vec<message_item, NUM_STORED_MESSAGES> store_t;
599 class message_store
601 store_t msgs;
602 message_item prev_msg;
603 bool last_of_turn;
604 int temp; // number of temporary messages
606 public:
607 message_store() : last_of_turn(false), temp(0) {}
609 void add(const message_item& msg)
611 if (msg.channel != MSGCH_PROMPT && prev_msg.merge(msg))
612 return;
613 flush_prev();
614 prev_msg = msg;
615 if (msg.channel == MSGCH_PROMPT || _temporary)
616 flush_prev();
619 bool have_prev()
621 return (prev_msg);
624 void store_msg(const message_item& msg)
626 prefix_type p = P_NONE;
627 msgs.push_back(msg);
628 if (_temporary)
629 temp++;
630 else
631 reset_temp();
632 msgwin.add_item(msg.with_repeats(), p, _temporary);
635 void roll_back()
637 msgs.roll_back(temp);
638 temp = 0;
641 void reset_temp()
643 temp = 0;
646 void flush_prev()
648 if (!prev_msg)
649 return;
650 message_item msg = prev_msg;
651 // Clear prev_msg before storing it, since
652 // writing out to the message window might
653 // in turn result in a recursive flush_prev.
654 prev_msg = message_item();
655 store_msg(msg);
656 if (last_of_turn)
658 msgwin.new_cmdturn(true);
659 last_of_turn = false;
663 void new_turn()
665 if (prev_msg)
666 last_of_turn = true;
667 else
668 msgwin.new_cmdturn(true);
671 // XXX: this should not need to exist
672 const store_t& get_store()
674 return msgs;
677 void clear()
679 msgs.clear();
680 prev_msg = message_item();
681 last_of_turn = false;
682 temp = 0;
686 // Circular buffer for keeping past messages.
687 message_store messages;
689 static FILE* _msg_dump_file = NULL;
691 static bool suppress_messages = false;
692 static msg_colour_type prepare_message(const std::string& imsg,
693 msg_channel_type channel,
694 int param);
696 no_messages::no_messages() : msuppressed(suppress_messages)
698 suppress_messages = true;
701 no_messages::~no_messages()
703 suppress_messages = msuppressed;
706 msg_colour_type msg_colour(int col)
708 return static_cast<msg_colour_type>(col);
711 static int colour_msg(msg_colour_type col)
713 if (col == MSGCOL_MUTED)
714 return (DARKGREY);
715 else
716 return static_cast<int>(col);
719 // Returns a colour or MSGCOL_MUTED.
720 static msg_colour_type channel_to_msgcol(msg_channel_type channel, int param)
722 if (you.asleep())
723 return (MSGCOL_DARKGREY);
725 msg_colour_type ret;
727 switch (Options.channels[channel])
729 case MSGCOL_PLAIN:
730 // Note that if the plain channel is muted, then we're protecting
731 // the player from having that spread to other channels here.
732 // The intent of plain is to give non-coloured messages, not to
733 // suppress them.
734 if (Options.channels[MSGCH_PLAIN] >= MSGCOL_DEFAULT)
735 ret = MSGCOL_LIGHTGREY;
736 else
737 ret = Options.channels[MSGCH_PLAIN];
738 break;
740 case MSGCOL_DEFAULT:
741 case MSGCOL_ALTERNATE:
742 switch (channel)
744 case MSGCH_GOD:
745 case MSGCH_PRAY:
746 ret = (Options.channels[channel] == MSGCOL_DEFAULT)
747 ? msg_colour(god_colour(static_cast<god_type>(param)))
748 : msg_colour(god_message_altar_colour(static_cast<god_type>(param)));
749 break;
751 case MSGCH_DURATION:
752 ret = MSGCOL_LIGHTBLUE;
753 break;
755 case MSGCH_DANGER:
756 ret = MSGCOL_RED;
757 break;
759 case MSGCH_WARN:
760 case MSGCH_ERROR:
761 ret = MSGCOL_LIGHTRED;
762 break;
764 case MSGCH_FOOD:
765 if (param) // positive change
766 ret = MSGCOL_GREEN;
767 else
768 ret = MSGCOL_YELLOW;
769 break;
771 case MSGCH_INTRINSIC_GAIN:
772 ret = MSGCOL_GREEN;
773 break;
775 case MSGCH_RECOVERY:
776 ret = MSGCOL_LIGHTGREEN;
777 break;
779 case MSGCH_TALK:
780 case MSGCH_TALK_VISUAL:
781 ret = MSGCOL_WHITE;
782 break;
784 case MSGCH_MUTATION:
785 ret = MSGCOL_LIGHTRED;
786 break;
788 case MSGCH_MONSTER_SPELL:
789 case MSGCH_MONSTER_ENCHANT:
790 case MSGCH_FRIEND_SPELL:
791 case MSGCH_FRIEND_ENCHANT:
792 ret = MSGCOL_LIGHTMAGENTA;
793 break;
795 case MSGCH_TUTORIAL:
796 case MSGCH_ORB:
797 case MSGCH_BANISHMENT:
798 ret = MSGCOL_MAGENTA;
799 break;
801 case MSGCH_MONSTER_DAMAGE:
802 ret = ((param == MDAM_DEAD) ? MSGCOL_RED :
803 (param >= MDAM_SEVERELY_DAMAGED) ? MSGCOL_LIGHTRED :
804 (param >= MDAM_MODERATELY_DAMAGED) ? MSGCOL_YELLOW
805 : MSGCOL_LIGHTGREY);
806 break;
808 case MSGCH_PROMPT:
809 ret = MSGCOL_CYAN;
810 break;
812 case MSGCH_DIAGNOSTICS:
813 case MSGCH_MULTITURN_ACTION:
814 ret = MSGCOL_DARKGREY; // makes it easier to ignore at times -- bwr
815 break;
817 case MSGCH_PLAIN:
818 case MSGCH_FRIEND_ACTION:
819 case MSGCH_ROTTEN_MEAT:
820 case MSGCH_EQUIPMENT:
821 case MSGCH_EXAMINE:
822 case MSGCH_EXAMINE_FILTER:
823 default:
824 ret = param > 0 ? msg_colour(param) : MSGCOL_LIGHTGREY;
825 break;
827 break;
829 case MSGCOL_MUTED:
830 ret = MSGCOL_MUTED;
831 break;
833 default:
834 // Setting to a specific colour is handled here, special
835 // cases should be handled above.
836 if (channel == MSGCH_MONSTER_DAMAGE)
838 // A special case right now for monster damage (at least until
839 // the init system is improved)... selecting a specific
840 // colour here will result in only the death messages coloured.
841 if (param == MDAM_DEAD)
842 ret = Options.channels[channel];
843 else if (Options.channels[MSGCH_PLAIN] >= MSGCOL_DEFAULT)
844 ret = MSGCOL_LIGHTGREY;
845 else
846 ret = Options.channels[MSGCH_PLAIN];
848 else
849 ret = Options.channels[channel];
850 break;
853 return (ret);
856 int channel_to_colour(msg_channel_type channel, int param)
858 return colour_msg(channel_to_msgcol(channel, param));
861 static void do_message_print(msg_channel_type channel, int param,
862 const char *format, va_list argp)
864 va_list ap;
865 va_copy(ap, argp);
866 char buff[200];
867 size_t len = vsnprintf(buff, sizeof(buff), format, argp);
868 if (len < sizeof(buff))
870 mpr(buff, channel, param);
872 else
874 char *heapbuf = (char*)malloc(len + 1);
875 vsnprintf(heapbuf, len + 1, format, ap);
876 mpr(heapbuf, channel, param);
877 free(heapbuf);
879 va_end(ap);
882 void mprf(msg_channel_type channel, int param, const char *format, ...)
884 va_list argp;
885 va_start(argp, format);
886 do_message_print(channel, param, format, argp);
887 va_end(argp);
890 void mprf(msg_channel_type channel, const char *format, ...)
892 va_list argp;
893 va_start(argp, format);
894 do_message_print(channel, channel == MSGCH_GOD ? you.religion : 0,
895 format, argp);
896 va_end(argp);
899 void mprf(const char *format, ...)
901 va_list argp;
902 va_start(argp, format);
903 do_message_print(MSGCH_PLAIN, 0, format, argp);
904 va_end(argp);
907 #ifdef DEBUG_DIAGNOSTICS
908 void dprf(const char *format, ...)
910 va_list argp;
911 va_start(argp, format);
912 do_message_print(MSGCH_DIAGNOSTICS, 0, format, argp);
913 va_end(argp);
915 #endif
917 static bool _updating_view = false;
919 static bool check_more(const std::string& line, msg_channel_type channel)
921 for (unsigned i = 0; i < Options.force_more_message.size(); ++i)
922 if (Options.force_more_message[i].is_filtered(channel, line))
923 return true;
924 return false;
927 static bool check_join(const std::string& line, msg_channel_type channel)
929 switch (channel)
931 case MSGCH_EQUIPMENT:
932 return false;
933 default:
934 break;
936 return true;
939 static void debug_channel_arena(msg_channel_type channel)
941 switch (channel)
943 case MSGCH_PROMPT:
944 case MSGCH_GOD:
945 case MSGCH_PRAY:
946 case MSGCH_DURATION:
947 case MSGCH_FOOD:
948 case MSGCH_RECOVERY:
949 case MSGCH_INTRINSIC_GAIN:
950 case MSGCH_MUTATION:
951 case MSGCH_ROTTEN_MEAT:
952 case MSGCH_EQUIPMENT:
953 case MSGCH_FLOOR_ITEMS:
954 case MSGCH_MULTITURN_ACTION:
955 case MSGCH_EXAMINE:
956 case MSGCH_EXAMINE_FILTER:
957 case MSGCH_ORB:
958 case MSGCH_TUTORIAL:
959 die("Invalid channel '%s' in arena mode",
960 channel_to_str(channel).c_str());
961 break;
962 default:
963 break;
967 void msgwin_set_temporary(bool temp)
969 flush_prev_message();
970 _temporary = temp;
971 if (!temp)
973 messages.reset_temp();
974 msgwin.reset_temp();
978 void msgwin_clear_temporary()
980 messages.roll_back();
981 msgwin.roll_back();
984 static long _last_msg_turn = -1; // Turn of last message.
986 void mpr(std::string text, msg_channel_type channel, int param, bool nojoin)
988 if (_msg_dump_file != NULL)
989 fprintf(_msg_dump_file, "%s\n", text.c_str());
991 if (crawl_state.game_crashed)
992 return;
994 if (crawl_state.game_is_arena())
995 debug_channel_arena(channel);
997 if (!crawl_state.io_inited)
999 if (channel == MSGCH_ERROR)
1000 fprintf(stderr, "%s\n", text.c_str());
1001 return;
1004 // Flush out any "comes into view" monster announcements before the
1005 // monster has a chance to give any other messages.
1006 if (!_updating_view)
1008 _updating_view = true;
1009 flush_comes_into_view();
1010 _updating_view = false;
1013 if (channel == MSGCH_GOD && param == 0)
1014 param = you.religion;
1016 msg_colour_type colour = prepare_message(text, channel, param);
1018 if (colour == MSGCOL_MUTED)
1019 return;
1021 bool domore = check_more(text, channel);
1022 bool join = !domore && !nojoin && check_join(text, channel);
1024 if (you.duration[DUR_QUAD_DAMAGE])
1026 // No sound, so we simulate the reverb with all caps.
1027 formatted_string fs = formatted_string::parse_string(text);
1028 fs.all_caps();
1029 text = fs.to_colour_string();
1032 std::string col = colour_to_str(colour_msg(colour));
1033 text = "<" + col + ">" + text + "</" + col + ">"; // XXX
1034 message_item msg = message_item(text, channel, param, join);
1035 messages.add(msg);
1036 _last_msg_turn = msg.turn;
1038 if (channel == MSGCH_ERROR)
1039 interrupt_activity(AI_FORCE_INTERRUPT);
1041 if (channel == MSGCH_PROMPT || channel == MSGCH_ERROR)
1042 set_more_autoclear(false);
1044 if (domore)
1045 more(true);
1048 static std::string show_prompt(std::string prompt)
1050 mpr(prompt, MSGCH_PROMPT);
1052 // FIXME: duplicating mpr code.
1053 msg_colour_type colour = prepare_message(prompt, MSGCH_PROMPT, 0);
1054 return colour_string(prompt, colour_msg(colour));
1057 static std::string _prompt;
1058 void msgwin_prompt(std::string prompt)
1060 msgwin_set_temporary(true);
1061 _prompt = show_prompt(prompt);
1064 void msgwin_reply(std::string reply)
1066 msgwin_clear_temporary();
1067 msgwin_set_temporary(false);
1068 reply = replace_all(reply, "<", "<<");
1069 mpr(_prompt + "<lightgrey>" + reply + "</lightgrey>", MSGCH_PROMPT);
1070 msgwin.got_input();
1073 void msgwin_got_input()
1075 msgwin.got_input();
1078 int msgwin_get_line(std::string prompt, char *buf, int len,
1079 input_history *mh, int (*keyproc)(int& c))
1081 if (prompt != "")
1082 msgwin_prompt(prompt);
1084 int ret = cancelable_get_line(buf, len, mh, keyproc);
1085 msgwin_reply(buf);
1086 return ret;
1089 void msgwin_new_turn()
1091 messages.new_turn();
1094 void msgwin_new_cmd()
1096 flush_prev_message();
1097 bool new_turn = (you.num_turns > _last_msg_turn);
1098 msgwin.new_cmdturn(new_turn);
1101 static unsigned int msgwin_line_length()
1103 return msgwin.out_width();
1106 unsigned int msgwin_lines()
1108 return msgwin.out_height();
1111 // mpr() an arbitrarily long list of strings without truncation or risk
1112 // of overflow.
1113 void mpr_comma_separated_list(const std::string prefix,
1114 const std::vector<std::string> list,
1115 const std::string &andc,
1116 const std::string &comma,
1117 const msg_channel_type channel,
1118 const int param)
1120 std::string out = prefix;
1122 for (int i = 0, size = list.size(); i < size; i++)
1124 out += list[i];
1126 if (size > 0 && i < (size - 2))
1127 out += comma;
1128 else if (i == (size - 2))
1129 out += andc;
1130 else if (i == (size - 1))
1131 out += ".";
1133 mpr(out, channel, param);
1137 // Checks whether a given message contains patterns relevant for
1138 // notes, stop_running or sounds and handles these cases.
1139 static void mpr_check_patterns(const std::string& message,
1140 msg_channel_type channel,
1141 int param)
1143 for (unsigned i = 0; i < Options.note_messages.size(); ++i)
1145 if (channel == MSGCH_EQUIPMENT || channel == MSGCH_FLOOR_ITEMS
1146 || channel == MSGCH_MULTITURN_ACTION
1147 || channel == MSGCH_EXAMINE || channel == MSGCH_EXAMINE_FILTER
1148 || channel == MSGCH_TUTORIAL)
1150 continue;
1153 if (Options.note_messages[i].matches(message))
1155 take_note(Note(NOTE_MESSAGE, channel, param, message.c_str()));
1156 break;
1160 if (channel != MSGCH_DIAGNOSTICS && channel != MSGCH_EQUIPMENT)
1161 interrupt_activity(AI_MESSAGE, channel_to_str(channel) + ":" + message);
1163 // Any sound has a chance of waking the PC if the PC is asleep.
1164 if (channel == MSGCH_SOUND)
1165 you.check_awaken(5);
1167 if (!Options.sound_mappings.empty())
1168 for (unsigned i = 0; i < Options.sound_mappings.size(); i++)
1170 // Maybe we should allow message channel matching as for
1171 // force_more_message?
1172 if (Options.sound_mappings[i].pattern.matches(message))
1174 play_sound(Options.sound_mappings[i].soundfile.c_str());
1175 break;
1180 static bool channel_message_history(msg_channel_type channel)
1182 switch (channel)
1184 case MSGCH_PROMPT:
1185 case MSGCH_EQUIPMENT:
1186 case MSGCH_EXAMINE_FILTER:
1187 return (false);
1188 default:
1189 return (true);
1193 // Returns the default colour of the message, or MSGCOL_MUTED if
1194 // the message should be suppressed.
1195 static msg_colour_type prepare_message(const std::string& imsg,
1196 msg_channel_type channel,
1197 int param)
1199 if (suppress_messages)
1200 return MSGCOL_MUTED;
1202 if (silenced(you.pos())
1203 && (channel == MSGCH_SOUND || channel == MSGCH_TALK))
1205 return MSGCOL_MUTED;
1208 msg_colour_type colour = channel_to_msgcol(channel, param);
1210 if (colour != MSGCOL_MUTED)
1211 mpr_check_patterns(imsg, channel, param);
1213 const std::vector<message_colour_mapping>& mcm
1214 = Options.message_colour_mappings;
1215 typedef std::vector<message_colour_mapping>::const_iterator mcmci;
1217 for (mcmci ci = mcm.begin(); ci != mcm.end(); ++ci)
1219 if (ci->message.is_filtered(channel, imsg))
1221 colour = ci->colour;
1222 break;
1226 return colour;
1229 void flush_prev_message()
1231 messages.flush_prev();
1234 void mesclr(bool force)
1236 if (!crawl_state.io_inited)
1237 return;
1238 // Unflushed message will be lost with clear_messages,
1239 // so they shouldn't really exist, but some of the delay
1240 // code appears to do this intentionally.
1241 // ASSERT(!messages.have_prev());
1242 flush_prev_message();
1244 msgwin.got_input(); // Consider old messages as read.
1246 if (Options.clear_messages || force)
1247 msgwin.clear();
1249 // TODO: we could indicate indicate mesclr with a different
1250 // leading character than '-'.
1253 static bool autoclear_more = false;
1255 void set_more_autoclear(bool on)
1257 autoclear_more = on;
1260 static void readkey_more(bool user_forced)
1262 if (autoclear_more)
1263 return;
1264 int keypress;
1265 mouse_control mc(MOUSE_MODE_MORE);
1267 keypress = getch_ck();
1268 while (keypress != ' ' && keypress != '\r' && keypress != '\n'
1269 && !key_is_escape(keypress)
1270 && (user_forced || keypress != CK_MOUSE_CLICK));
1272 if (key_is_escape(keypress))
1273 set_more_autoclear(true);
1277 * more() preprocessing.
1279 * @return Whether the more prompt should be skipped.
1281 static bool _pre_more()
1283 if (crawl_state.game_crashed || crawl_state.seen_hups)
1284 return true;
1286 #ifdef DEBUG_DIAGNOSTICS
1287 if (you.running)
1288 return true;
1289 #endif
1291 if (crawl_state.game_is_arena())
1293 delay(Options.arena_delay);
1294 return true;
1297 if (crawl_state.is_replaying_keys())
1298 return true;
1300 #ifdef WIZARD
1301 if (luaterp_running())
1302 return true;
1303 #endif
1305 if (!crawl_state.show_more_prompt || suppress_messages)
1306 return true;
1308 return false;
1311 void more(bool user_forced)
1313 if (!crawl_state.io_inited)
1314 return;
1315 flush_prev_message();
1316 msgwin.more(false, user_forced);
1317 mesclr();
1320 static bool is_channel_dumpworthy(msg_channel_type channel)
1322 return (channel != MSGCH_EQUIPMENT
1323 && channel != MSGCH_DIAGNOSTICS
1324 && channel != MSGCH_TUTORIAL);
1327 void clear_message_store()
1329 messages.clear();
1332 std::string get_last_messages(int mcount)
1334 flush_prev_message();
1336 std::string text;
1337 // XXX: should use some message_history iterator here
1338 const store_t& msgs = messages.get_store();
1339 // XXX: loop wraps around otherwise. This could be done better.
1340 mcount = std::min(mcount, NUM_STORED_MESSAGES);
1341 for (int i = -1; mcount > 0; --i)
1343 const message_item msg = msgs[i];
1344 if (!msg)
1345 break;
1346 if (is_channel_dumpworthy(msg.channel))
1348 text = msg.pure_text() + "\n" + text;
1349 mcount--;
1353 // An extra line of clearance.
1354 if (!text.empty())
1355 text += "\n";
1356 return text;
1359 // We just write out the whole message store including empty/unused
1360 // messages. They'll be ignored when restoring.
1361 void save_messages(writer& outf)
1363 store_t msgs = messages.get_store();
1364 marshallInt(outf, msgs.size());
1365 for (int i = 0; i < msgs.size(); ++i)
1367 marshallString4(outf, msgs[i].text);
1368 marshallInt(outf, msgs[i].channel);
1369 marshallInt(outf, msgs[i].param);
1370 marshallInt(outf, msgs[i].repeats);
1371 marshallInt(outf, msgs[i].turn);
1375 void load_messages(reader& inf)
1377 unwind_var<bool> save_more(crawl_state.show_more_prompt, false);
1379 int num = unmarshallInt(inf);
1380 for (int i = 0; i < num; ++i)
1382 std::string text;
1383 unmarshallString4(inf, text);
1385 msg_channel_type channel = (msg_channel_type) unmarshallInt(inf);
1386 int param = unmarshallInt(inf);
1387 int repeats = unmarshallInt(inf);
1388 int turn = unmarshallInt(inf);
1390 message_item msg(message_item(text, channel, param, repeats, turn));
1391 if (msg)
1392 messages.store_msg(msg);
1394 // With Options.message_clear, we don't want the message window
1395 // pre-filled.
1396 mesclr();
1399 void replay_messages(void)
1401 formatted_scroller hist(MF_START_AT_END | MF_ALWAYS_SHOW_MORE, "");
1402 hist.set_more(formatted_string::parse_string(
1403 "<cyan>[up/<< : Page up. down/Space/> : Page down."
1404 " Esc exits.]</cyan>"));
1406 const store_t msgs = messages.get_store();
1407 for (int i = 0; i < msgs.size(); ++i)
1408 if (channel_message_history(msgs[i].channel))
1410 std::string text = msgs[i].with_repeats();
1411 linebreak_string2(text, cgetsize(GOTO_CRT).x - 1);
1412 std::vector<formatted_string> parts;
1413 formatted_string::parse_string_to_multiple(text, parts);
1414 for (unsigned int j = 0; j < parts.size(); ++j)
1416 formatted_string line;
1417 prefix_type p = P_NONE;
1418 if (j == parts.size() - 1 && i + 1 < msgs.size()
1419 && msgs[i+1].turn > msgs[i].turn)
1421 p = P_TURN_END;
1423 line.add_glyph(prefix_glyph(p));
1424 line += parts[j];
1425 hist.add_item_formatted_string(line);
1429 hist.show();
1432 void set_msg_dump_file(FILE* file)
1434 _msg_dump_file = file;
1438 void formatted_mpr(const formatted_string& fs,
1439 msg_channel_type channel, int param)
1441 mpr(fs.to_colour_string(), channel, param);