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"
20 /* Used in msg_print() for "buffering" */
23 COMMAND_CODE now_message;
28 static int msg_head_pos = 0;
30 using msg_sp = std::shared_ptr<const std::string>;
31 using msg_wp = std::weak_ptr<const std::string>;
33 /** メッセージが同一かどうかを比較するためのラムダ式 */
34 auto string_ptr_cmp = [](const std::string *a, const std::string *b) { return *a < *b; };
36 /** 同一メッセージの検索に使用するmapオブジェクト。
37 * 同一メッセージがあるかどうかを検索し、ヒットしたらweak_ptrからshared_ptrを作成しメッセージを共有する。
38 * message_historyのカスタムデリータの中で参照するので、message_historyより先に宣言しなければならない事に注意。
40 std::map<const std::string *, msg_wp, decltype(string_ptr_cmp)> message_map(string_ptr_cmp);
43 std::deque<msg_sp> message_history;
46 * @brief メッセージを保持する msg_sp オブジェクトを生成する
48 * @tparam T メッセージの型。std::string / std::string_view / const char* 等
50 * @return 生成した msg_sp オブジェクト
53 msg_sp make_message(T &&str)
55 /** std::stringオブジェクトと同時にmessage_mapのエントリも削除するカスタムデリータ */
56 auto deleter = [](std::string *s) {
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));
70 * @brief 保存中の過去ゲームメッセージの数を返す。 / How many messages are "available"?
71 * @return 残っているメッセージの数
73 int32_t message_num(void)
75 return message_history.size();
79 * @brief 過去のゲームメッセージを返す。 / Recall the "text" of a saved message
81 * @return メッセージの文字列ポインタ
83 std::shared_ptr<const std::string> message_str(int age)
85 if ((age < 0) || (age >= message_num())) {
86 return std::make_shared<const std::string>("");
89 return message_history[age];
92 static void message_add_aux(std::string str)
98 // 直前と同じメッセージの場合、「~ <xNN>」と表示する
99 if (!message_history.empty()) {
101 std::string_view last_message = *message_history.front();
103 for (t = last_message.data(); *t && (*t != '<' || (*(t + 1) != 'x')); t++) {
109 for (t = last_message.data(); *t && (*t != '<'); t++) {
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);
121 if (str == last_message && (j < 1000)) {
122 str = format("%s <x%d>", str.data(), j + 1);
123 message_history.pop_front();
136 // メッセージ履歴から同一のメッセージを探す
137 if (const auto &it = message_map.find(&str); it != message_map.end()) {
138 // 同一のメッセージが見つかったならそのメッセージの msg_sp オブジェクトを複製
139 add_msg = it->second.lock();
141 // 見つからなかった場合は新たに msg_sp オブジェクトを作成
142 add_msg = make_message(std::move(str));
146 message_history.push_front(std::move(add_msg));
148 if (message_history.size() == MESSAGE_MAX) {
149 message_history.pop_back();
154 * @brief ゲームメッセージをログに追加する。 / Add a new message, with great efficiency
155 * @param msg 保存したいメッセージ
157 void message_add(std::string_view msg)
159 message_add_aux(std::string(msg));
162 bool is_msg_window_flowed(void)
165 for (; i < angband_terms.size(); ++i) {
166 if (angband_terms[i] && g_window_flags[i].has(SubWindowRedrawingFlag::MESSAGE)) {
171 if (num_more < angband_terms[i]->hgt) {
175 return num_more >= 0;
177 return num_more >= 0;
183 static void msg_flush(PlayerType *player_ptr, int x)
185 byte a = TERM_L_BLUE;
186 bool show_more = (num_more >= 0);
188 if (auto_more && !player_ptr->now_damaged) {
189 show_more = is_msg_window_flowed();
196 player_ptr->now_damaged = false;
197 if (!player_ptr->playing || show_more) {
198 term_putstr(x, 0, -1, a, _("-続く-", "-more-"));
202 /* auto_moreのとき、全て流す */
205 } else if (cmd == ' ') {
209 } else if ((cmd == '\n') || (cmd == '\r')) {
215 if (quick_messages) {
230 static int split_length(std::string_view sv, int max)
237 for (auto check = 0; check < max; check++) {
243 if (iskanji(sv[check])) {
246 } else if (sv[check] == ' ') {
257 for (auto check = 40; check < 72; check++) {
258 if (sv[check] == ' ') {
268 * @briefOutput a message to the top line of the screen.
270 * Break long messages into multiple pieces (40-72 chars).
272 * Allow multiple short messages to "share" the top line.
274 * Prompt the user to make sure he has a chance to read them.
276 * These messages are memorized for later reference (see above).
278 * We could do "term_fresh()" to provide "flicker" if needed.
280 * The global "msg_flag" variable can be cleared to tell us to
281 * "erase" any "pending" messages still on the screen.
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.
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を削除するのは破滅的に作業が増えるので保留
293 void msg_print(std::string_view msg)
295 if (w_ptr->timewalk_m_idx) {
304 std::string msg_includes_turn;
306 msg = msg_includes_turn = format("T:%d - %s", w_ptr->game_turn, msg.data());
309 const auto &[wid, hgt] = term_get_size();
310 const auto split_width = wid - 8;
312 if ((msg_head_pos > 0) && ((msg_head_pos + std::ssize(msg)) > split_width)) {
313 msg_flush(p_ptr, msg_head_pos);
318 if (msg.size() > 1000) {
322 if (w_ptr->character_generated) {
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);
333 term_putstr(msg_head_pos, 0, msg.size(), TERM_WHITE, msg.data());
334 RedrawingFlagsUpdater::get_instance().set_flag(SubWindowRedrawingFlag::MESSAGE);
338 msg_head_pos += msg.size() + _(0, 1);
345 void msg_print(std::nullptr_t)
347 if (w_ptr->timewalk_m_idx) {
356 if (msg_head_pos > 0) {
357 msg_flush(p_ptr, msg_head_pos);
363 void msg_format(const char *fmt, ...)
368 (void)vstrnfmt(buf, sizeof(buf), fmt, vp);