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"
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 concptr message_str(int age)
85 if ((age < 0) || (age >= message_num())) {
89 return message_history[age]->c_str();
92 static void message_add_aux(std::string str)
100 // 80桁を超えるメッセージは80桁ずつ分割する
101 if (str.length() > 80) {
104 for (n = 0; n < 80; n++) {
105 if (iskanji(str[n])) {
115 for (n = 80; n > 60; n--) {
124 splitted = str.substr(n);
125 str = str.substr(0, n);
128 // 直前と同じメッセージの場合、「~ <xNN>」と表示する
129 if (!message_history.empty()) {
131 std::string_view last_message = *message_history.front();
133 for (t = last_message.data(); *t && (*t != '<' || (*(t + 1) != 'x')); t++) {
139 for (t = last_message.data(); *t && (*t != '<'); t++) {
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);
151 if (str == last_message && (j < 1000)) {
152 str = format("%s <x%d>", str.c_str(), j + 1);
153 message_history.pop_front();
166 // メッセージ履歴から同一のメッセージを探す
167 if (const auto &it = message_map.find(&str); it != message_map.end()) {
168 // 同一のメッセージが見つかったならそのメッセージの msg_sp オブジェクトを複製
169 add_msg = it->second.lock();
171 // 見つからなかった場合は新たに msg_sp オブジェクトを作成
172 add_msg = make_message(std::move(str));
176 message_history.push_front(std::move(add_msg));
178 if (message_history.size() == MESSAGE_MAX) {
179 message_history.pop_back();
182 if (!splitted.empty()) {
183 message_add_aux(std::move(splitted));
188 * @brief ゲームメッセージをログに追加する。 / Add a new message, with great efficiency
189 * @param msg 保存したいメッセージ
191 void message_add(std::string_view msg)
193 message_add_aux(std::string(msg));
196 bool is_msg_window_flowed(void)
199 for (i = 0; i < 8; i++) {
200 if (angband_term[i] && (window_flag[i] & PW_MESSAGE)) {
205 if (num_more < angband_term[i]->hgt) {
209 return num_more >= 0;
211 return num_more >= 0;
217 static void msg_flush(PlayerType *player_ptr, int x)
219 byte a = TERM_L_BLUE;
220 bool show_more = (num_more >= 0);
222 if (auto_more && !player_ptr->now_damaged) {
223 show_more = is_msg_window_flowed();
230 player_ptr->now_damaged = false;
231 if (!player_ptr->playing || show_more) {
232 term_putstr(x, 0, -1, a, _("-続く-", "-more-"));
236 /* auto_moreのとき、全て流す */
239 } else if (cmd == ' ') {
243 } else if ((cmd == '\n') || (cmd == '\r')) {
249 if (quick_messages) {
256 term_erase(0, 0, 255);
264 static int split_length(std::string_view sv, int max)
271 for (auto check = 0; check < max; check++) {
277 if (iskanji(sv[check])) {
280 } else if (sv[check] == ' ') {
291 for (auto check = 40; check < 72; check++) {
292 if (sv[check] == ' ') {
302 * @briefOutput a message to the top line of the screen.
304 * Break long messages into multiple pieces (40-72 chars).
306 * Allow multiple short messages to "share" the top line.
308 * Prompt the user to make sure he has a chance to read them.
310 * These messages are memorized for later reference (see above).
312 * We could do "term_fresh()" to provide "flicker" if needed.
314 * The global "msg_flag" variable can be cleared to tell us to
315 * "erase" any "pending" messages still on the screen.
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.
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を削除するのは破滅的に作業が増えるので保留
327 void msg_print(std::string_view msg)
329 if (w_ptr->timewalk_m_idx) {
334 term_erase(0, 0, 255);
338 std::string msg_includes_turn;
340 msg = msg_includes_turn = format("T:%d - %s", w_ptr->game_turn, msg.data());
343 if ((msg_head_pos > 0) && ((msg_head_pos + msg.size()) > 72)) {
344 msg_flush(p_ptr, msg_head_pos);
349 if (msg.size() > 1000) {
353 if (w_ptr->character_generated) {
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);
364 term_putstr(msg_head_pos, 0, msg.size(), TERM_WHITE, msg.data());
365 p_ptr->window_flags |= (PW_MESSAGE);
369 msg_head_pos += msg.size() + _(0, 1);
376 void msg_print(std::nullptr_t)
378 if (w_ptr->timewalk_m_idx) {
383 term_erase(0, 0, 255);
387 if (msg_head_pos > 0) {
388 msg_flush(p_ptr, msg_head_pos);
395 * Display a formatted message, using "vstrnfmt()" and "msg_print()".
397 void msg_format(std::string_view fmt, ...)
402 (void)vstrnfmt(buf, sizeof(buf), fmt.data(), vp);