Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 36 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
| #include "messages.h" | |
| #include "input.h" | |
| #include "game.h" | |
| #include "player.h" // Only u.is_dead | |
| #include "debug.h" | |
| #include "compatibility.h" //to_string | |
| #include "json.h" | |
| #include "output.h" | |
| #include "calendar.h" | |
| #include "translations.h" | |
| #include <deque> | |
| #include <iterator> | |
| // sidebar messages flow direction | |
| extern bool log_from_top; | |
| namespace { | |
| // Messages object. | |
| Messages player_messages; | |
| struct game_message : public JsonDeserializer, public JsonSerializer { | |
| std::string message; | |
| calendar time = 0; | |
| int count = 1; | |
| game_message_type type = m_neutral; | |
| game_message() = default; | |
| game_message(std::string &&msg, game_message_type const t) | |
| : message (std::move(msg)), time (calendar::turn), type (t) | |
| { | |
| } | |
| int turn() const { | |
| return time.get_turn(); | |
| } | |
| std::string get_with_count() const { | |
| if (count <= 1) { | |
| return message; | |
| } | |
| //~ Message %s on the message log was repeated %d times, eg. "You hear a whack! x 12" | |
| return string_format(_("%s x %d"), message.c_str(), count); | |
| } | |
| nc_color get_color(int const current) const { | |
| if (turn() >= current) { | |
| // color for new messages | |
| return msgtype_to_color(type, false); | |
| } else if (turn() + 5 >= current) { | |
| // color for slightly old messages | |
| return msgtype_to_color(type, true); | |
| } | |
| // color for old messages | |
| return c_dkgray; | |
| } | |
| void deserialize(JsonIn &jsin) override { | |
| JsonObject obj = jsin.get_object(); | |
| time = obj.get_int( "turn" ); | |
| message = obj.get_string( "message" ); | |
| count = obj.get_int( "count" ); | |
| type = static_cast<game_message_type>( obj.get_int( "type" ) ); | |
| } | |
| void serialize(JsonOut &jsout) const override { | |
| jsout.start_object(); | |
| jsout.member( "turn", static_cast<int>( time ) ); | |
| jsout.member( "message", message ); | |
| jsout.member( "count", count ); | |
| jsout.member( "type", static_cast<int>( type ) ); | |
| jsout.end_object(); | |
| } | |
| }; | |
| } //namespace | |
| class Messages::impl_t { | |
| public: | |
| std::deque<game_message> messages; // Messages to be printed | |
| int curmes = 0; // The last-seen message. | |
| bool has_undisplayed_messages() const { | |
| return !messages.empty() && messages.back().turn() > curmes; | |
| } | |
| game_message const& history(int const i) const { | |
| return messages[messages.size() - i - 1]; | |
| } | |
| // coalesce recent like messages | |
| bool coalesce_messages(std::string const &msg, game_message_type const type) { | |
| if (messages.empty()) { | |
| return false; | |
| } | |
| auto &last_msg = messages.back(); | |
| if (last_msg.turn() + 3 < calendar::turn.get_turn()) { | |
| return false; | |
| } | |
| if (type != last_msg.type || msg != last_msg.message) { | |
| return false; | |
| } | |
| last_msg.count++; | |
| last_msg.time = calendar::turn; | |
| last_msg.type = type; | |
| return true; | |
| } | |
| void add_msg_string(std::string &&msg) { | |
| add_msg_string(std::move(msg), m_neutral); | |
| } | |
| void add_msg_string(std::string &&msg, game_message_type const type) { | |
| if (msg.length() == 0) { | |
| return; | |
| } | |
| // hide messages if dead | |
| if (g->u.is_dead_state()) { | |
| return; | |
| } | |
| if (type == m_debug && !debug_mode) { | |
| return; | |
| } | |
| if (coalesce_messages(msg, type)) { | |
| return; | |
| } | |
| while (messages.size() > 255) { | |
| messages.pop_front(); | |
| } | |
| messages.emplace_back(remove_color_tags(std::move(msg)), type); | |
| } | |
| std::vector<std::pair<std::string, std::string>> recent_messages(size_t count) const { | |
| count = std::min(count, messages.size()); | |
| std::vector<std::pair<std::string, std::string>> result; | |
| result.reserve(count); | |
| const int offset = static_cast<std::ptrdiff_t>( messages.size() - count ); | |
| std::transform(begin(messages) + offset, end(messages), back_inserter(result), | |
| [](game_message const& msg) { | |
| return std::make_pair(msg.time.print_time(), | |
| msg.count ? msg.message + to_string(msg.count) : msg.message); | |
| } | |
| ); | |
| return result; | |
| } | |
| }; | |
| Messages::Messages() : impl_ {new impl_t()} | |
| { | |
| } | |
| Messages::~Messages() = default; | |
| std::vector<std::pair<std::string, std::string>> Messages::recent_messages(const size_t count) | |
| { | |
| return player_messages.impl_->recent_messages(count); | |
| } | |
| void Messages::serialize( JsonOut &json ) | |
| { | |
| json.member( "player_messages" ); | |
| json.start_object(); | |
| json.member( "messages", player_messages.impl_->messages ); | |
| json.member( "curmes", player_messages.impl_->curmes ); | |
| json.end_object(); | |
| } | |
| void Messages::deserialize( JsonObject &json ) | |
| { | |
| if (!json.has_member("player_messages")) { | |
| return; | |
| } | |
| JsonObject obj = json.get_object( "player_messages" ); | |
| obj.read( "messages", player_messages.impl_->messages ); | |
| obj.read( "curmes", player_messages.impl_->curmes ); | |
| } | |
| void Messages::vadd_msg(const char *msg, va_list ap) | |
| { | |
| player_messages.impl_->add_msg_string(vstring_format(msg, ap)); | |
| } | |
| void Messages::vadd_msg(game_message_type type, const char *msg, va_list ap) | |
| { | |
| player_messages.impl_->add_msg_string(vstring_format(msg, ap), type); | |
| } | |
| void Messages::clear_messages() | |
| { | |
| player_messages.impl_->messages.clear(); | |
| } | |
| size_t Messages::size() | |
| { | |
| return player_messages.impl_->messages.size(); | |
| } | |
| bool Messages::has_undisplayed_messages() | |
| { | |
| return player_messages.impl_->has_undisplayed_messages(); | |
| } | |
| void Messages::display_messages() | |
| { | |
| WINDOW_PTR w_ptr {newwin( | |
| FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH, | |
| (TERMY > FULL_SCREEN_HEIGHT) ? (TERMY - FULL_SCREEN_HEIGHT) / 2 : 0, | |
| (TERMX > FULL_SCREEN_WIDTH) ? (TERMX - FULL_SCREEN_WIDTH) / 2 : 0)}; | |
| WINDOW *const w = w_ptr.get(); | |
| input_context ctxt("MESSAGE_LOG"); | |
| ctxt.register_action("UP", _("Scroll up")); | |
| ctxt.register_action("DOWN", _("Scroll down")); | |
| ctxt.register_action("QUIT"); | |
| ctxt.register_action("HELP_KEYBINDINGS"); | |
| int offset = 0; | |
| const int maxlength = FULL_SCREEN_WIDTH - 2 - 1; | |
| const int bottom = FULL_SCREEN_HEIGHT - 2; | |
| const int msg_count = size(); | |
| for (;;) { | |
| werase(w); | |
| draw_border(w); | |
| mvwprintz(w, bottom + 1, 32, c_red, _("Press %s to return"), ctxt.get_desc("QUIT").c_str()); | |
| draw_scrollbar(w, offset, bottom, msg_count, 1); | |
| int line = 1; | |
| int lasttime = -1; | |
| for( int i = offset; i < msg_count; ++i ) { | |
| if (line > bottom) { | |
| break; | |
| } | |
| const game_message &m = player_messages.impl_->history(i); | |
| const nc_color col = m.get_color(player_messages.impl_->curmes); | |
| const calendar timepassed = calendar::turn - m.time; | |
| if (timepassed.get_turn() > lasttime) { | |
| mvwprintz(w, line++, 3, c_ltblue, _("%s ago:"), | |
| timepassed.textify_period().c_str()); | |
| lasttime = timepassed.get_turn(); | |
| } | |
| for( const std::string &folded : foldstring(m.get_with_count(), maxlength) ) { | |
| if (line > bottom) { | |
| break; | |
| } | |
| mvwprintz(w, line++, 1, col, "%s", folded.c_str()); | |
| } | |
| } | |
| if (offset + 1 < msg_count) { | |
| mvwprintz(w, bottom + 1, 5, c_magenta, "vvv"); | |
| } | |
| if (offset > 0) { | |
| mvwprintz(w, bottom + 1, maxlength - 3, c_magenta, "^^^"); | |
| } | |
| wrefresh(w); | |
| const std::string &action = ctxt.handle_input(); | |
| if (action == "DOWN" && offset + 1 < msg_count) { | |
| offset++; | |
| } else if (action == "UP" && offset > 0) { | |
| offset--; | |
| } else if (action == "QUIT") { | |
| break; | |
| } | |
| } | |
| player_messages.impl_->curmes = calendar::turn.get_turn(); | |
| } | |
| void Messages::display_messages(WINDOW *const ipk_target, int const left, int const top, | |
| int const right, int const bottom) | |
| { | |
| if (!size()) { | |
| return; | |
| } | |
| int const maxlength = right - left; | |
| int line = log_from_top ? top : bottom; | |
| if (log_from_top) { | |
| for (int i = size() - 1; i >= 0; --i) { | |
| if (line > bottom) { | |
| break; | |
| } | |
| const game_message &m = player_messages.impl_->messages[i]; | |
| const nc_color col = m.get_color(player_messages.impl_->curmes); | |
| for( const std::string &folded : foldstring(m.get_with_count(), maxlength) ) { | |
| if (line > bottom) { | |
| break; | |
| } | |
| mvwprintz(ipk_target, line++, left, col, "%s", folded.c_str()); | |
| } | |
| } | |
| } else { | |
| for (int i = size() - 1; i >= 0; --i) { | |
| if (line < top) { | |
| break; | |
| } | |
| const game_message &m = player_messages.impl_->messages[i]; | |
| const nc_color col = m.get_color(player_messages.impl_->curmes); | |
| const auto folded_strings = foldstring(m.get_with_count(), maxlength); | |
| const auto folded_rend = folded_strings.rend(); | |
| for( auto string_iter = folded_strings.rbegin(); | |
| string_iter != folded_rend && line >= top; ++string_iter, line-- ) { | |
| mvwprintz(ipk_target, line, left, col, "%s", string_iter->c_str()); | |
| } | |
| } | |
| } | |
| player_messages.impl_->curmes = calendar::turn.get_turn(); | |
| } | |
| void add_msg(const char *msg, ...) | |
| { | |
| va_list ap; | |
| va_start(ap, msg); | |
| Messages::vadd_msg(msg, ap); | |
| va_end(ap); | |
| } | |
| void add_msg(game_message_type const type, const char *msg, ...) | |
| { | |
| va_list ap; | |
| va_start(ap, msg); | |
| Messages::vadd_msg(type, msg, ap); | |
| va_end(ap); | |
| } |