1 #include "help.h"
2 
3 #include <algorithm>
4 #include <array>
5 #include <cstddef>
6 #include <functional>
7 #include <iterator>
8 #include <numeric>
9 #include <string>
10 #include <unordered_map>
11 #include <vector>
12 
13 #include "action.h"
14 #include "cata_utility.h"
15 #include "catacharset.h"
16 #include "color.h"
17 #include "cursesdef.h"
18 #include "debug.h"
19 #include "input.h"
20 #include "json.h"
21 #include "optional.h"
22 #include "output.h"
23 #include "path_info.h"
24 #include "point.h"
25 #include "string_formatter.h"
26 #include "text_snippets.h"
27 #include "translations.h"
28 #include "ui_manager.h"
29 
get_help()30 help &get_help()
31 {
32     static help single_instance;
33     return single_instance;
34 }
35 
load()36 void help::load()
37 {
38     read_from_file_optional_json( PATH_INFO::help(), [&]( JsonIn & jsin ) {
39         deserialize( jsin );
40     } );
41 }
42 
deserialize(JsonIn & jsin)43 void help::deserialize( JsonIn &jsin )
44 {
45     jsin.start_array();
46     while( !jsin.end_array() ) {
47         JsonObject jo = jsin.get_object();
48 
49         if( jo.get_string( "type" ) != "help" ) {
50             debugmsg( "object with type other than \"type\" found in help text file" );
51             continue;
52         }
53 
54         std::vector<translation> messages;
55         jo.read( "messages", messages );
56 
57         translation name;
58         jo.read( "name", name );
59 
60         help_texts[jo.get_int( "order" )] = std::make_pair( name, messages );
61     }
62 }
63 
get_dir_grid()64 std::string help::get_dir_grid()
65 {
66     static const std::array<action_id, 9> movearray = {{
67             ACTION_MOVE_FORTH_LEFT, ACTION_MOVE_FORTH, ACTION_MOVE_FORTH_RIGHT,
68             ACTION_MOVE_LEFT,  ACTION_PAUSE,  ACTION_MOVE_RIGHT,
69             ACTION_MOVE_BACK_LEFT, ACTION_MOVE_BACK, ACTION_MOVE_BACK_RIGHT
70         }
71     };
72 
73     std::string movement = "<LEFTUP_0>  <UP_0>  <RIGHTUP_0>   <LEFTUP_1>  <UP_1>  <RIGHTUP_1>\n"
74                            " \\ | /     \\ | /\n"
75                            "  \\|/       \\|/\n"
76                            "<LEFT_0>--<pause_0>--<RIGHT_0>   <LEFT_1>--<pause_1>--<RIGHT_1>\n"
77                            "  /|\\       /|\\\n"
78                            " / | \\     / | \\\n"
79                            "<LEFTDOWN_0>  <DOWN_0>  <RIGHTDOWN_0>   <LEFTDOWN_1>  <DOWN_1>  <RIGHTDOWN_1>";
80 
81     for( action_id dir : movearray ) {
82         std::vector<input_event> keys = keys_bound_to( dir, /*maximum_modifier_count=*/0 );
83         for( size_t i = 0; i < 2; i++ ) {
84             movement = string_replace( movement, "<" + action_ident( dir ) + string_format( "_%d>", i ),
85                                        i < keys.size()
86                                        ? string_format( "<color_light_blue>%s</color>",
87                                                keys[i].short_description() )
88                                        : "<color_red>?</color>" );
89         }
90     }
91 
92     return movement;
93 }
94 
draw_menu(const catacurses::window & win) const95 void help::draw_menu( const catacurses::window &win ) const
96 {
97     werase( win );
98     // NOLINTNEXTLINE(cata-use-named-point-constants)
99     int y = fold_and_print( win, point( 1, 0 ), getmaxx( win ) - 2, c_white,
100                             _( "Please press one of the following for help on that topic:\n"
101                                "Press ESC to return to the game." ) ) + 1;
102 
103     size_t half_size = help_texts.size() / 2 + 1;
104     int second_column = divide_round_up( getmaxx( win ), 2 );
105     size_t i = 0;
106     for( const auto &text : help_texts ) {
107         const std::string cat_name = text.second.first.translated();
108         if( i < half_size ) {
109             second_column = std::max( second_column, utf8_width( cat_name ) + 4 );
110         }
111 
112         shortcut_print( win, point( i < half_size ? 1 : second_column, y + i % half_size ),
113                         c_white, c_light_blue, cat_name );
114         ++i;
115     }
116 
117     wnoutrefresh( win );
118 }
119 
get_note_colors()120 std::string help::get_note_colors()
121 {
122     std::string text = _( "Note colors: " );
123     for( const auto &color_pair : get_note_color_names() ) {
124         // The color index is not translatable, but the name is.
125         //~ %1$s: note color abbreviation, %2$s: note color name
126         text += string_format( pgettext( "note color", "%1$s:%2$s, " ),
127                                colorize( color_pair.first, color_pair.second.color ),
128                                color_pair.second.name );
129     }
130 
131     return text;
132 }
133 
display_help() const134 void help::display_help() const
135 {
136     catacurses::window w_help_border;
137     catacurses::window w_help;
138 
139     ui_adaptor ui;
140     const auto init_windows = [&]( ui_adaptor & ui ) {
141         w_help_border = catacurses::newwin( FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH,
142                                             point( TERMX > FULL_SCREEN_WIDTH ? ( TERMX - FULL_SCREEN_WIDTH ) / 2 : 0,
143                                                     TERMY > FULL_SCREEN_HEIGHT ? ( TERMY - FULL_SCREEN_HEIGHT ) / 2 : 0 ) );
144         w_help = catacurses::newwin( FULL_SCREEN_HEIGHT - 2, FULL_SCREEN_WIDTH - 2,
145                                      point( 1 + static_cast<int>( TERMX > FULL_SCREEN_WIDTH ? ( TERMX - FULL_SCREEN_WIDTH ) / 2 : 0 ),
146                                             1 + static_cast<int>( TERMY > FULL_SCREEN_HEIGHT ? ( TERMY - FULL_SCREEN_HEIGHT ) / 2 : 0 ) ) );
147         ui.position_from_window( w_help_border );
148     };
149     init_windows( ui );
150     ui.on_screen_resize( init_windows );
151 
152     input_context ctxt( "default", keyboard_mode::keychar );
153     ctxt.register_cardinal();
154     ctxt.register_action( "QUIT" );
155     ctxt.register_action( "CONFIRM" );
156     // for the menu shortcuts
157     ctxt.register_action( "ANY_INPUT" );
158 
159     std::string action;
160 
161     ui.on_redraw( [&]( const ui_adaptor & ) {
162         draw_border( w_help_border, BORDER_COLOR, _( " HELP " ), c_black_white );
163         wnoutrefresh( w_help_border );
164         draw_menu( w_help );
165     } );
166 
167     std::map<int, std::vector<std::string>> hotkeys;
168     for( const auto &text : help_texts ) {
169         hotkeys.emplace( text.first, get_hotkeys( text.second.first.translated() ) );
170     }
171 
172     do {
173         ui_manager::redraw();
174 
175         action = ctxt.handle_input();
176         std::string sInput = ctxt.get_raw_input().text;
177         for( const auto &hotkey_entry : hotkeys ) {
178             auto help_text_it = help_texts.find( hotkey_entry.first );
179             if( help_text_it == help_texts.end() ) {
180                 continue;
181             }
182             for( const std::string &hotkey : hotkey_entry.second ) {
183                 if( sInput == hotkey ) {
184                     std::vector<std::string> i18n_help_texts;
185                     i18n_help_texts.reserve( help_text_it->second.second.size() );
186                     std::transform( help_text_it->second.second.begin(), help_text_it->second.second.end(),
187                     std::back_inserter( i18n_help_texts ), [&]( const translation & line ) {
188                         std::string line_proc = line.translated();
189                         if( line_proc == "<DRAW_NOTE_COLORS>" ) {
190                             line_proc = get_note_colors();
191                         } else if( line_proc == "<HELP_DRAW_DIRECTIONS>" ) {
192                             line_proc = get_dir_grid();
193                         }
194                         size_t pos = line_proc.find( "<press_", 0, 7 );
195                         while( pos != std::string::npos ) {
196                             size_t pos2 = line_proc.find( ">", pos, 1 );
197 
198                             std::string action = line_proc.substr( pos + 7, pos2 - pos - 7 );
199                             auto replace = "<color_light_blue>" + press_x( look_up_action( action ), "", "" ) + "</color>";
200 
201                             if( replace.empty() ) {
202                                 debugmsg( "Help json: Unknown action: %s", action );
203                             } else {
204                                 line_proc = string_replace( line_proc, "<press_" + action + ">", replace );
205                             }
206 
207                             pos = line_proc.find( "<press_", pos2, 7 );
208                         }
209                         return line_proc;
210                     } );
211 
212                     if( !i18n_help_texts.empty() ) {
213                         ui.on_screen_resize( nullptr );
214 
215                         const auto get_w_help_border = [&]() {
216                             init_windows( ui );
217                             return w_help_border;
218                         };
219 
220                         scrollable_text( get_w_help_border, _( " HELP " ),
221                                          std::accumulate( i18n_help_texts.begin() + 1, i18n_help_texts.end(),
222                                                           i18n_help_texts.front(),
223                         []( const std::string & lhs, const std::string & rhs ) {
224                             return lhs + "\n\n" + rhs;
225                         } ) );
226 
227                         ui.on_screen_resize( init_windows );
228                     }
229                     action = "CONFIRM";
230                     break;
231                 }
232             }
233         }
234     } while( action != "QUIT" );
235 }
236 
get_hint()237 std::string get_hint()
238 {
239     return SNIPPET.random_from_category( "hint" ).value_or( translation() ).translated();
240 }
241