1 #include "game.h" // IWYU pragma: associated
2
3 #include <algorithm>
4 #include <map>
5 #include <string>
6 #include <vector>
7
8 #include "avatar.h"
9 #include "calendar.h"
10 #include "color.h"
11 #include "debug.h"
12 #include "input.h"
13 #include "mission.h"
14 #include "npc.h"
15 #include "output.h"
16 #include "string_formatter.h"
17 #include "translations.h"
18 #include "ui.h"
19 #include "ui_manager.h"
20
list_missions()21 void game::list_missions()
22 {
23 catacurses::window w_missions;
24
25 enum class tab_mode : int {
26 TAB_ACTIVE = 0,
27 TAB_COMPLETED,
28 TAB_FAILED,
29 NUM_TABS,
30 FIRST_TAB = 0,
31 LAST_TAB = NUM_TABS - 1
32 };
33 tab_mode tab = tab_mode::FIRST_TAB;
34 size_t selection = 0;
35 int entries_per_page = 0;
36 input_context ctxt( "MISSIONS" );
37 ctxt.register_cardinal();
38 ctxt.register_action( "CONFIRM" );
39 ctxt.register_action( "QUIT" );
40 ctxt.register_action( "HELP_KEYBINDINGS" );
41
42 ui_adaptor ui;
43 ui.on_screen_resize( [&]( ui_adaptor & ui ) {
44 w_missions = new_centered_win( FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH );
45
46 // content ranges from y=3 to FULL_SCREEN_HEIGHT - 2
47 entries_per_page = FULL_SCREEN_HEIGHT - 4;
48
49 ui.position_from_window( w_missions );
50 } );
51 ui.mark_resize();
52
53 std::vector<mission *> umissions;
54
55 ui.on_redraw( [&]( const ui_adaptor & ) {
56 werase( w_missions );
57 // entries_per_page * page number
58 const int top_of_page = entries_per_page * ( selection / entries_per_page );
59 const int bottom_of_page =
60 std::min( top_of_page + entries_per_page - 1, static_cast<int>( umissions.size() ) - 1 );
61
62 for( int i = 3; i < FULL_SCREEN_HEIGHT - 1; i++ ) {
63 mvwputch( w_missions, point( 30, i ), BORDER_COLOR, LINE_XOXO );
64 }
65
66 const std::vector<std::pair<tab_mode, std::string>> tabs = {
67 { tab_mode::TAB_ACTIVE, _( "ACTIVE MISSIONS" ) },
68 { tab_mode::TAB_COMPLETED, _( "COMPLETED MISSIONS" ) },
69 { tab_mode::TAB_FAILED, _( "FAILED MISSIONS" ) },
70 };
71 draw_tabs( w_missions, tabs, tab );
72 draw_border_below_tabs( w_missions );
73 int x1 = 2, x2 = 2;
74 for( const std::pair<tab_mode, std::string> &t : tabs ) {
75 x2 = x1 + utf8_width( t.second ) + 1;
76 if( t.first == tab ) {
77 break;
78 }
79 x1 = x2 + 2;
80 }
81 mvwputch( w_missions, point( 30, 2 ), BORDER_COLOR, x1 < 30 && 30 < x2 ? ' ' : LINE_OXXX ); // ^|^*/
82 mvwputch( w_missions, point( 30, FULL_SCREEN_HEIGHT - 1 ), BORDER_COLOR, LINE_XXOX ); // _|_
83
84 draw_scrollbar( w_missions, selection, entries_per_page, umissions.size(), point( 0, 3 ) );
85
86 for( int i = top_of_page; i <= bottom_of_page; i++ ) {
87 mission *miss = umissions[i];
88 const nc_color col = u.get_active_mission() == miss ? c_light_green : c_white;
89 const int y = i - top_of_page + 3;
90 trim_and_print( w_missions, point( 1, y ), 28,
91 static_cast<int>( selection ) == i ? hilite( col ) : col,
92 miss->name() );
93 }
94
95 if( selection < umissions.size() ) {
96 mission *miss = umissions[selection];
97 const nc_color col = u.get_active_mission() == miss ? c_light_green : c_white;
98 std::string for_npc;
99 if( miss->get_npc_id().is_valid() ) {
100 npc *guy = g->find_npc( miss->get_npc_id() );
101 if( guy ) {
102 for_npc = string_format( _( " for %s" ), guy->disp_name() );
103 }
104 }
105
106 int y = 3;
107
108 auto format_tokenized_description = []( const std::string & description,
109 const std::vector<std::pair<int, itype_id>> &rewards ) {
110 std::string formatted_description = description;
111 for( const auto &reward : rewards ) {
112 std::string token = "<reward_count:" + reward.second.str() + ">";
113 formatted_description = string_replace( formatted_description, token, string_format( "%d",
114 reward.first ) );
115 }
116 return formatted_description;
117 };
118 y += fold_and_print( w_missions, point( 31, y ), getmaxx( w_missions ) - 33, col,
119 format_tokenized_description( miss->name(), miss->get_likely_rewards() ) + for_npc );
120 y++;
121 if( !miss->get_description().empty() ) {
122 y += fold_and_print( w_missions, point( 31, y ), getmaxx( w_missions ) - 33, c_white,
123 format_tokenized_description( miss->get_description(), miss->get_likely_rewards() ) );
124 }
125 if( miss->has_deadline() ) {
126 const time_point deadline = miss->get_deadline();
127 mvwprintz( w_missions, point( 31, ++y ), c_white, _( "Deadline: %s" ), to_string( deadline ) );
128
129 if( tab != tab_mode::TAB_COMPLETED ) {
130 // There's no point in displaying this for a completed mission.
131 // @ TODO: But displaying when you completed it would be useful.
132 const time_duration remaining = deadline - calendar::turn;
133 std::string remaining_time;
134
135 if( remaining <= 0_turns ) {
136 remaining_time = _( "None!" );
137 } else if( u.has_watch() ) {
138 remaining_time = to_string( remaining );
139 } else {
140 remaining_time = to_string_approx( remaining );
141 }
142
143 mvwprintz( w_missions, point( 31, ++y ), c_white, _( "Time remaining: %s" ), remaining_time );
144 }
145 }
146 if( miss->has_target() ) {
147 const tripoint_abs_omt pos = u.global_omt_location();
148 // TODO: target does not contain a z-component, targets are assumed to be on z=0
149 mvwprintz( w_missions, point( 31, ++y ), c_white, _( "Target: %s You: %s" ),
150 miss->get_target().to_string(), pos.to_string() );
151 }
152 } else {
153 static const std::map< tab_mode, std::string > nope = {
154 { tab_mode::TAB_ACTIVE, translate_marker( "You have no active missions!" ) },
155 { tab_mode::TAB_COMPLETED, translate_marker( "You haven't completed any missions!" ) },
156 { tab_mode::TAB_FAILED, translate_marker( "You haven't failed any missions!" ) }
157 };
158 mvwprintz( w_missions, point( 31, 4 ), c_light_red, _( nope.at( tab ) ) );
159 }
160
161 wnoutrefresh( w_missions );
162 } );
163
164 while( true ) {
165 umissions.clear();
166 if( tab < tab_mode::FIRST_TAB || tab >= tab_mode::NUM_TABS ) {
167 debugmsg( "The sanity check failed because tab=%d", static_cast<int>( tab ) );
168 tab = tab_mode::FIRST_TAB;
169 }
170 switch( tab ) {
171 case tab_mode::TAB_ACTIVE:
172 umissions = u.get_active_missions();
173 break;
174 case tab_mode::TAB_COMPLETED:
175 umissions = u.get_completed_missions();
176 break;
177 case tab_mode::TAB_FAILED:
178 umissions = u.get_failed_missions();
179 break;
180 default:
181 break;
182 }
183 if( ( !umissions.empty() && selection >= umissions.size() ) ||
184 ( umissions.empty() && selection != 0 ) ) {
185 debugmsg( "Sanity check failed: selection=%d, size=%d", static_cast<int>( selection ),
186 static_cast<int>( umissions.size() ) );
187 selection = 0;
188 }
189 ui_manager::redraw();
190 const std::string action = ctxt.handle_input();
191 if( action == "RIGHT" ) {
192 tab = static_cast<tab_mode>( static_cast<int>( tab ) + 1 );
193 if( tab >= tab_mode::NUM_TABS ) {
194 tab = tab_mode::FIRST_TAB;
195 }
196 selection = 0;
197 } else if( action == "LEFT" ) {
198 tab = static_cast<tab_mode>( static_cast<int>( tab ) - 1 );
199 if( tab < tab_mode::FIRST_TAB ) {
200 tab = tab_mode::LAST_TAB;
201 }
202 selection = 0;
203 } else if( action == "DOWN" ) {
204 selection++;
205 if( selection >= umissions.size() ) {
206 selection = 0;
207 }
208 } else if( action == "UP" ) {
209 if( selection == 0 ) {
210 selection = umissions.empty() ? 0 : umissions.size() - 1;
211 } else {
212 selection--;
213 }
214 } else if( action == "CONFIRM" ) {
215 if( tab == tab_mode::TAB_ACTIVE && selection < umissions.size() ) {
216 u.set_active_mission( *umissions[selection] );
217 }
218 break;
219 } else if( action == "QUIT" ) {
220 break;
221 }
222 }
223 }
224