OSDN Git Service

[Refactor] mind_mirror_master をパスカルケース化
[hengbandforosx/hengbandosx.git] / src / view / display-messages.cpp
1 #include "view/display-messages.h"
2 #include "core/window-redrawer.h"
3 #include "game-option/cheat-options.h"
4 #include "game-option/input-options.h"
5 #include "game-option/map-screen-options.h"
6 #include "game-option/option-flags.h"
7 #include "io/input-key-acceptor.h"
8 #include "main/sound-of-music.h"
9 #include "system/player-type-definition.h"
10 #include "term/gameterm.h"
11 #include "term/term-color-types.h"
12 #include "util/int-char-converter.h"
13 #include "world/world.h"
14
15 #include <deque>
16 #include <map>
17 #include <memory>
18 #include <string>
19
20 /* Used in msg_print() for "buffering" */
21 bool msg_flag;
22
23 COMMAND_CODE now_message;
24
25 namespace {
26
27 /*! 表示するメッセージの先頭位置 */
28 static int msg_head_pos = 0;
29
30 using msg_sp = std::shared_ptr<const std::string>;
31 using msg_wp = std::weak_ptr<const std::string>;
32
33 /** メッセージが同一かどうかを比較するためのラムダ式 */
34 auto string_ptr_cmp = [](const std::string *a, const std::string *b) { return *a < *b; };
35
36 /** 同一メッセージの検索に使用するmapオブジェクト。
37  * 同一メッセージがあるかどうかを検索し、ヒットしたらweak_ptrからshared_ptrを作成しメッセージを共有する。
38  * message_historyのカスタムデリータの中で参照するので、message_historyより先に宣言しなければならない事に注意。
39  */
40 std::map<const std::string *, msg_wp, decltype(string_ptr_cmp)> message_map(string_ptr_cmp);
41
42 /** メッセージ履歴 */
43 std::deque<msg_sp> message_history;
44
45 /**
46  * @brief メッセージを保持する msg_sp オブジェクトを生成する
47  *
48  * @tparam T メッセージの型。std::string / std::string_view / const char* 等
49  * @param str メッセージ
50  * @return 生成した msg_sp オブジェクト
51  */
52 template <typename T>
53 msg_sp make_message(T &&str)
54 {
55     /** std::stringオブジェクトと同時にmessage_mapのエントリも削除するカスタムデリータ */
56     auto deleter = [](std::string *s) {
57         message_map.erase(s);
58         delete s;
59     };
60
61     // 新たにメッセージを保持する msg_sp オブジェクトを生成し、検索用mapオブジェクトにも追加する
62     auto new_msg = msg_sp(new std::string(std::forward<T>(str)), std::move(deleter));
63     message_map.emplace(new_msg.get(), msg_wp(new_msg));
64
65     return new_msg;
66 }
67 }
68
69 /*!
70  * @brief 保存中の過去ゲームメッセージの数を返す。 / How many messages are "available"?
71  * @return 残っているメッセージの数
72  */
73 int32_t message_num(void)
74 {
75     return message_history.size();
76 }
77
78 /*!
79  * @brief 過去のゲームメッセージを返す。 / Recall the "text" of a saved message
80  * @param age メッセージの世代
81  * @return メッセージの文字列ポインタ
82  */
83 concptr message_str(int age)
84 {
85     if ((age < 0) || (age >= message_num())) {
86         return "";
87     }
88
89     return message_history[age]->c_str();
90 }
91
92 static void message_add_aux(std::string str)
93 {
94     std::string splitted;
95
96     if (str.empty()) {
97         return;
98     }
99
100     // 80桁を超えるメッセージは80桁ずつ分割する
101     if (str.length() > 80) {
102         int n;
103 #ifdef JP
104         for (n = 0; n < 80; n++) {
105             if (iskanji(str[n])) {
106                 n++;
107             }
108         }
109
110         /* 最後の文字が漢字半分 */
111         if (n == 81) {
112             n = 79;
113         }
114 #else
115         for (n = 80; n > 60; n--) {
116             if (str[n] == ' ') {
117                 break;
118             }
119         }
120         if (n == 60) {
121             n = 80;
122         }
123 #endif
124         splitted = str.substr(n);
125         str = str.substr(0, n);
126     }
127
128     // 直前と同じメッセージの場合、「~ <xNN>」と表示する
129     if (!message_history.empty()) {
130         const char *t;
131         std::string_view last_message = *message_history.front();
132 #ifdef JP
133         for (t = last_message.data(); *t && (*t != '<' || (*(t + 1) != 'x')); t++) {
134             if (iskanji(*t)) {
135                 t++;
136             }
137         }
138 #else
139         for (t = last_message.data(); *t && (*t != '<'); t++) {
140             ;
141         }
142 #endif
143         int j = 1;
144         if (*t && t != last_message.data()) {
145             if (last_message.length() >= sizeof(" <xN>") - 1) {
146                 last_message = last_message.substr(0, t - last_message.data() - 1);
147                 j = atoi(t + 2);
148             }
149         }
150
151         if (str == last_message && (j < 1000)) {
152             str = format("%s <x%d>", str.c_str(), j + 1);
153             message_history.pop_front();
154             if (!now_message) {
155                 now_message++;
156             }
157         } else {
158             /*流れた行の数を数えておく */
159             num_more++;
160             now_message++;
161         }
162     }
163
164     msg_sp add_msg;
165
166     // メッセージ履歴から同一のメッセージを探す
167     if (const auto &it = message_map.find(&str); it != message_map.end()) {
168         // 同一のメッセージが見つかったならそのメッセージの msg_sp オブジェクトを複製
169         add_msg = it->second.lock();
170     } else {
171         // 見つからなかった場合は新たに msg_sp オブジェクトを作成
172         add_msg = make_message(std::move(str));
173     }
174
175     // メッセージ履歴に追加
176     message_history.push_front(std::move(add_msg));
177
178     if (message_history.size() == MESSAGE_MAX) {
179         message_history.pop_back();
180     }
181
182     if (!splitted.empty()) {
183         message_add_aux(std::move(splitted));
184     }
185 }
186
187 /*!
188  * @brief ゲームメッセージをログに追加する。 / Add a new message, with great efficiency
189  * @param msg 保存したいメッセージ
190  */
191 void message_add(std::string_view msg)
192 {
193     message_add_aux(std::string(msg));
194 }
195
196 bool is_msg_window_flowed(void)
197 {
198     int i;
199     for (i = 0; i < 8; i++) {
200         if (angband_term[i] && (window_flag[i] & PW_MESSAGE)) {
201             break;
202         }
203     }
204     if (i < 8) {
205         if (num_more < angband_term[i]->hgt) {
206             return false;
207         }
208
209         return num_more >= 0;
210     }
211     return num_more >= 0;
212 }
213
214 /*
215  * Hack -- flush
216  */
217 static void msg_flush(PlayerType *player_ptr, int x)
218 {
219     byte a = TERM_L_BLUE;
220     bool show_more = (num_more >= 0);
221
222     if (auto_more && !player_ptr->now_damaged) {
223         show_more = is_msg_window_flowed();
224     }
225
226     if (skip_more) {
227         show_more = false;
228     }
229
230     player_ptr->now_damaged = false;
231     if (!player_ptr->playing || show_more) {
232         term_putstr(x, 0, -1, a, _("-続く-", "-more-"));
233         while (true) {
234             int cmd = inkey();
235             if (cmd == ESCAPE) {
236                 /* auto_moreのとき、全て流す */
237                 num_more = -9999;
238                 break;
239             } else if (cmd == ' ') {
240                 /* 1画面だけ流す */
241                 num_more = 0;
242                 break;
243             } else if ((cmd == '\n') || (cmd == '\r')) {
244                 /* 1行だけ流す */
245                 num_more--;
246                 break;
247             }
248
249             if (quick_messages) {
250                 break;
251             }
252             bell();
253         }
254     }
255
256     term_erase(0, 0, 255);
257 }
258
259 void msg_erase(void)
260 {
261     msg_print(nullptr);
262 }
263
264 static int split_length(std::string_view sv, int max)
265 {
266     auto split = max;
267
268 #ifdef JP
269     auto k_flag = false;
270     auto wordlen = 0;
271     for (auto check = 0; check < max; check++) {
272         if (k_flag) {
273             k_flag = false;
274             continue;
275         }
276
277         if (iskanji(sv[check])) {
278             k_flag = true;
279             split = check;
280         } else if (sv[check] == ' ') {
281             split = check;
282             wordlen = 0;
283         } else {
284             wordlen++;
285             if (wordlen > 20) {
286                 split = check;
287             }
288         }
289     }
290 #else
291     for (auto check = 40; check < 72; check++) {
292         if (sv[check] == ' ') {
293             split = check;
294         }
295     }
296 #endif
297
298     return split;
299 }
300
301 /*!
302  * @briefOutput a message to the top line of the screen.
303  *
304  * Break long messages into multiple pieces (40-72 chars).
305  *
306  * Allow multiple short messages to "share" the top line.
307  *
308  * Prompt the user to make sure he has a chance to read them.
309  *
310  * These messages are memorized for later reference (see above).
311  *
312  * We could do "term_fresh()" to provide "flicker" if needed.
313  *
314  * The global "msg_flag" variable can be cleared to tell us to
315  * "erase" any "pending" messages still on the screen.
316  *
317  * Note that we must be very careful about using the
318  * "msg_print()" functions without explicitly calling the special
319  * "msg_print(nullptr)" function, since this may result in the loss
320  * of information if the screen is cleared, or if anything is
321  * displayed on the top line.
322  *
323  * Note that "msg_print(nullptr)" will clear the top line
324  * even if no messages are pending.  This is probably a hack.
325  * @todo ここのp_ptrを削除するのは破滅的に作業が増えるので保留
326  */
327 void msg_print(std::string_view msg)
328 {
329     if (w_ptr->timewalk_m_idx) {
330         return;
331     }
332
333     if (!msg_flag) {
334         term_erase(0, 0, 255);
335         msg_head_pos = 0;
336     }
337
338     std::string msg_includes_turn;
339     if (cheat_turn) {
340         msg = msg_includes_turn = format("T:%d - %s", w_ptr->game_turn, msg.data());
341     }
342
343     if ((msg_head_pos > 0) && ((msg_head_pos + msg.size()) > 72)) {
344         msg_flush(p_ptr, msg_head_pos);
345         msg_flag = false;
346         msg_head_pos = 0;
347     }
348
349     if (msg.size() > 1000) {
350         return;
351     }
352
353     if (w_ptr->character_generated) {
354         message_add(msg);
355     }
356
357     while (msg.size() > 72) {
358         auto split = split_length(msg, 72);
359         term_putstr(0, 0, split, TERM_WHITE, msg.data());
360         msg_flush(p_ptr, split + 1);
361         msg.remove_prefix(split);
362     }
363
364     term_putstr(msg_head_pos, 0, msg.size(), TERM_WHITE, msg.data());
365     p_ptr->window_flags |= (PW_MESSAGE);
366     window_stuff(p_ptr);
367
368     msg_flag = true;
369     msg_head_pos += msg.size() + _(0, 1);
370
371     if (fresh_message) {
372         term_fresh_force();
373     }
374 }
375
376 void msg_print(std::nullptr_t)
377 {
378     if (w_ptr->timewalk_m_idx) {
379         return;
380     }
381
382     if (!msg_flag) {
383         term_erase(0, 0, 255);
384         msg_head_pos = 0;
385     }
386
387     if (msg_head_pos > 0) {
388         msg_flush(p_ptr, msg_head_pos);
389         msg_flag = false;
390         msg_head_pos = 0;
391     }
392 }
393
394 /*
395  * Display a formatted message, using "vstrnfmt()" and "msg_print()".
396  */
397 void msg_format(std::string_view fmt, ...)
398 {
399     va_list vp;
400     char buf[1024];
401     va_start(vp, fmt);
402     (void)vstrnfmt(buf, sizeof(buf), fmt.data(), vp);
403     va_end(vp);
404     msg_print(buf);
405 }