OSDN Git Service

Merge pull request #4185 from Hourier/Make-FloorChangeModesStore
[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 "system/redrawing-flags-updater.h"
11 #include "term/gameterm.h"
12 #include "term/term-color-types.h"
13 #include "util/int-char-converter.h"
14 #include "world/world.h"
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 std::shared_ptr<const std::string> message_str(int age)
84 {
85     if ((age < 0) || (age >= message_num())) {
86         return std::make_shared<const std::string>("");
87     }
88
89     return message_history[age];
90 }
91
92 static void message_add_aux(std::string str)
93 {
94     if (str.empty()) {
95         return;
96     }
97
98     // 直前と同じメッセージの場合、「~ <xNN>」と表示する
99     if (!message_history.empty()) {
100         const char *t;
101         std::string_view last_message = *message_history.front();
102 #ifdef JP
103         for (t = last_message.data(); *t && (*t != '<' || (*(t + 1) != 'x')); t++) {
104             if (iskanji(*t)) {
105                 t++;
106             }
107         }
108 #else
109         for (t = last_message.data(); *t && (*t != '<'); t++) {
110             ;
111         }
112 #endif
113         int j = 1;
114         if (*t && t != last_message.data()) {
115             if (last_message.length() >= sizeof(" <xN>") - 1) {
116                 last_message = last_message.substr(0, t - last_message.data() - 1);
117                 j = atoi(t + 2);
118             }
119         }
120
121         if (str == last_message && (j < 1000)) {
122             str = format("%s <x%d>", str.data(), j + 1);
123             message_history.pop_front();
124             if (!now_message) {
125                 now_message++;
126             }
127         } else {
128             /*流れた行の数を数えておく */
129             num_more++;
130             now_message++;
131         }
132     }
133
134     msg_sp add_msg;
135
136     // メッセージ履歴から同一のメッセージを探す
137     if (const auto &it = message_map.find(&str); it != message_map.end()) {
138         // 同一のメッセージが見つかったならそのメッセージの msg_sp オブジェクトを複製
139         add_msg = it->second.lock();
140     } else {
141         // 見つからなかった場合は新たに msg_sp オブジェクトを作成
142         add_msg = make_message(std::move(str));
143     }
144
145     // メッセージ履歴に追加
146     message_history.push_front(std::move(add_msg));
147
148     if (message_history.size() == MESSAGE_MAX) {
149         message_history.pop_back();
150     }
151 }
152
153 /*!
154  * @brief ゲームメッセージをログに追加する。 / Add a new message, with great efficiency
155  * @param msg 保存したいメッセージ
156  */
157 void message_add(std::string_view msg)
158 {
159     message_add_aux(std::string(msg));
160 }
161
162 bool is_msg_window_flowed(void)
163 {
164     auto i = 0U;
165     for (; i < angband_terms.size(); ++i) {
166         if (angband_terms[i] && g_window_flags[i].has(SubWindowRedrawingFlag::MESSAGE)) {
167             break;
168         }
169     }
170     if (i < 8) {
171         if (num_more < angband_terms[i]->hgt) {
172             return false;
173         }
174
175         return num_more >= 0;
176     }
177     return num_more >= 0;
178 }
179
180 /*
181  * Hack -- flush
182  */
183 static void msg_flush(PlayerType *player_ptr, int x)
184 {
185     byte a = TERM_L_BLUE;
186     bool show_more = (num_more >= 0);
187
188     if (auto_more && !player_ptr->now_damaged) {
189         show_more = is_msg_window_flowed();
190     }
191
192     if (skip_more) {
193         show_more = false;
194     }
195
196     player_ptr->now_damaged = false;
197     if (!player_ptr->playing || show_more) {
198         term_putstr(x, 0, -1, a, _("-続く-", "-more-"));
199         while (true) {
200             int cmd = inkey();
201             if (cmd == ESCAPE) {
202                 /* auto_moreのとき、全て流す */
203                 num_more = -9999;
204                 break;
205             } else if (cmd == ' ') {
206                 /* 1画面だけ流す */
207                 num_more = 0;
208                 break;
209             } else if ((cmd == '\n') || (cmd == '\r')) {
210                 /* 1行だけ流す */
211                 num_more--;
212                 break;
213             }
214
215             if (quick_messages) {
216                 break;
217             }
218             bell();
219         }
220     }
221
222     term_erase(0, 0);
223 }
224
225 void msg_erase(void)
226 {
227     msg_print(nullptr);
228 }
229
230 static int split_length(std::string_view sv, int max)
231 {
232     auto split = max;
233
234 #ifdef JP
235     auto k_flag = false;
236     auto wordlen = 0;
237     for (auto check = 0; check < max; check++) {
238         if (k_flag) {
239             k_flag = false;
240             continue;
241         }
242
243         if (iskanji(sv[check])) {
244             k_flag = true;
245             split = check;
246         } else if (sv[check] == ' ') {
247             split = check;
248             wordlen = 0;
249         } else {
250             wordlen++;
251             if (wordlen > 20) {
252                 split = check;
253             }
254         }
255     }
256 #else
257     for (auto check = 40; check < 72; check++) {
258         if (sv[check] == ' ') {
259             split = check;
260         }
261     }
262 #endif
263
264     return split;
265 }
266
267 /*!
268  * @briefOutput a message to the top line of the screen.
269  *
270  * Break long messages into multiple pieces (40-72 chars).
271  *
272  * Allow multiple short messages to "share" the top line.
273  *
274  * Prompt the user to make sure he has a chance to read them.
275  *
276  * These messages are memorized for later reference (see above).
277  *
278  * We could do "term_fresh()" to provide "flicker" if needed.
279  *
280  * The global "msg_flag" variable can be cleared to tell us to
281  * "erase" any "pending" messages still on the screen.
282  *
283  * Note that we must be very careful about using the
284  * "msg_print()" functions without explicitly calling the special
285  * "msg_print(nullptr)" function, since this may result in the loss
286  * of information if the screen is cleared, or if anything is
287  * displayed on the top line.
288  *
289  * Note that "msg_print(nullptr)" will clear the top line
290  * even if no messages are pending.  This is probably a hack.
291  * @todo ここのp_ptrを削除するのは破滅的に作業が増えるので保留
292  */
293 void msg_print(std::string_view msg)
294 {
295     if (w_ptr->timewalk_m_idx) {
296         return;
297     }
298
299     if (!msg_flag) {
300         term_erase(0, 0);
301         msg_head_pos = 0;
302     }
303
304     std::string msg_includes_turn;
305     if (cheat_turn) {
306         msg = msg_includes_turn = format("T:%d - %s", w_ptr->game_turn, msg.data());
307     }
308
309     const auto &[wid, hgt] = term_get_size();
310     const auto split_width = wid - 8;
311
312     if ((msg_head_pos > 0) && ((msg_head_pos + std::ssize(msg)) > split_width)) {
313         msg_flush(p_ptr, msg_head_pos);
314         msg_flag = false;
315         msg_head_pos = 0;
316     }
317
318     if (msg.size() > 1000) {
319         return;
320     }
321
322     if (w_ptr->character_generated) {
323         message_add(msg);
324     }
325
326     while (std::ssize(msg) > split_width) {
327         auto split = split_length(msg, split_width);
328         term_putstr(0, 0, split, TERM_WHITE, msg.data());
329         msg_flush(p_ptr, split + 1);
330         msg.remove_prefix(split);
331     }
332
333     term_putstr(msg_head_pos, 0, msg.size(), TERM_WHITE, msg.data());
334     RedrawingFlagsUpdater::get_instance().set_flag(SubWindowRedrawingFlag::MESSAGE);
335     window_stuff(p_ptr);
336
337     msg_flag = true;
338     msg_head_pos += msg.size() + _(0, 1);
339
340     if (fresh_message) {
341         term_fresh_force();
342     }
343 }
344
345 void msg_print(std::nullptr_t)
346 {
347     if (w_ptr->timewalk_m_idx) {
348         return;
349     }
350
351     if (!msg_flag) {
352         term_erase(0, 0);
353         msg_head_pos = 0;
354     }
355
356     if (msg_head_pos > 0) {
357         msg_flush(p_ptr, msg_head_pos);
358         msg_flag = false;
359         msg_head_pos = 0;
360     }
361 }
362
363 void msg_format(const char *fmt, ...)
364 {
365     va_list vp;
366     char buf[1024];
367     va_start(vp, fmt);
368     (void)vstrnfmt(buf, sizeof(buf), fmt, vp);
369     va_end(vp);
370     msg_print(buf);
371 }