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