OSDN Git Service

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