1 /*
2 Rewrite of quest log with new context menu features.
3
4 Author bluap/pjbroad Feb 2010
5 */
6
7 #include <numeric>
8 #include <string>
9 #include <vector>
10 #include <map>
11 #include <queue>
12 #include <set>
13 #include <cctype>
14 #include <algorithm>
15 #include <iostream>
16 #include <fstream>
17 #include <sstream>
18 #include <cmath> // for std::round()
19
20 #include "asc.h"
21 #include "context_menu.h"
22 #include "dialogues.h"
23 #include "elconfig.h"
24 #include "elwindows.h"
25 #include "errors.h"
26 #include "gamewin.h"
27 #include "gl_init.h"
28 #include "hud.h"
29 #include "icon_window.h"
30 #include "init.h"
31 #include "loginwin.h"
32 #include "io/elpathwrapper.h"
33 #ifdef JSON_FILES
34 #include "json_io.h"
35 #endif
36 #include "multiplayer.h"
37 #include "notepad.h"
38 #include "paste.h"
39 #include "questlog.h"
40 #include "sound.h"
41 #include "translate.h"
42
43 /*
44 * TODO Possible changes...
45 * Make questlog file xml?
46 * Remove save options - always save / timed save?
47 * Replace entry containers with classes
48 * Quest lists
49 * Space title retrieve by a few seconds
50 * Context menu
51 * refresh list?
52 * refresh highlighted title string?
53 * copy highlighted title to clipboard
54 * delete highlighted
55 * Feature to set the quest index for quest entries
56 * output md5sums with quest index
57 * import md5sums with quest index and set matches
58 */
59
60 using namespace eternal_lands;
61
62 // An individual quest.
63 //
64 class Quest
65 {
66 public:
Quest(Uint16 id)67 Quest(Uint16 id) : is_completed(false), title("") { this->id = id; }
68 Quest(const std::string & line);
get_title(void) const69 const std::string &get_title(void) const { return title; }
set_title(const char * new_title)70 void set_title(const char* new_title) { title = new_title; }
get_completed(void) const71 bool get_completed(void) const { return is_completed; }
set_completed(bool yes_complete)72 void set_completed(bool yes_complete) { is_completed = yes_complete; }
get_id(void) const73 Uint16 get_id(void) const { return id; }
write(std::ostream & out) const74 void write(std::ostream & out) const
75 { out << id << " " << is_completed << " " << title << std::endl; }
76 static const Uint16 UNSET_ID;
77 private:
78 Uint16 id;
79 bool is_completed;
80 std::string title;
81 };
82
83 const Uint16 Quest::UNSET_ID = static_cast<Uint16>(-1);
84
85
86 // Construct a quest object from a string - probably read from a file.
87 //
Quest(const std::string & line)88 Quest::Quest(const std::string & line)
89 {
90 std::istringstream ss(line);
91 id = Quest::UNSET_ID;
92 is_completed = false;
93 ss >> id;
94 ss >> is_completed;
95 getline(ss, title);
96 // trim any leading or trailing space from title
97 std::string::size_type start = title.find_first_not_of(' ');
98 std::string::size_type end = title.find_last_not_of(' ');
99 if (start == std::string::npos)
100 title = "";
101 else
102 title = title.substr(start,end-start+1);
103 }
104
105
106 // Used to track requests for a quest title from the server
107 //
108 class Quest_Title_Request
109 {
110 public:
Quest_Title_Request(Uint16 req_id)111 Quest_Title_Request(Uint16 req_id) : id(req_id), requested(false) {}
get_id(void) const112 Uint16 get_id(void) const { return id; }
113 void request(void);
been_requested(void) const114 bool been_requested(void) const { return requested; }
is_too_old(void) const115 bool is_too_old(void) const { return ((SDL_GetTicks() - request_time) > 5000); }
116 private:
117 Uint16 id;
118 Uint32 request_time;
119 bool requested;
120 };
121
122
123 // A single entry for the questlog.
124 class Quest_Entry
125 {
126 public:
127 static int content_width;
128 static float content_zoom;
129
Quest_Entry(void)130 Quest_Entry(void) : deleted(false), quest_id(Quest::UNSET_ID), charsum(0) {}
131 void set(const ustring& the_text);
132 void set(const ustring& the_text, const ustring& the_npc);
133 const std::vector<ustring> & get_lines(void) const;
134 void save(std::ofstream & out) const;
135 bool contains_string(const char *text_to_find) const;
get_npc(void) const136 const ustring & get_npc(void) const { return npc; }
get_charsum(void) const137 Uint16 get_charsum(void) const { return charsum; }
set_id(Uint16 id)138 void set_id(Uint16 id) { quest_id = id; }
get_id(void) const139 Uint16 get_id(void) const { return quest_id; }
set_deleted(bool is_deleted)140 void set_deleted(bool is_deleted) { deleted = is_deleted; update_displayed_npc_name(); }
get_deleted(void) const141 bool get_deleted(void) const { return deleted; }
get_disp_npc(void) const142 const ustring & get_disp_npc(void) const { return disp_npc; };
143
144 /*!
145 * Recalculate the line breaks
146 *
147 * Recalculate the line breaks in the text for this entry after a font
148 * change or a change in the UI scale.
149 */
150 void rewrap_lines();
151 private:
152 static const unsigned char NPC_NAME_COLOUR;
153 static const std::vector<ustring> deleted_line;
154 static const ustring npc_spacer;
155
156 bool deleted;
157 std::vector<ustring> _lines;
158 ustring npc;
159 ustring disp_npc;
160 Uint16 quest_id;
161 Uint16 charsum;
162
163 void set_lines(const ustring& the_text);
164 void update_displayed_npc_name();
165 };
166
167 const unsigned char Quest_Entry::NPC_NAME_COLOUR = to_color_char(c_blue2);
168 const std::vector<ustring> Quest_Entry::deleted_line(1,
169 to_color_char(c_grey2) + ustring(reinterpret_cast<const unsigned char*>(questlog_deleted_str)));
170 const ustring Quest_Entry::npc_spacer = reinterpret_cast<const unsigned char*>(": ");
171 // Will be reset from Questlog_Window::ui_scale_handler
172 int Quest_Entry::content_width = window_width;
173 float Quest_Entry::content_zoom = 1.0;
174
175 // Ask the server for the title this quest.
176 //
request(void)177 void Quest_Title_Request::request(void)
178 {
179 if (requested)
180 return;
181 Uint8 str[10];
182 //char buf [80];
183 //safe_snprintf(buf, 80, "Sending WHAT_QUEST_IS_THIS_ID with id=%d", id);
184 //LOG_TO_CONSOLE(c_green2,buf);
185 str[0]=WHAT_QUEST_IS_THIS_ID;
186 *((Uint16 *)(str+1)) = SDL_SwapLE16((Uint16)id);
187 my_tcp_send (my_socket, str, 3);
188 request_time = SDL_GetTicks();
189 requested = true;
190 }
191
192
193 // Needed to sort quests with "Show all" first.
194 //
195 class QuestCompare {
196 public:
operator ()(const Quest & x,const Quest & y) const197 bool operator()(const Quest& x, const Quest& y) const
198 {
199 if (x.get_id() == Quest::UNSET_ID)
200 return true;
201 else if (y.get_id() == Quest::UNSET_ID)
202 return false;
203 else
204 return x.get_id() < y.get_id();
205 }
206 };
207
208
209 // Class to hold and tests position information for a shown entry.
210 //
211 class Shown_Entry
212 {
213 public:
Shown_Entry(std::vector<std::string>::size_type entry_m,int start_y_m,int end_y_m)214 Shown_Entry(std::vector<std::string>::size_type entry_m, int start_y_m, int end_y_m)
215 : entry(entry_m), start_y(start_y_m), end_y(end_y_m) {}
get_entry(void) const216 std::vector<std::string>::size_type get_entry(void) const { return entry; }
is_over(int pos_y) const217 bool is_over(int pos_y) const { return ((pos_y > start_y) && (pos_y < end_y)); }
218 private:
219 std::vector<std::string>::size_type entry;
220 int start_y, end_y;
221 };
222
223
224 static std::vector<Quest_Entry> quest_entries;
225 static std::vector<size_t> active_entries;
226 static std::set<size_t> selected_entries;
227 static std::vector<Shown_Entry> shown_entries;
228 static enum { QLFLT_NONE=0, QLFLT_QUEST, QLFLT_NPC, QLFLT_SEL } active_filter = QLFLT_NONE;
229
230
231 // A list of Quests
232 //
233 class Quest_List
234 {
235 public:
Quest_List(void)236 Quest_List(void) : save_needed(false), iter_set(false), max_title(0),
237 selected_id(Quest::UNSET_ID), highlighted_id(Quest::UNSET_ID),
238 win_id(-1), scroll_id(0), mouseover_y(-1), clicked(false), cm_id(CM_INIT_VALUE),
239 no_auto_open(0), hide_completed(0), list_left_of_entries(0), quest_completed(0), number_shown(0),
240 spacer(0), linesep(0), next_entry_quest_id(Quest::UNSET_ID) {}
241 void add(Uint16 id);
242 void set_requested_title(const char* title);
243 void load(void);
244 void save(void);
245 void set_completed(Uint16 id, bool is_complete);
set_selected(Uint16 id)246 void set_selected(Uint16 id) { selected_id = id; }
get_selected(void) const247 Uint16 get_selected(void) const { return selected_id; }
248 void open_window(void);
get_win_id(void) const249 int get_win_id(void) const { return win_id; }
250 void scroll_to_selected(void);
set_mouseover_y(int val)251 void set_mouseover_y(int val) { mouseover_y = val; }
252 void recalc_num_shown(void);
253 unsigned int get_options(void) const;
254 void set_options(unsigned int options);
255 #ifdef JSON_FILES
256 void write_options(const char *dict_name) const;
257 void read_options(const char *dict_name);
258 #endif
set_highlighted(Uint16 id)259 void set_highlighted(Uint16 id) { highlighted_id = id; }
get_highlighted(void) const260 Uint16 get_highlighted(void) const { return highlighted_id; }
clear_highlighted(void)261 void clear_highlighted(void) { set_highlighted(Quest::UNSET_ID); }
262 void cm_pre_show_handler(void);
cm_active(void) const263 bool cm_active(void) const { return ((cm_id != CM_INIT_VALUE) && (cm_window_shown() == cm_id)); }
264 void check_title_requests(void);
265 void ui_scale_handler(window_info *win);
266 int font_change_handler(window_info *win, FontManager::Category cat);
check_auto_open(void)267 void check_auto_open(void) { if (!no_auto_open && !get_show_window(get_win_id())) open_window(); }
268 void display_handler(window_info *win);
269 void click_handler(window_info *win, Uint32 flags);
270 void cm_handler(int option);
271 void resize_handler(window_info *win);
get_next_id(void) const272 Uint16 get_next_id(void) const { return next_entry_quest_id; }
set_next_id(Uint16 id)273 void set_next_id(Uint16 id) { next_entry_quest_id = id; }
clear_next_id(void)274 void clear_next_id(void) { next_entry_quest_id = Quest::UNSET_ID; }
waiting_for_entry(void)275 int waiting_for_entry(void) { return (next_entry_quest_id != Quest::UNSET_ID) ?1 :0; }
276 private:
277 static const std::string _empty_title_replacement;
278
get_scroll_id(void) const279 int get_scroll_id(void) const { return scroll_id; }
280 void showall(void);
281 bool get_completed(Uint16 id) const;
toggle_completed(Uint16 id)282 void toggle_completed(Uint16 id) { set_completed(id, !get_completed(id)); }
num_shown(void) const283 size_t num_shown(void) const { return number_shown; }
284 const Quest * get_first_quest(int offset);
285 const Quest * get_next_quest(void);
get_max_title(void) const286 size_t get_max_title(void) const { return max_title; }
287 /*!
288 * Return the maximum width of a quest title when drawn in the current font
289 * with scale \a zoom.
290 */
291 int get_max_title_width(float zoom) const;
get_mouseover_y(void) const292 int get_mouseover_y(void) const { return mouseover_y; }
has_mouseover(void) const293 bool has_mouseover(void) const { return mouseover_y != -1; }
clear_mouseover(void)294 void clear_mouseover(void) { mouseover_y = -1; }
was_clicked(void) const295 bool was_clicked(void) const { return clicked; }
set_clicked(bool val)296 bool set_clicked(bool val) { return clicked = val; }
297 std::map <Uint16,Quest,QuestCompare> quests;
298 std::queue <Quest_Title_Request> title_requests;
299 bool save_needed;
300 std::string list_filename;
301 std::map <Uint16,Quest,QuestCompare>::const_iterator iter;
302 bool iter_set;
303 size_t max_title;
304 Uint16 selected_id;
305 Uint16 highlighted_id;
306 int win_id;
307 int scroll_id;
308 int mouseover_y;
309 bool clicked;
310 size_t cm_id;
311 // use logical sense so zero/false value is off
312 int no_auto_open;
313 int hide_completed;
314 int list_left_of_entries;
315 int quest_completed;
316 size_t number_shown;
317 int spacer;
318 int linesep;
319 Uint16 next_entry_quest_id;
320 enum { CMQL_COMPLETED=0, CMQL_ADDSEL, CMQL_S11, CMQL_HIDECOMPLETED, CMQL_NOAUTOOPEN, CMQL_LISTLEFTOFENTRIES };
321 };
322
323 const std::string Quest_List::_empty_title_replacement = "???";
324
325 static Quest_List questlist;
326
display_questlist_handler(window_info * win)327 static int display_questlist_handler(window_info *win)
328 { questlist.display_handler(win); return 1; }
click_questlist_handler(window_info * win,int mx,int my,Uint32 flags)329 static int click_questlist_handler(window_info *win, int mx, int my, Uint32 flags)
330 { questlist.click_handler(win, flags); return 1; }
resize_questlist_handler(window_info * win,int new_width,int new_height)331 static int resize_questlist_handler(window_info *win, int new_width, int new_height)
332 { questlist.resize_handler(win); return 0; }
mouseover_questlist_handler(window_info * win,int mx,int my)333 static int mouseover_questlist_handler(window_info *win, int mx, int my)
334 { if ((my>=0) && (mx<win->len_x-win->box_size)) questlist.set_mouseover_y(my); return 0; }
ui_scale_questlist_handler(window_info * win)335 static int ui_scale_questlist_handler(window_info *win)
336 { questlist.ui_scale_handler(win); return 1; }
change_questlist_font_handler(window_info * win,font_cat cat)337 static int change_questlist_font_handler(window_info *win, font_cat cat)
338 { return questlist.font_change_handler(win, cat); }
cm_questlist_handler(window_info * win,int widget_id,int mx,int my,int option)339 static int cm_questlist_handler(window_info *win, int widget_id, int mx, int my, int option)
340 { questlist.cm_handler(option); return 1; }
cm_questlist_pre_show_handler(window_info * win,int widget_id,int mx,int my,window_info * cm_win)341 static void cm_questlist_pre_show_handler(window_info *win, int widget_id, int mx, int my, window_info *cm_win)
342 { questlist.cm_pre_show_handler(); }
343
344
345 // Container class for quest log
346 //
347 class Questlog_Container
348 {
349 public:
Questlog_Container(void)350 Questlog_Container(void) : need_to_save(false) {}
351 void add_entry(const unsigned char *t, int len);
352 void load(void);
353 void save(void);
354 void show_all_entries(void);
355 void rebuild_active_entries(size_t desired_top_entry);
set_save(void)356 void set_save(void) { need_to_save = true; }
save_needed(void) const357 bool save_needed(void) const { return need_to_save; }
358 private:
359 void add_line(const ustring& t, const unsigned char *npcprefix);
360 std::string filename;
361 bool need_to_save;
362 };
363
364 static Questlog_Container questlog_container;
365
366
367 // Class for main questlog window
368 //
369 class Questlog_Window
370 {
371 public:
Questlog_Window(void)372 Questlog_Window(void) : qlwinwidth(0), qlwinheight(0), qlborder(0), spacer(0), win_space(0), linesep(0),
373 mouse_over_questlog(false), quest_scroll_id(14), current_action(-1), prompt_for_add_text(false),
374 adding_insert_pos(0), cm_questlog_id(CM_INIT_VALUE), cm_questlog_over_entry(static_cast<size_t>(-1)),
375 current_line(0) {}
376 void open(void);
377 int display_handler(window_info *win);
378 int click_handler(window_info *win, Uint32 flags);
379 int keypress_handler(window_info *win, SDL_Keycode key_code, Uint32 key_unicode, Uint16 key_mod);
380 void mouseover_handler(int my);
381 void ui_scale_handler(window_info *win);
382 int font_change_handler(window_info *win, FontManager::Category cat);
383 void scroll_click_handler(widget_list *widget);
384 void scroll_drag_handler(widget_list *widget);
385 void cm_handler(window_info *win, int my, int option);
386 void cm_preshow_handler(int my);
get_win_space(void) const387 int get_win_space(void) const { return win_space; }
update_scrollbar_len(void)388 void update_scrollbar_len(void)
389 { if (get_id_MW(MW_QUESTLOG) >= 0) vscrollbar_set_bar_len (get_id_MW(MW_QUESTLOG), quest_scroll_id, (active_entries.empty()) ?0 :active_entries.size()-1); }
390 void add_npc_input_handler(const unsigned char *input_text, void *data);
391 void add_text_input_handler(const unsigned char *input_text, void *data);
cancel_action(void)392 void cancel_action(void) { current_action = -1; }
393 void find_input_handler(const char *input_text, void *data);
394 void goto_entry(int ln);
get_current_entry(void) const395 size_t get_current_entry(void) const { return (current_line < active_entries.size()) ?active_entries[current_line] :0; }
396 private:
397 void find_in_entry(window_info *win);
398 void delete_entry(size_t entry);
399 void undelete_entry(size_t entry);
400 void delete_duplicates(void);
401 void copy_entry(size_t entry);
402 void copy_all_entries(void);
403 void copy_one_entry(std::string ©_str, size_t entry);
404 void add_entry(window_info *win, size_t entry);
405 void add_text_input(window_info *win);
406 void draw_underline(int startx, int starty, int endx, int endy);
407 // set in ui_scale_handler()
408 int qlwinwidth;
409 int qlwinheight;
410 int qlborder;
411 int spacer;
412 int win_space;
413 int linesep;
414 // others
415 bool mouse_over_questlog;
416 int quest_scroll_id;
417 int current_action;
418 bool prompt_for_add_text;
419 size_t adding_insert_pos;
420 ustring adding_npc;
421 INPUT_POPUP ipu_questlog;
422 size_t cm_questlog_id;
423 size_t cm_questlog_over_entry;
424 size_t current_line;
425 enum { CMQL_SHOWALL=0, CMQL_QUESTFILTER, CMQL_NPCFILTER, CMQL_NPCSHOWNONE,
426 CMQL_JUSTTHISNPC, CMQL_JUSTTHISQUEST, CMQL_S01,
427 CMQL_COPY, CMQL_COPYALL, CMQL_FIND, CMQL_ADD, CMQL_S02,
428 CMQL_SEL, CMQL_UNSEL, CMQL_SELALL, CMQL_UNSELALL, CMQL_SHOWSEL, CMQL_S03,
429 CMQL_DELETE, CMQL_UNDEL, CMQL_S04, CMQL_DEDUPE, CMQL_S05, CMQL_SAVE };
430 };
431
432 static Questlog_Window questlog_window;
433
display_questlog_handler(window_info * win)434 static int display_questlog_handler(window_info *win) { return questlog_window.display_handler(win); }
questlog_click(window_info * win,int mx,int my,Uint32 flags)435 static int questlog_click(window_info *win, int mx, int my, Uint32 flags) { return questlog_window.click_handler(win, flags); }
keypress_questlog_handler(window_info * win,int mx,int my,SDL_Keycode key_code,Uint32 key_unicode,Uint16 key_mod)436 static int keypress_questlog_handler(window_info *win, int mx, int my, SDL_Keycode key_code, Uint32 key_unicode, Uint16 key_mod)
437 { return questlog_window.keypress_handler(win, key_code, key_unicode, key_mod); }
mouseover_questlog_handler(window_info * win,int mx,int my)438 static int mouseover_questlog_handler(window_info *win, int mx, int my) { questlog_window.mouseover_handler(my); return 0; }
show_questlog_handler(window_info * win)439 static int show_questlog_handler(window_info *win) { questlist.check_auto_open(); return 0; }
ui_scale_questlog_handler(window_info * win)440 static int ui_scale_questlog_handler(window_info *win) { questlog_window.ui_scale_handler(win); return 1; }
change_questlog_font_handler(window_info * win,font_cat cat)441 static int change_questlog_font_handler(window_info *win, font_cat cat) { return questlog_window.font_change_handler(win, cat); }
questlog_scroll_click(widget_list * widget,int mx,int my,Uint32 flags)442 static int questlog_scroll_click (widget_list *widget, int mx, int my, Uint32 flags) { questlog_window.scroll_click_handler(widget); return 1; }
questlog_scroll_drag(widget_list * widget,int mx,int my,Uint32 flags,int dx,int dy)443 static int questlog_scroll_drag (widget_list *widget, int mx, int my, Uint32 flags, int dx, int dy) { questlog_window.scroll_drag_handler(widget); return 1; }
cm_quest_handler(window_info * win,int widget_id,int mx,int my,int option)444 static int cm_quest_handler(window_info *win, int widget_id, int mx, int my, int option) { questlog_window.cm_handler(win, my, option); return 1; }
cm_questlog_pre_show_handler(window_info * win,int widget_id,int mx,int my,window_info * cm_win)445 static void cm_questlog_pre_show_handler(window_info *win, int widget_id, int mx, int my, window_info *cm_win) { questlog_window.cm_preshow_handler(my); }
questlog_input_cancel_handler(void * data)446 static void questlog_input_cancel_handler(void *data) { questlog_window.cancel_action(); }
questlog_add_npc_input_handler(const char * input_text,void * data)447 static void questlog_add_npc_input_handler(const char *input_text, void *data) { questlog_window.add_npc_input_handler(reinterpret_cast<const unsigned char*>(input_text), data); }
questlog_add_text_input_handler(const char * input_text,void * data)448 static void questlog_add_text_input_handler(const char *input_text, void *data) { questlog_window.add_text_input_handler(reinterpret_cast<const unsigned char*>(input_text), data); }
questlog_find_input_handler(const char * input_text,void * data)449 static void questlog_find_input_handler(const char *input_text, void *data) { questlog_window.find_input_handler(input_text, data); }
450
451
452 // A class to impliment the NPC filter window
453 //
454 class NPC_Filter
455 {
456 public:
NPC_Filter(void)457 NPC_Filter(void) : npc_name_space(0), npc_name_border(0), npc_name_box_size(0), max_npc_name_x(0), max_npc_name_y(0),
458 min_npc_name_cols(1), min_npc_name_rows(10), npc_name_cols(0), npc_name_rows(0),
459 npc_filter_active_npc_name(static_cast<size_t>(-1)), npc_filter_win(-1) {}
460 void open_window(void);
461 void resize_handler(window_info *win);
462 void ui_scale_handler(window_info *win);
463 int font_change_handler(window_info *win, FontManager::Category cat);
464 void display_handler(window_info *win);
465 int click_handler(window_info *win, int mx, int my, Uint32 flags);
466 int keypress_handler(window_info *win, int mx, int my, SDL_Keycode key_code, Uint32 key_unicode, Uint16 key_mod);
467 void mouseover_handler(window_info *win, int mx, int my);
get_win_id(void) const468 int get_win_id(void) const { return npc_filter_win; }
is_set(const ustring & npc)469 bool is_set(const ustring& npc) { return (npc_filter_map[npc] == 1); }
set(const ustring & npc)470 void set(const ustring& npc) { npc_filter_map[npc] = 1; }
set_all(void)471 void set_all(void) { for (auto& i: npc_filter_map) i.second = 1; }
unset_all(void)472 void unset_all(void) { for (auto& i: npc_filter_map) i.second = 0; }
473 private:
474 // scaled
475 int npc_name_space;
476 int npc_name_border;
477 int npc_name_box_size;
478 float max_npc_name_x;
479 float max_npc_name_y;
480 // other
481 const unsigned int min_npc_name_cols;
482 const unsigned int min_npc_name_rows;
483 unsigned int npc_name_cols;
484 unsigned int npc_name_rows;
485 size_t npc_filter_active_npc_name;
486 int npc_filter_win;
487 std::map<ustring, int> npc_filter_map;
488 };
489
490 static NPC_Filter npc_filter;
491
resize_npc_filter_handler(window_info * win,int new_width,int new_height)492 static int resize_npc_filter_handler(window_info *win, int new_width, int new_height)
493 { npc_filter.resize_handler(win); return 0; }
ui_scale_npc_filter_handler(window_info * win)494 static int ui_scale_npc_filter_handler(window_info *win)
495 { npc_filter.ui_scale_handler(win); return 1; }
change_npc_filter_font_handler(window_info * win,font_cat cat)496 static int change_npc_filter_font_handler(window_info *win, font_cat cat)
497 { return npc_filter.font_change_handler(win, cat); }
display_npc_filter_handler(window_info * win)498 static int display_npc_filter_handler(window_info *win)
499 { npc_filter.display_handler(win); return 1; }
click_npc_filter_handler(window_info * win,int mx,int my,Uint32 flags)500 static int click_npc_filter_handler(window_info *win, int mx, int my, Uint32 flags)
501 { return npc_filter.click_handler(win, mx, my, flags); }
keypress_npc_filter_handler(window_info * win,int mx,int my,SDL_Keycode key_code,Uint32 key_unicode,Uint16 key_mod)502 static int keypress_npc_filter_handler(window_info *win, int mx, int my, SDL_Keycode key_code, Uint32 key_unicode, Uint16 key_mod)
503 { return npc_filter.keypress_handler(win, mx, my, key_code, key_unicode, key_mod); }
mouseover_npc_filter_handler(window_info * win,int mx,int my)504 static int mouseover_npc_filter_handler(window_info *win, int mx, int my)
505 { npc_filter.mouseover_handler(win, mx, my); return 0; }
506
507
508 // Draw a context menu like hightlight using the supplied coords.
509 //
draw_highlight(int topleftx,int toplefty,int widthx,int widthy,size_t col)510 void draw_highlight(int topleftx, int toplefty, int widthx, int widthy, size_t col)
511 {
512 float colours[2][2][3] = { { {gui_invert_color[0], gui_invert_color[1], gui_invert_color[2]}, {gui_color[0], gui_color[1], gui_color[2]} },
513 { {0.11, 0.11f, 0.11f}, {0.33, 0.42f, 0.70f} } };
514 if (col > 1)
515 col = 0;
516 glDisable(GL_TEXTURE_2D);
517 glBegin(GL_QUADS);
518 glColor3fv(colours[col][0]);
519 glVertex2i(topleftx, toplefty);
520 glColor3fv(colours[col][1]);
521 glVertex2i(topleftx, toplefty + widthy);
522 glVertex2i(topleftx + widthx, toplefty + widthy);
523 glColor3fv(colours[col][0]);
524 glVertex2i(topleftx + widthx, toplefty);
525 glEnd();
526 glEnable(GL_TEXTURE_2D);
527 #ifdef OPENGL_TRACE
528 CHECK_GL_ERRORS();
529 #endif //OPENGL_TRACE
530 }
531
532 // Calculate the maximum string width of a title in the list
get_max_title_width(float zoom) const533 int Quest_List::get_max_title_width(float zoom) const
534 {
535 int max_width = 0;
536 for (const auto& iter: quests)
537 {
538 const std::string& title = iter.second.get_title().empty()
539 ? _empty_title_replacement : iter.second.get_title();
540 int width = FontManager::get_instance().line_width(UI_FONT,
541 reinterpret_cast<const unsigned char*>(title.c_str()), title.size(), zoom);
542 max_width = std::max(max_width, width);
543 }
544 return max_width;
545 }
546
547 // Add any new quest object to the list and request the title.
548 //
add(Uint16 id)549 void Quest_List::add(Uint16 id)
550 {
551 if ((quests.find(id) != quests.end()) || (id == Quest::UNSET_ID))
552 return;
553 quests.insert( std::make_pair( id, Quest(id)) );
554 save_needed = true;
555 title_requests.push(id);
556 recalc_num_shown();
557 if (title_requests.size() == 1)
558 title_requests.front().request();
559 }
560
561
562 // Mark quest completed, making a quest object if unknown so far.
563 //
set_completed(Uint16 id,bool is_complete)564 void Quest_List::set_completed(Uint16 id, bool is_complete)
565 {
566 if (id == Quest::UNSET_ID)
567 return;
568 std::map<Uint16,Quest,QuestCompare>::iterator i = quests.find(id);
569 if (i != quests.end())
570 i->second.set_completed(is_complete);
571 else
572 {
573 add(id);
574 i = quests.find(id);
575 if (i != quests.end())
576 i->second.set_completed(is_complete);
577 }
578 recalc_num_shown();
579 save_needed = true;
580 }
581
582
583 // Return true if the specified quest is known and its marked complete
get_completed(Uint16 id) const584 bool Quest_List::get_completed(Uint16 id) const
585 {
586 if (id == Quest::UNSET_ID)
587 return false;
588 std::map<Uint16,Quest,QuestCompare>::const_iterator i = quests.find(id);
589 if (i != quests.end())
590 return i->second.get_completed();
591 return false;
592 }
593
594
595 // Have received a title so set the quest from the front of the request queue.
596 //
set_requested_title(const char * title)597 void Quest_List::set_requested_title(const char* title)
598 {
599 if (title_requests.empty())
600 {
601 std::cerr << "Received title [" << title << "] but not requested" << std::endl;
602 return;
603 }
604 std::map<Uint16,Quest,QuestCompare>::iterator i = quests.find(title_requests.front().get_id());
605 if (i != quests.end())
606 {
607 i->second.set_title(title);
608 if (i->second.get_title().size() > max_title)
609 max_title = i->second.get_title().size();
610 title_requests.pop();
611 save_needed = true;
612 if (!title_requests.empty())
613 title_requests.front().request();
614 }
615 }
616
617
618 // Load quests from file, creating objects as needed.
619 //
load(void)620 void Quest_List::load(void)
621 {
622 if (!quests.empty())
623 {
624 save();
625 return;
626 }
627
628 // Add the "show all" entry
629 Quest showall(Quest::UNSET_ID);
630 showall.set_title(questlist_showall_str);
631 quests.insert( std::make_pair( Quest::UNSET_ID, showall) );
632 max_title = showall.get_title().size();
633
634 list_filename = std::string(get_path_config()) + "quest_" + std::string(get_lowercase_username()) + ".list";
635 recalc_num_shown();
636
637 std::ifstream in(list_filename.c_str());
638 if (!in)
639 return;
640
641 std::string line;
642 while (getline(in, line))
643 {
644 Quest new_quest(line);
645 if (new_quest.get_id() != Quest::UNSET_ID)
646 {
647 quests.insert( std::make_pair( new_quest.get_id(), new_quest) );
648 std::map<Uint16,Quest,QuestCompare>::iterator i = quests.find(new_quest.get_id());
649 if ((i != quests.end()))
650 {
651 if (i->second.get_title().empty())
652 {
653 title_requests.push(i->first);
654 if (title_requests.size() == 1)
655 title_requests.front().request();
656 }
657 else if (i->second.get_title().size() > max_title)
658 max_title = i->second.get_title().size();
659 }
660 }
661 }
662
663 save_needed = false;
664 recalc_num_shown();
665 }
666
667
668 // Save the list of quests.
669 //
save(void)670 void Quest_List::save(void)
671 {
672 if (!save_needed)
673 return;
674 std::ofstream out(list_filename.c_str(), std::ios_base::out | std::ios_base::trunc);
675 if (!out)
676 {
677 std::string error_str = std::string(file_write_error_str) + ' ' + list_filename;
678 LOG_TO_CONSOLE(c_red2, error_str.c_str());
679 LOG_ERROR("%s: %s \"%s\"\n", reg_error_str, file_write_error_str, list_filename.c_str());
680 return;
681 }
682 for (std::map<Uint16,Quest,QuestCompare>::const_iterator i=quests.begin(); i!=quests.end(); ++i)
683 if (i->first != Quest::UNSET_ID)
684 i->second.write(out);
685 save_needed = false;
686 }
687
688
689 // Used for debug, write the list of quests to std out.
690 //
showall(void)691 void Quest_List::showall(void)
692 {
693 for (std::map<Uint16,Quest,QuestCompare>::const_iterator i=quests.begin(); i!=quests.end(); ++i)
694 i->second.write(std::cout);
695 }
696
697
698 // Allow readonly access to quests, start from the first
699 //
get_first_quest(int offset)700 const Quest * Quest_List::get_first_quest(int offset)
701 {
702 if (quests.empty())
703 return 0;
704 iter = quests.begin();
705 for (int i=0; i<offset; i++)
706 {
707 if (++iter == quests.end())
708 return 0;
709 if (hide_completed)
710 while (iter->second.get_completed())
711 if (++iter == quests.end())
712 return 0;
713 }
714 iter_set = true;
715 return &iter->second;
716 }
717
718
719 // Allow readonly access to quests, continue with next
720 //
get_next_quest(void)721 const Quest * Quest_List::get_next_quest(void)
722 {
723 if (!iter_set)
724 return get_first_quest(0);
725 ++iter;
726 if (hide_completed)
727 while (iter != quests.end() && iter->second.get_completed())
728 ++iter;
729 if (iter != quests.end())
730 return &iter->second;
731 iter_set = false;
732 return 0;
733 }
734
735
736 // Recalulate the number of quests that can be shown, it varies if hiding is enabled.
737 //
recalc_num_shown(void)738 void Quest_List::recalc_num_shown(void)
739 {
740 if (hide_completed)
741 {
742 number_shown = 0;
743 for (std::map<Uint16,Quest,QuestCompare>::const_iterator i=quests.begin(); i!=quests.end(); ++i)
744 if (!i->second.get_completed())
745 number_shown++;
746 }
747 else
748 number_shown = quests.size();
749 }
750
751
752 // Return all the options values as bits in the word.
753 //
get_options(void) const754 unsigned int Quest_List::get_options(void) const
755 {
756 unsigned int options = 0;
757 options |= (no_auto_open) ?1: 0;
758 options |= ((hide_completed) ?1: 0) << 1;
759 options |= ((list_left_of_entries) ?1: 0) << 2;
760 return options;
761 }
762
763
764 // Get the options from the bits of the word.
765 //
set_options(unsigned int options)766 void Quest_List::set_options(unsigned int options)
767 {
768 no_auto_open = options & 1;
769 hide_completed = (options >> 1) & 1;
770 list_left_of_entries = (options >> 2) & 1;
771 }
772
773
774 #ifdef JSON_FILES
write_options(const char * dict_name) const775 void Quest_List::write_options(const char *dict_name) const
776 {
777 json_cstate_set_bool(dict_name, "no_auto_open", no_auto_open);
778 json_cstate_set_bool(dict_name, "hide_completed", hide_completed);
779 json_cstate_set_bool(dict_name, "list_left_of_entries", list_left_of_entries);
780 }
781
782
read_options(const char * dict_name)783 void Quest_List::read_options(const char *dict_name)
784 {
785 no_auto_open = json_cstate_get_bool(dict_name, "no_auto_open", 0);
786 hide_completed = json_cstate_get_bool(dict_name, "hide_completed", 0);
787 list_left_of_entries = json_cstate_get_bool(dict_name, "list_left_of_entries", 0);
788 }
789 #endif
790
791
792 // Check the title request queue, remove stalled requests, request new ones.
793 //
check_title_requests(void)794 void Quest_List::check_title_requests(void)
795 {
796 if (title_requests.empty())
797 return;
798 if (title_requests.front().been_requested())
799 {
800 if (title_requests.front().is_too_old())
801 title_requests.pop();
802 }
803 else
804 title_requests.front().request();
805 }
806
807
808 // Display the quest list, highlighing any selected and one with mouse over.
809 //
display_handler(window_info * win)810 void Quest_List::display_handler(window_info *win)
811 {
812 const size_t used_x = 4*spacer + win->box_size;
813 const size_t disp_lines = win->len_y / linesep;
814 float zoom = win->current_scale_small;
815
816 // if resizing wait until we stop
817 static Uint8 resizing = 0;
818 if (win->resized)
819 resizing = 1;
820 // once we stop, snap the window size to fix nicely
821 else if (resizing)
822 {
823 size_t to_show = (disp_lines>num_shown()) ?num_shown() :disp_lines;
824 size_t newy = static_cast<size_t>(to_show * linesep);
825 size_t newx = std::min(win->len_x, int(used_x) + get_max_title_width(zoom));
826 resize_window(win->window_id, newx, newy);
827 resizing = 0;
828 }
829
830 // only show help and clear the highlighted quest if the context window is closed
831 if (cm_window_shown() == CM_INIT_VALUE)
832 {
833 clear_highlighted();
834 if (show_help_text && has_mouseover())
835 show_help(questlog_cm_help_str, 0, win->len_y + questlog_window.get_win_space(), win->current_scale);
836 }
837
838 // get the top line and then loop drawing all quests we can display
839 vscrollbar_set_bar_len(win->window_id, get_scroll_id(), (num_shown()>disp_lines) ?num_shown()-disp_lines :0);
840 int offset = (num_shown()>disp_lines) ?vscrollbar_get_pos(win->window_id, get_scroll_id()) :0;
841 const Quest* thequest = get_first_quest(offset);
842 int posy = spacer;
843 TextDrawOptions text_options = TextDrawOptions().set_max_width(win->len_x - used_x)
844 .set_max_lines(1).set_zoom(zoom);
845 while ((thequest != 0) && (posy+linesep-spacer <= win->len_y))
846 {
847 const int hl_x = win->len_x - 2*spacer - win->box_size;
848 // is this the quest the mouse is over?
849 if (has_mouseover() && (cm_window_shown() == CM_INIT_VALUE) &&
850 (posy+linesep-spacer > get_mouseover_y()))
851 {
852 set_highlighted(thequest->get_id());
853 // draw highlight over active name
854 draw_highlight(spacer, posy-spacer, hl_x, linesep, 0);
855 // if clicked, update the filter
856 if (was_clicked())
857 {
858 if (thequest->get_id() == Quest::UNSET_ID)
859 {
860 questlog_container.show_all_entries();
861 active_filter = QLFLT_QUEST;
862 }
863 else
864 {
865 active_filter = QLFLT_QUEST;
866 set_selected(thequest->get_id());
867 questlog_container.rebuild_active_entries(quest_entries.size()-1);
868 }
869 do_click_sound();
870 }
871 clear_mouseover();
872 }
873 // is this the selected title?
874 if ((active_filter == QLFLT_QUEST) && (thequest->get_id() == get_selected()))
875 draw_highlight(spacer, posy-spacer, hl_x, linesep, 1);
876 if (cm_active() && (get_highlighted() == thequest->get_id()))
877 glColor3fv(gui_color);
878 // display comleted quests less prominently
879 else if (thequest->get_completed())
880 glColor3f(0.6f,0.6f,0.6f);
881 else
882 glColor3f(1.0f,1.0f,1.0f);
883
884 // display the title, truncating if its too long for the window width
885 const std::string& title = thequest->get_title().empty()
886 ? _empty_title_replacement : thequest->get_title();
887 FontManager::get_instance().draw(UI_FONT,
888 reinterpret_cast<const unsigned char*>(title.c_str()), title.size(),
889 2*spacer, posy, text_options);
890
891 thequest = get_next_quest();
892 posy += linesep;
893 }
894 // reset mouse over and clicked
895 clear_mouseover();
896 set_clicked(false);
897 }
898
899
900 // Mouse left-click select an quest, mouse wheel scroll window.
901 //
click_handler(window_info * win,Uint32 flags)902 void Quest_List::click_handler(window_info *win, Uint32 flags)
903 {
904 if (flags&ELW_WHEEL_UP)
905 vscrollbar_scroll_up(win->window_id, get_scroll_id());
906 else if(flags&ELW_WHEEL_DOWN)
907 vscrollbar_scroll_down(win->window_id, get_scroll_id());
908 else if(flags&ELW_LEFT_MOUSE)
909 set_clicked(true);
910 }
911
912
913 // Handle window resize, keeping the scroll handler in place.
914 //
resize_handler(window_info * win)915 void Quest_List::resize_handler(window_info *win)
916 {
917 widget_move(win->window_id, get_scroll_id(), win->len_x-win->box_size, win->box_size);
918 widget_resize(win->window_id, get_scroll_id(), win->box_size, win->len_y-2*win->box_size);
919 }
920
921
922 // Handle option selection for the quest list context menu
cm_handler(int option)923 void Quest_List::cm_handler(int option)
924 {
925 switch (option)
926 {
927 case CMQL_COMPLETED: toggle_completed(get_highlighted()); break;
928 case CMQL_ADDSEL:
929 for (std::set<size_t>::const_iterator i=selected_entries.begin(); i!=selected_entries.end(); ++i)
930 quest_entries[*i].set_id(get_highlighted());
931 questlog_container.set_save();
932 questlog_container.rebuild_active_entries(questlog_window.get_current_entry());
933 break;
934 case CMQL_HIDECOMPLETED: recalc_num_shown(); break;
935 }
936 }
937
938
939 // Adjust things just before the quest list context menu is opened
940 //
cm_pre_show_handler(void)941 void Quest_List::cm_pre_show_handler(void)
942 {
943 quest_completed = (get_completed(highlighted_id)) ?1 :0;
944 cm_grey_line(cm_id, CMQL_COMPLETED, (highlighted_id == Quest::UNSET_ID) ?1 :0);
945 cm_grey_line(cm_id, CMQL_ADDSEL, selected_entries.empty() ?1 :0);
946 }
947
948 // Scroll to show the currently selected quest in the quest list window.
949 //
scroll_to_selected(void)950 void Quest_List::scroll_to_selected(void)
951 {
952 Uint16 setected_id = questlist.get_selected();
953 if (setected_id == Quest::UNSET_ID)
954 return;
955 const Quest* thequest = get_first_quest(0);
956 for (int line_num = 0; thequest != 0; line_num++)
957 {
958 if (thequest->get_id() == setected_id)
959 {
960 vscrollbar_set_pos(get_win_id(), get_scroll_id(), line_num);
961 break;
962 }
963 thequest = questlist.get_next_quest();
964 }
965 }
966
967
968 // Resize the quest list window.
969 //
ui_scale_handler(window_info * win)970 void Quest_List::ui_scale_handler(window_info *win)
971 {
972 spacer = static_cast<int>(0.5 + 3 * win->current_scale);
973 linesep = win->small_font_len_y + 2 * spacer;
974 if ((win->pos_id >= 0) && (win->pos_id<windows_list.num_windows))
975 {
976 float zoom = win->current_scale_small;
977 window_info *parent_win = &windows_list.window[win->pos_id];
978 int max_size_x = get_max_title_width(zoom) + win->box_size + 4 * spacer;
979 int min_size_x = 10 * FontManager::get_instance().max_width_spacing(UI_FONT, zoom)
980 + 4 * spacer;
981 int min_size_y = 5 * linesep;
982 int size_y = (list_left_of_entries) ?linesep * static_cast<int>(parent_win->len_y / linesep) : min_size_y;
983 int pos_x = (list_left_of_entries) ?-(max_size_x + questlog_window.get_win_space()) :(parent_win->len_x - max_size_x) / 2;
984 int pos_y = (list_left_of_entries) ?0 : parent_win->len_y + win->small_font_len_y + questlog_window.get_win_space() + parent_win->title_height;
985 set_window_min_size(win_id, min_size_x, min_size_y);
986 resize_window(win_id, max_size_x, size_y);
987 move_window(win_id, win->pos_id, win->pos_loc, parent_win->pos_x + pos_x, parent_win->pos_y + pos_y);
988 }
989 }
990
font_change_handler(window_info * win,FontManager::Category cat)991 int Quest_List::font_change_handler(window_info *win, FontManager::Category cat)
992 {
993 if (cat != UI_FONT)
994 return 0;
995 ui_scale_handler(win);
996 return 1;
997 }
998
999 // Create or open the quest list window.
1000 //
open_window(void)1001 void Quest_List::open_window(void)
1002 {
1003 if (win_id < 0)
1004 {
1005 win_id = create_window(questlist_filter_title_str, get_id_MW(MW_QUESTLOG), 0, 0, 0, 0, 0, ELW_USE_UISCALE|ELW_WIN_DEFAULT|ELW_RESIZEABLE);
1006 if (win_id < 0 || win_id >= windows_list.num_windows)
1007 return;
1008 set_window_custom_scale(win_id, MW_QUESTLOG);
1009 set_window_handler(win_id, ELW_HANDLER_DISPLAY, (int (*)())&display_questlist_handler );
1010 set_window_handler(win_id, ELW_HANDLER_CLICK, (int (*)())&click_questlist_handler );
1011 set_window_handler(win_id, ELW_HANDLER_MOUSEOVER, (int (*)())&mouseover_questlist_handler );
1012 set_window_handler(win_id, ELW_HANDLER_RESIZE, (int (*)())&resize_questlist_handler );
1013 set_window_handler(win_id, ELW_HANDLER_UI_SCALE, (int (*)())&ui_scale_questlist_handler );
1014 set_window_handler(win_id, ELW_HANDLER_FONT_CHANGE, (int (*)())&change_questlist_font_handler);
1015 scroll_id = vscrollbar_add_extended(win_id, scroll_id, NULL, 0, 0, 0, 0, 0, 1.0, 0, 1, quests.size()-1);
1016 ui_scale_handler(&windows_list.window[win_id]);
1017
1018 cm_id = cm_create(cm_questlist_menu_str, cm_questlist_handler);
1019 cm_set_pre_show_handler(cm_id, cm_questlist_pre_show_handler);
1020 cm_add_window(cm_id, win_id);
1021
1022 cm_bool_line(cm_id, CMQL_COMPLETED, &quest_completed, NULL);
1023 cm_bool_line(cm_id, CMQL_NOAUTOOPEN, &no_auto_open, NULL);
1024 cm_bool_line(cm_id, CMQL_HIDECOMPLETED, &hide_completed, NULL);
1025 cm_bool_line(cm_id, CMQL_LISTLEFTOFENTRIES, &list_left_of_entries, NULL);
1026 }
1027 else
1028 show_window(win_id);
1029 }
1030
1031
1032 // Create a fixed vector to use if deleted and a raw npc name / deleted string.
1033 //
update_displayed_npc_name()1034 void Quest_Entry::update_displayed_npc_name()
1035 {
1036 if (deleted)
1037 disp_npc = ustring(reinterpret_cast<const unsigned char*>(questlog_deleted_str));
1038 else
1039 disp_npc = ustring(npc + npc_spacer.substr(0, npc_spacer.size()-1));
1040 }
1041
1042
1043 // Return the lines of an entry.
1044 //
get_lines() const1045 const std::vector<ustring>& Quest_Entry::get_lines() const
1046 {
1047 return deleted ? deleted_line : _lines;
1048 }
1049
1050
1051 // Write a quest entry as a single line to the output stream.
1052 //
save(std::ofstream & out) const1053 void Quest_Entry::save(std::ofstream & out) const
1054 {
1055 for (std::vector<ustring>::size_type i=0; i<_lines.size(); i++)
1056 {
1057 out.write(reinterpret_cast<const char*>(_lines[i].c_str()), _lines[i].size());
1058 if (i<_lines.size()-1)
1059 out.put(' ');
1060 else if (quest_id == Quest::UNSET_ID)
1061 out.put('\n');
1062 }
1063 if (quest_id != Quest::UNSET_ID)
1064 out << "<<" << quest_id << ">>" << std::endl;
1065 }
1066
1067
1068 // Store entry as a group of lines that fit into the window width.
set_lines(const ustring & the_text)1069 void Quest_Entry::set_lines(const ustring& the_text)
1070 {
1071 // make a copy of the string, replacing \n with spaces unless preceeded by a space
1072 ustring text;
1073 char last_char = ' ';
1074 text.reserve(the_text.size());
1075 for (unsigned char c: the_text)
1076 {
1077 if (c == '\n')
1078 {
1079 if (last_char != ' ')
1080 text += last_char = ' ';
1081 }
1082 else if (c != '\r')
1083 {
1084 text += last_char = c;
1085 }
1086 }
1087
1088 // look for and <<number>> at the end of the text, its the quest id
1089 if (text.compare(text.length() - 2, 2, reinterpret_cast<const unsigned char*>(">>")) == 0)
1090 {
1091 std::string::size_type open = text.rfind(reinterpret_cast<const unsigned char*>("<<"));
1092 if (open != std::string::npos && open != text.length() - 4)
1093 {
1094 ustring id_str = text.substr(open + 2, text.length() - open - 4);
1095 if (id_str.find_first_not_of(reinterpret_cast<const unsigned char*>("0123456789")) == std::string::npos)
1096 {
1097 quest_id = static_cast<Uint16>(atoi(reinterpret_cast<const char*>(id_str.c_str())));
1098 questlist.add(quest_id);
1099 text.erase(open);
1100 }
1101 }
1102 }
1103
1104 // for matching purposes calculate the sum of the characters, if it wraps, fine
1105 charsum = std::accumulate(text.begin(), text.end(), Uint16(0));
1106
1107 _lines.assign(1, text);
1108 rewrap_lines();
1109
1110 update_displayed_npc_name();
1111
1112 // make sure latest entry is not filtered
1113 npc_filter.set(npc);
1114 }
1115
rewrap_lines()1116 void Quest_Entry::rewrap_lines()
1117 {
1118 ustring full_text;
1119 for (const auto& line: _lines)
1120 full_text += line;
1121
1122 // divide text into lines that fit within the window width
1123 ustring wrapped_text;
1124 int nr_lines;
1125 TextDrawOptions options = TextDrawOptions().set_max_width(content_width)
1126 .set_zoom(content_zoom);
1127 std::tie(wrapped_text, nr_lines) = FontManager::get_instance().reset_soft_breaks(
1128 UI_FONT, full_text, options);
1129
1130 _lines.clear();
1131 size_t off = 0;
1132 while (off < wrapped_text.length())
1133 {
1134 ustring::size_type end = wrapped_text.find('\r', off);
1135 if (end == ustring::npos)
1136 {
1137 _lines.push_back(wrapped_text.substr(off));
1138 break;
1139 }
1140 _lines.push_back(wrapped_text.substr(off, end-off));
1141 off = end + 1;
1142 }
1143 }
1144
1145 // Set a new entry, using the specified npc name.
1146 //
set(const ustring & the_text,const ustring & the_npc)1147 void Quest_Entry::set(const ustring& the_text, const ustring& the_npc)
1148 {
1149 // npc.assign(the_npc.begin(), the_npc.end());
1150 npc = the_npc;
1151 set_lines(NPC_NAME_COLOUR + npc + npc_spacer + the_text);
1152 }
1153
1154
1155 // Set a new entry, finding the npc name from the text.
1156 //
set(const ustring & the_text_const)1157 void Quest_Entry::set(const ustring& the_text_const)
1158 {
1159 if (the_text_const.empty())
1160 return;
1161 ustring the_text = the_text_const;
1162
1163 // find any quest id - a mistake!
1164 // adding at the start was a mistake as it messes things up for the old client
1165 // moved to end of the text but keep this check for now for the early adopters!
1166 if (the_text[0] == '<')
1167 {
1168 std::string::size_type id_end = the_text.find_first_of('>', 1);
1169 if (id_end != std::string::npos)
1170 {
1171 ustring id_str = the_text.substr(1,id_end-1);
1172 quest_id = static_cast<Uint16>(atoi(reinterpret_cast<const char*>(id_str.c_str())));
1173 the_text.erase(0, id_end+1);
1174 }
1175 // make sure we save to the new format
1176 questlog_container.set_save();
1177 }
1178
1179 // find and npc name
1180 if (the_text[0] == NPC_NAME_COLOUR)
1181 {
1182 std::string::size_type npc_end = the_text.find(npc_spacer, 1);
1183 if ((npc_end != std::string::npos) &&
1184 (npc_end < MAX_USERNAME_LENGTH) &&
1185 is_color(the_text[npc_end + npc_spacer.size()]))
1186 npc = the_text.substr(1,npc_end-1);
1187 }
1188
1189 if (npc.empty())
1190 {
1191 npc = ustring(reinterpret_cast<const unsigned char*>("----"));
1192 set_lines(NPC_NAME_COLOUR + npc + npc_spacer + the_text);
1193 }
1194 else
1195 set_lines(the_text);
1196 }
1197
1198
1199 // Return true if the entry contains the specified text, case insensitive.
1200 //
contains_string(const char * text_to_find) const1201 bool Quest_Entry::contains_string(const char *text_to_find) const
1202 {
1203 if (deleted)
1204 return false;
1205 std::string lowercase(text_to_find);
1206 std::transform(lowercase.begin(), lowercase.end(), lowercase.begin(), tolower);
1207 std::string fulltext;
1208 for (const auto& line: _lines)
1209 {
1210 std::remove_copy_if(line.begin(), line.end(), std::back_inserter(fulltext), is_color);
1211 fulltext += ' ';
1212 }
1213 std::transform(fulltext.begin(), fulltext.end(), fulltext.begin(), tolower);
1214 if (fulltext.find(lowercase, 0) != std::string::npos)
1215 return true;
1216 return false;
1217 }
1218
1219
1220 // Make sure the window size is fits the rows/cols nicely.
1221 //
resize_handler(window_info * win)1222 void NPC_Filter::resize_handler(window_info *win)
1223 {
1224 // let the width lead
1225 npc_name_cols = static_cast<int>(win->len_x / max_npc_name_x);
1226 if (npc_name_cols < min_npc_name_cols)
1227 npc_name_cols = min_npc_name_cols;
1228 // but maintain a minimum height
1229 npc_name_rows = (npc_filter_map.size() + npc_name_cols - 1) / npc_name_cols;
1230 if (npc_name_rows <= min_npc_name_rows)
1231 {
1232 npc_name_rows = min_npc_name_rows;
1233 npc_name_cols = min_npc_name_cols;
1234 while (npc_name_cols*npc_name_rows < npc_filter_map.size())
1235 npc_name_cols++;
1236 }
1237 set_window_scroll_inc(win->window_id, static_cast<int>(max_npc_name_y));
1238 set_window_scroll_len(win->window_id, static_cast<int>(npc_name_rows*max_npc_name_y-win->len_y));
1239 }
1240
1241
1242 // Called on creation and when scaling changes, set the starting size and position fo npc filter window
1243 //
ui_scale_handler(window_info * win)1244 void NPC_Filter::ui_scale_handler(window_info *win)
1245 {
1246 npc_name_space = static_cast<int>(0.5 + 3 * win->current_scale);
1247 npc_name_border = static_cast<int>(0.5 + 5 * win->current_scale);
1248 npc_name_box_size = static_cast<int>(0.5 + 12 * win->current_scale);
1249 max_npc_name_x = npc_name_space * 3 + npc_name_box_size + MAX_USERNAME_LENGTH * win->small_font_max_len_x;
1250 max_npc_name_y = std::max(win->small_font_len_y, npc_name_box_size) + 2 * npc_name_space;
1251 if ((win->pos_id >= 0) && (win->pos_id<windows_list.num_windows))
1252 {
1253 window_info *parent_win = &windows_list.window[win->pos_id];
1254 int size_x = static_cast<int>(2*npc_name_border + min_npc_name_cols * max_npc_name_x + win->box_size);
1255 int size_y = static_cast<int>(static_cast<int>(parent_win->len_y/max_npc_name_y) * max_npc_name_y);
1256 int min_size_x = static_cast<int>(2*npc_name_border + min_npc_name_cols * max_npc_name_x + win->box_size);
1257 int min_size_y = static_cast<int>(min_npc_name_rows * max_npc_name_y);
1258 int pos_x = parent_win->len_x + questlog_window.get_win_space();
1259 int pos_y = 0;
1260 set_window_min_size(win->window_id, min_size_x, min_size_y);
1261 resize_window(win->window_id, size_x, size_y);
1262 move_window(win->window_id, win->pos_id, win->pos_loc, parent_win->pos_x + pos_x, parent_win->pos_y + pos_y);
1263 }
1264 }
1265
font_change_handler(window_info * win,FontManager::Category cat)1266 int NPC_Filter::font_change_handler(window_info *win, FontManager::Category cat)
1267 {
1268 if (cat != win->font_category)
1269 return 0;
1270 ui_scale_handler(win);
1271 return 1;
1272 }
1273
1274 // Display handler for the quest log filter.
1275 //
display_handler(window_info * win)1276 void NPC_Filter::display_handler(window_info *win)
1277 {
1278 FontManager& font_manager = FontManager::get_instance();
1279 static Uint8 resizing = 0;
1280 static size_t last_filter_size = static_cast<size_t>(-1);
1281 int line_height = win->small_font_len_y;
1282
1283 // if resizing wait until we stop
1284 if (win->resized)
1285 resizing = 1;
1286
1287 // once we stop, snap the window to the new grid size
1288 else if (resizing)
1289 {
1290 int new_width = static_cast<int>(2*npc_name_border + npc_name_cols * max_npc_name_x + win->box_size);
1291 int new_rows = static_cast<int>((win->len_y+max_npc_name_y/2)/max_npc_name_y);
1292 int max_rows = static_cast<int>((npc_filter_map.size() + npc_name_cols - 1) / npc_name_cols);
1293 resizing = 0;
1294 resize_window (win->window_id, new_width, static_cast<int>(((new_rows > max_rows) ?max_rows :new_rows)*max_npc_name_y));
1295 }
1296 // spot new entries and make sure we resize
1297 else if (last_filter_size != npc_filter_map.size())
1298 resize_npc_filter_handler(win, -1, -1);
1299 last_filter_size = npc_filter_map.size();
1300
1301 unsigned int row = 0;
1302 unsigned int col = 0;
1303 TextDrawOptions options = TextDrawOptions().set_max_lines(1)
1304 .set_zoom(win->current_scale_small);
1305 for (const auto& i: npc_filter_map)
1306 {
1307 int posx = static_cast<int>(npc_name_border + col*max_npc_name_x + 0.5);
1308 int posy = static_cast<int>(row*max_npc_name_y + 0.5);
1309 int boxy = posy + static_cast<int>(std::round((max_npc_name_y-npc_name_box_size)/2));
1310 int texty = posy + static_cast<int>(std::round((max_npc_name_y-line_height)/2));
1311
1312 // draw highlight over active name
1313 if ((col+row*npc_name_cols) == npc_filter_active_npc_name)
1314 draw_highlight(posx, posy, static_cast<int>(0.5+max_npc_name_x), static_cast<int>(0.5+max_npc_name_y), 0);
1315
1316 // set the colour and position for the box and text
1317 if ((active_filter != QLFLT_NPC) && (active_filter != QLFLT_NONE))
1318 glColor3f(0.7f, 0.7f, 0.7f);
1319 else
1320 glColor3f(1.0f, 1.0f, 1.0f);
1321 posx += npc_name_space;
1322
1323 // draw the on/off box
1324 glDisable(GL_TEXTURE_2D);
1325 glBegin( i.second ? GL_QUADS: GL_LINE_LOOP);
1326 glVertex2i(posx, boxy);
1327 glVertex2i(posx + npc_name_box_size, boxy);
1328 glVertex2i(posx + npc_name_box_size, boxy + npc_name_box_size);
1329 glVertex2i(posx, boxy + npc_name_box_size);
1330 glEnd();
1331 glEnable(GL_TEXTURE_2D);
1332
1333 // draw the string
1334 font_manager.draw(win->font_category, i.first.c_str(), i.first.length(),
1335 posx + npc_name_box_size + npc_name_space, texty, options);
1336
1337 // control row and col values
1338 col++;
1339 if (col >= npc_name_cols)
1340 {
1341 col = 0;
1342 row++;
1343 }
1344 }
1345
1346 // make sure the mouse over detection is fresh next time
1347 npc_filter_active_npc_name = static_cast<size_t>(-1);
1348
1349 #ifdef OPENGL_TRACE
1350 CHECK_GL_ERRORS();
1351 #endif //OPENGL_TRACE
1352 }
1353
1354
1355 // When a npc name is mouse clicked, toggle the filter value.
1356 //
click_handler(window_info * win,int mx,int my,Uint32 flags)1357 int NPC_Filter::click_handler(window_info *win, int mx, int my, Uint32 flags)
1358 {
1359 if ((my < 0) || (flags & ELW_WHEEL))
1360 return 0;
1361 int yoffset = get_window_scroll_pos(win->window_id);
1362 size_t index = static_cast<int>((my+yoffset) / max_npc_name_y) * npc_name_cols + static_cast<int>(mx / max_npc_name_x);
1363 if (index >= npc_filter_map.size())
1364 return 0;
1365 size_t j = 0;
1366 for (std::map<ustring,int>::iterator i = npc_filter_map.begin(); i != npc_filter_map.end(); ++i, j++)
1367 if (j == index)
1368 {
1369 do_click_sound();
1370 i->second ^= 1;
1371 active_filter = QLFLT_NPC;
1372 questlist.set_selected(Quest::UNSET_ID);
1373 questlog_container.rebuild_active_entries(questlog_window.get_current_entry());
1374 break;
1375 }
1376 return 1;
1377 }
1378
1379
1380 // Move the window scroll position to match the key pressed with the first character of the npc name.
1381 //
keypress_handler(window_info * win,int mx,int my,SDL_Keycode key_code,Uint32 key_unicode,Uint16 key_mod)1382 int NPC_Filter::keypress_handler(window_info *win, int mx, int my, SDL_Keycode key_code, Uint32 key_unicode, Uint16 key_mod)
1383 {
1384 char keychar = tolower(static_cast<char>(key_unicode));
1385 if ((key_mod & KMOD_CTRL) || (key_mod & KMOD_ALT) || (keychar<'a') || (keychar>'z'))
1386 return 0;
1387 size_t line = 0;
1388 for (std::map<ustring,int>::iterator i = npc_filter_map.begin(); i != npc_filter_map.end(); ++i, line++)
1389 {
1390 if (!i->first.empty() && (tolower(i->first[0]) == keychar))
1391 {
1392 set_window_scroll_pos(win->window_id, static_cast<int>(static_cast<int>(line/npc_name_cols)*max_npc_name_y));
1393 return 1;
1394 }
1395 }
1396 return 1;
1397 }
1398
1399
1400 // Record which name the mouse is over so it can be highlighted.
1401 //
mouseover_handler(window_info * win,int mx,int my)1402 void NPC_Filter::mouseover_handler(window_info *win, int mx, int my)
1403 {
1404 mx -= npc_name_border;
1405 int yoffset = get_window_scroll_pos(win->window_id);
1406 if ((my >= yoffset) && (mx >= 0) && (mx < (npc_name_cols * max_npc_name_x)))
1407 npc_filter_active_npc_name = static_cast<int>(my / max_npc_name_y) * npc_name_cols + static_cast<int>(mx / max_npc_name_x);
1408 }
1409
1410
1411 // Open/Create the npc filter window.
1412 //
open_window(void)1413 void NPC_Filter::open_window(void)
1414 {
1415 if (npc_filter_win < 0)
1416 {
1417 npc_filter_win = create_window(questlog_npc_filter_title_str, get_id_MW(MW_QUESTLOG), 0, 0, 0, 0, 0, ELW_USE_UISCALE|ELW_SCROLLABLE|ELW_RESIZEABLE|ELW_WIN_DEFAULT);
1418 if (npc_filter_win < 0 && npc_filter_win >= windows_list.num_windows)
1419 return;
1420 set_window_custom_scale(npc_filter_win, MW_QUESTLOG);
1421 set_window_handler(npc_filter_win, ELW_HANDLER_DISPLAY, (int (*)())&display_npc_filter_handler );
1422 set_window_handler(npc_filter_win, ELW_HANDLER_CLICK, (int (*)())&click_npc_filter_handler );
1423 set_window_handler(npc_filter_win, ELW_HANDLER_KEYPRESS, (int (*)())&keypress_npc_filter_handler );
1424 set_window_handler(npc_filter_win, ELW_HANDLER_MOUSEOVER, (int (*)())&mouseover_npc_filter_handler );
1425 set_window_handler(npc_filter_win, ELW_HANDLER_RESIZE, (int (*)())&resize_npc_filter_handler );
1426 set_window_handler(npc_filter_win, ELW_HANDLER_UI_SCALE, (int (*)())&ui_scale_npc_filter_handler );
1427 set_window_handler(npc_filter_win, ELW_HANDLER_FONT_CHANGE, (int (*)())&change_npc_filter_font_handler);
1428 ui_scale_npc_filter_handler(&windows_list.window[npc_filter_win]);
1429 }
1430 else
1431 show_window(npc_filter_win);
1432 set_window_scroll_pos(npc_filter_win, 0);
1433 }
1434
1435
1436 // Draw a simple line
1437 //
draw_underline(int startx,int starty,int endx,int endy)1438 void Questlog_Window::draw_underline(int startx, int starty, int endx, int endy)
1439 {
1440 glDisable(GL_TEXTURE_2D);
1441 glBegin(GL_LINES);
1442 glVertex2i(startx, starty);
1443 glVertex2i(endx, endy);
1444 glEnd();
1445 glEnable(GL_TEXTURE_2D);
1446 #ifdef OPENGL_TRACE
1447 CHECK_GL_ERRORS();
1448 #endif //OPENGL_TRACE
1449 }
1450
1451
1452 // Copy an entry to a string, stripped of special characters.
1453 //
copy_one_entry(std::string & copy_str,size_t entry)1454 void Questlog_Window::copy_one_entry(std::string ©_str, size_t entry)
1455 {
1456 for (const auto& line: quest_entries[active_entries[entry]].get_lines())
1457 {
1458 std::remove_copy_if(line.begin(), line.end(), std::back_inserter(copy_str), is_color);
1459 copy_str += ' ';
1460 }
1461 if (!copy_str.empty())
1462 copy_str[copy_str.size()-1] = '\n';
1463 }
1464
1465
1466 // Copy all shown enties to the clipboard, stripped of special characters.
1467 //
copy_all_entries(void)1468 void Questlog_Window::copy_all_entries(void)
1469 {
1470 std::string copy_str;
1471 for (std::vector<size_t>::size_type i=0; i<active_entries.size(); ++i)
1472 copy_one_entry(copy_str, i);
1473 copy_to_clipboard(copy_str.c_str());
1474 }
1475
1476
1477 // Copy an entry to the clipboard, stripped of special characters.
1478 //
copy_entry(size_t entry)1479 void Questlog_Window::copy_entry(size_t entry)
1480 {
1481 std::string copy_str;
1482 copy_one_entry(copy_str, entry);
1483 copy_to_clipboard(copy_str.c_str());
1484 }
1485
1486
1487 // Start inputting a new entry, part 1 prompt for npc name.
1488 //
add_entry(window_info * win,size_t entry)1489 void Questlog_Window::add_entry(window_info *win, size_t entry)
1490 {
1491 if (current_action != -1)
1492 return;
1493 current_action = CMQL_ADD;
1494 adding_insert_pos = (entry < active_entries.size()) ?active_entries[entry] :quest_entries.size();
1495 close_ipu(&ipu_questlog);
1496 init_ipu(&ipu_questlog, win->window_id, MAX_USERNAME_LENGTH, 1, MAX_USERNAME_LENGTH + 1, questlog_input_cancel_handler, questlog_add_npc_input_handler);
1497 display_popup_win(&ipu_questlog, questlog_add_npc_prompt_str);
1498 centre_popup_window(&ipu_questlog);
1499 }
1500
1501
1502 // Continue inputting a new entry, part 2 have npc so queue the input
1503 // for the body. Can't call directly as reusing ipu_questlog and it
1504 // would get cleared on return from this function!
1505 //
add_npc_input_handler(const unsigned char * input_text,void * data)1506 void Questlog_Window::add_npc_input_handler(const unsigned char *input_text, void *data)
1507 {
1508 adding_npc = ustring(input_text);
1509 prompt_for_add_text = true;
1510 }
1511
1512
1513 // Continue inputting a new entry, part 3 prompt for entry main text.
1514 //
add_text_input(window_info * win)1515 void Questlog_Window::add_text_input(window_info *win)
1516 {
1517 prompt_for_add_text = false;
1518 close_ipu(&ipu_questlog);
1519 init_ipu(&ipu_questlog, win->window_id, 1024, 5, 40, questlog_input_cancel_handler, questlog_add_text_input_handler);
1520 ipu_questlog.allow_nonprint_chars = 1;
1521 display_popup_win(&ipu_questlog, questlog_add_text_prompt_str);
1522 centre_popup_window(&ipu_questlog);
1523 }
1524
1525
1526 // Continue inputting a new entry, part 4 insert the entry.
1527 //
add_text_input_handler(const unsigned char * input_text,void * data)1528 void Questlog_Window::add_text_input_handler(const unsigned char *input_text, void *data)
1529 {
1530 current_action = -1;
1531 Quest_Entry ne;
1532 std::vector<Quest_Entry>::iterator e = quest_entries.insert(quest_entries.begin() + adding_insert_pos, ne);
1533 e->set(to_color_char(c_grey1) + ustring(input_text), adding_npc);
1534 questlog_container.set_save();
1535 questlist.set_selected(Quest::UNSET_ID);
1536 questlog_container.rebuild_active_entries(adding_insert_pos);
1537 }
1538
1539
1540 // Find, searching from next entry to end then back to current. Done
1541 // like this so we find multiple entries each time we hit ok.
1542 //
find_input_handler(const char * input_text,void * data)1543 void Questlog_Window::find_input_handler(const char *input_text, void *data)
1544 {
1545 for (std::vector<Quest_Entry>::size_type entry = current_line+1; entry<active_entries.size(); entry++)
1546 if (quest_entries[active_entries[entry]].contains_string(input_text))
1547 {
1548 goto_entry(entry);
1549 return;
1550 }
1551 for (std::vector<Quest_Entry>::size_type entry = 0; entry<=current_line && entry<active_entries.size(); entry++)
1552 if (quest_entries[active_entries[entry]].contains_string(input_text))
1553 {
1554 goto_entry(entry);
1555 return;
1556 }
1557 do_alert1_sound();
1558 }
1559
1560
1561 // Prompt for text to find. The dialogue will not close when "OK"
1562 // pressed but will call the callback. Use "Cancel" to close.
1563 //
find_in_entry(window_info * win)1564 void Questlog_Window::find_in_entry(window_info *win)
1565 {
1566 if (current_action != -1)
1567 return;
1568 current_action = CMQL_FIND;
1569 close_ipu(&ipu_questlog);
1570 init_ipu(&ipu_questlog, win->window_id, 21, 1, 22, questlog_input_cancel_handler, questlog_find_input_handler);
1571 ipu_questlog.accept_do_not_close = 1;
1572 display_popup_win(&ipu_questlog, questlog_find_prompt_str);
1573 centre_popup_window(&ipu_questlog);
1574 }
1575
1576
1577 // Mark the current entry as deleted.
1578 //
undelete_entry(size_t entry)1579 void Questlog_Window::undelete_entry(size_t entry)
1580 {
1581 quest_entries[active_entries[entry]].set_deleted(false);
1582 questlog_container.set_save();
1583 }
1584
1585
1586 // Mark the current entry as not deleted.
1587 //
delete_entry(size_t entry)1588 void Questlog_Window::delete_entry(size_t entry)
1589 {
1590 quest_entries[active_entries[entry]].set_deleted(true);
1591 questlog_container.set_save();
1592 }
1593
1594
1595 // Look for matching entries and delete (mark as deleted) all but first
1596 //
delete_duplicates(void)1597 void Questlog_Window::delete_duplicates(void)
1598 {
1599 int deleted_count = 0;
1600 LOG_TO_CONSOLE(c_green1, questlog_deldupe_start_str);
1601 std::multimap<Uint16, ustring> mm;
1602
1603 for (std::vector<Quest_Entry>::iterator entry=quest_entries.begin(); entry!=quest_entries.end(); ++entry)
1604 {
1605 if (!entry->get_deleted())
1606 {
1607 // get the full text for the entry
1608 ustring the_text;
1609 for (const auto& line: entry->get_lines())
1610 the_text += line;
1611
1612 // see if the charsum has already been seen
1613 std::multimap<Uint16, ustring>::const_iterator iter = mm.find(entry->get_charsum());
1614 if (iter != mm.end())
1615 {
1616 // we have a charsum match so check the text, there may be more than one with this charsum
1617 std::multimap<Uint16, ustring>::const_iterator last = mm.upper_bound(entry->get_charsum());
1618 for ( ; iter != last; ++iter)
1619 {
1620 // if the text and the charsum match, mark the entry as deleted
1621 if (iter->second == the_text)
1622 {
1623 entry->set_deleted(true);
1624 questlog_container.set_save();
1625 deleted_count++;
1626 break;
1627 }
1628 }
1629 }
1630
1631 // save - as either the charsum did not match a previous entry or the charsum did but the text didn't
1632 if (!entry->get_deleted())
1633 mm.insert(std::make_pair(entry->get_charsum(), the_text));
1634 }
1635 }
1636
1637 char message[80];
1638 safe_snprintf(message, sizeof(message), questlog_deldupe_end_str, mm.size(), deleted_count);
1639 LOG_TO_CONSOLE(c_green1, message);
1640 }
1641
1642
1643 // Enable/disable menu options as required....
1644 //
cm_preshow_handler(int my)1645 void Questlog_Window::cm_preshow_handler(int my)
1646 {
1647 cm_questlog_over_entry = active_entries.size();
1648 for (std::vector<Shown_Entry>::const_iterator i=shown_entries.begin(); i!=shown_entries.end(); ++i)
1649 if (i->is_over(my))
1650 {
1651 cm_questlog_over_entry = i->get_entry();
1652 break;
1653 }
1654 bool is_over_entry = (cm_questlog_over_entry < active_entries.size());
1655 bool is_deleted = is_over_entry && quest_entries[active_entries[cm_questlog_over_entry]].get_deleted();
1656 bool qlw_open = get_show_window(questlist.get_win_id());
1657 bool nfw_open = get_show_window(npc_filter.get_win_id());
1658 bool is_selected = is_over_entry && (selected_entries.find(active_entries[cm_questlog_over_entry]) != selected_entries.end());
1659 cm_grey_line(cm_questlog_id, CMQL_SHOWALL, quest_entries.empty() ?1 :0);
1660 cm_grey_line(cm_questlog_id, CMQL_QUESTFILTER, (qlw_open || quest_entries.empty()) ?1 :0);
1661 cm_grey_line(cm_questlog_id, CMQL_NPCFILTER, (nfw_open || quest_entries.empty()) ?1 :0);
1662 cm_grey_line(cm_questlog_id, CMQL_NPCSHOWNONE, (quest_entries.empty()) ?1 :0);
1663 cm_grey_line(cm_questlog_id, CMQL_JUSTTHISNPC, (is_over_entry) ?0 :1);
1664 cm_grey_line(cm_questlog_id, CMQL_JUSTTHISQUEST, (is_over_entry && (quest_entries[active_entries[cm_questlog_over_entry]].get_id() != Quest::UNSET_ID)) ?0 :1);
1665 cm_grey_line(cm_questlog_id, CMQL_COPY, (is_over_entry && !is_deleted) ?0 :1);
1666 cm_grey_line(cm_questlog_id, CMQL_COPYALL, active_entries.empty() ?1 :0);
1667 cm_grey_line(cm_questlog_id, CMQL_FIND, (current_action == -1 && !active_entries.empty()) ?0 :1);
1668 cm_grey_line(cm_questlog_id, CMQL_ADD, (current_action == -1) ?0 :1);
1669 cm_grey_line(cm_questlog_id, CMQL_SEL, (is_over_entry && !is_deleted && !is_selected) ?0 :1);
1670 cm_grey_line(cm_questlog_id, CMQL_UNSEL, (is_over_entry && is_selected) ?0 :1);
1671 cm_grey_line(cm_questlog_id, CMQL_SELALL, quest_entries.empty() ?1 :0);
1672 cm_grey_line(cm_questlog_id, CMQL_UNSELALL, selected_entries.empty() ?1 :0);
1673 cm_grey_line(cm_questlog_id, CMQL_SHOWSEL, selected_entries.empty() ?1 :0);
1674 cm_grey_line(cm_questlog_id, CMQL_DELETE, (is_over_entry && !is_deleted) ?0 :1);
1675 cm_grey_line(cm_questlog_id, CMQL_UNDEL, (is_over_entry && is_deleted) ?0 :1);
1676 cm_grey_line(cm_questlog_id, CMQL_DEDUPE, quest_entries.empty() ?1 :0);
1677 cm_grey_line(cm_questlog_id, CMQL_SAVE, (questlog_container.save_needed()) ?0 :1);
1678 }
1679
1680
1681 // Call options selected from the context menu.
1682 //
cm_handler(window_info * win,int my,int option)1683 void Questlog_Window::cm_handler(window_info *win, int my, int option)
1684 {
1685 size_t over_entry = active_entries.size();
1686 for (std::vector<Shown_Entry>::const_iterator i=shown_entries.begin(); i!=shown_entries.end(); ++i)
1687 if (i->is_over(my))
1688 {
1689 over_entry = i->get_entry();
1690 break;
1691 }
1692 switch (option)
1693 {
1694 case CMQL_SHOWALL: questlog_container.show_all_entries(); break;
1695 case CMQL_QUESTFILTER: questlist.open_window(); break;
1696 case CMQL_NPCFILTER: npc_filter.open_window(); break;
1697 case CMQL_JUSTTHISQUEST:
1698 if (over_entry < active_entries.size())
1699 {
1700 Uint16 the_id = quest_entries[active_entries[over_entry]].get_id();
1701 if (the_id != Quest::UNSET_ID)
1702 {
1703 active_filter = QLFLT_QUEST;
1704 questlist.set_selected(the_id);
1705 questlog_container.rebuild_active_entries(quest_entries.size()-1);
1706 questlist.scroll_to_selected();
1707 }
1708 }
1709 break;
1710 case CMQL_JUSTTHISNPC:
1711 case CMQL_NPCSHOWNONE:
1712 questlist.set_selected(Quest::UNSET_ID);
1713 active_filter = QLFLT_NPC;
1714 npc_filter.unset_all();
1715 if (option == CMQL_JUSTTHISNPC)
1716 npc_filter.set(quest_entries[active_entries[over_entry]].get_npc());
1717 questlog_container.rebuild_active_entries(get_current_entry());
1718 npc_filter.open_window();
1719 break;
1720 case CMQL_COPY: if (over_entry < active_entries.size()) copy_entry(over_entry); break;
1721 case CMQL_COPYALL: copy_all_entries(); break;
1722 case CMQL_FIND: find_in_entry(win); break;
1723 case CMQL_ADD: add_entry(win, over_entry); break;
1724 case CMQL_SEL: if (over_entry < active_entries.size()) selected_entries.insert(active_entries[over_entry]); break;
1725 case CMQL_UNSEL: if (over_entry < active_entries.size()) selected_entries.erase(active_entries[over_entry]); break;
1726 case CMQL_SELALL:
1727 for (std::vector<size_t>::const_iterator i=active_entries.begin(); i!=active_entries.end(); ++i)
1728 selected_entries.insert(*i);
1729 break;
1730 case CMQL_UNSELALL:
1731 if (active_filter == QLFLT_SEL)
1732 questlog_container.show_all_entries();
1733 selected_entries.clear();
1734 break;
1735 case CMQL_SHOWSEL:
1736 active_filter = QLFLT_SEL;
1737 questlog_container.rebuild_active_entries(get_current_entry());
1738 break;
1739 case CMQL_DELETE: if (over_entry < active_entries.size()) delete_entry(over_entry); break;
1740 case CMQL_UNDEL: if (over_entry < active_entries.size()) undelete_entry(over_entry); break;
1741 case CMQL_DEDUPE: delete_duplicates(); break;
1742 case CMQL_SAVE: questlog_container.save(); break;
1743 }
1744 }
1745
1746
1747 // Update values that change with ui scaling
1748 //
ui_scale_handler(window_info * win)1749 void Questlog_Window::ui_scale_handler(window_info *win)
1750 {
1751 int content_width = 70 * 8 * win->current_scale;
1752 float zoom = win->current_scale_small;
1753
1754 qlborder = static_cast<int>(0.5 + 5 * win->current_scale);
1755 spacer = static_cast<int>(0.5 + 3 * win->current_scale);
1756 win_space = static_cast<int>(0.5 + 10 * win->current_scale);
1757 linesep = win->small_font_len_y + 2 * spacer;
1758 qlwinwidth = content_width + 2 * qlborder + win->box_size;
1759 qlwinheight = 16 * linesep;
1760
1761 widget_resize(win->window_id, quest_scroll_id, win->box_size, qlwinheight - win->box_size);
1762 widget_move(win->window_id, quest_scroll_id, qlwinwidth - win->box_size, win->box_size);
1763 resize_window(win->window_id, qlwinwidth, qlwinheight);
1764
1765 if (questlist.get_win_id() >= 0 && questlist.get_win_id() < windows_list.num_windows)
1766 questlist.ui_scale_handler(&windows_list.window[questlist.get_win_id()]);
1767
1768 if (npc_filter.get_win_id() >= 0 && npc_filter.get_win_id() < windows_list.num_windows)
1769 ui_scale_npc_filter_handler(&windows_list.window[npc_filter.get_win_id()]);
1770
1771 Quest_Entry::content_width = content_width;
1772 Quest_Entry::content_zoom = zoom;
1773 for (auto& entry: quest_entries)
1774 entry.rewrap_lines();
1775 }
1776
font_change_handler(window_info * win,FontManager::Category cat)1777 int Questlog_Window::font_change_handler(window_info *win, FontManager::Category cat)
1778 {
1779 if (cat != FontManager::Category::UI_FONT)
1780 return 0;
1781 ui_scale_handler(win);
1782 return 1;
1783 }
1784
1785 // Draw the window contents.
1786 //
display_handler(window_info * win)1787 int Questlog_Window::display_handler(window_info *win)
1788 {
1789 // If required, call the next stage of a entry input.
1790 if (prompt_for_add_text)
1791 add_text_input(win);
1792
1793 questlist.check_title_requests();
1794
1795 if (cm_window_shown() == CM_INIT_VALUE)
1796 {
1797 cm_questlog_over_entry = active_entries.size();
1798 if (show_help_text && mouse_over_questlog && (current_action == -1))
1799 {
1800 show_help(questlog_cm_help_str, 0, win->len_y + win_space, win->current_scale);
1801 mouse_over_questlog = false;
1802 }
1803 }
1804
1805 TextDrawOptions options = TextDrawOptions().set_max_lines(1)
1806 .set_zoom(win->current_scale_small);
1807 FontManager& font_manager = FontManager::get_instance();
1808 int questlog_y = qlborder;
1809 shown_entries.clear();
1810 for (std::vector<Quest_Entry>::size_type entry = current_line; entry<active_entries.size(); entry++)
1811 {
1812 int start_y = questlog_y;
1813 const std::vector<ustring> &lines = quest_entries[active_entries[entry]].get_lines();
1814 glColor3f(1.0f, 1.0f, 1.0f);
1815 for (const auto& line: lines)
1816 {
1817 font_manager.draw(FontManager::Category::UI_FONT, line.c_str(), line.length(),
1818 qlborder, questlog_y, options);
1819 questlog_y += win->small_font_len_y;
1820 if (questlog_y+qlborder > qlwinheight - win->small_font_len_y)
1821 break;
1822 }
1823 glColor3f(0.7f, 0.7f, 1.0f);
1824 if ((cm_questlog_over_entry < active_entries.size()) && (entry == cm_questlog_over_entry))
1825 {
1826 glColor3fv(gui_color);
1827 const ustring& name = quest_entries[active_entries[entry]].get_disp_npc();
1828 font_manager.draw(FontManager::Category::UI_FONT, name.c_str(), name.length(),
1829 qlborder, start_y, options);
1830 }
1831 if (selected_entries.find(active_entries[entry]) != selected_entries.end())
1832 {
1833 const ustring& name = quest_entries[active_entries[entry]].get_disp_npc();
1834 int width = font_manager.line_width(UI_FONT, name.c_str(), name.length(),
1835 options.zoom());
1836 draw_underline(qlborder, start_y + win->small_font_len_y,
1837 qlborder + width, start_y + win->small_font_len_y);
1838 }
1839 shown_entries.push_back(Shown_Entry(entry, start_y, questlog_y));
1840 questlog_y += qlborder;
1841 if (questlog_y+qlborder > qlwinheight - win->small_font_len_y)
1842 return 1;
1843 }
1844 return 1;
1845 }
1846
1847
1848 // Move to quest entry after scroll click
scroll_click_handler(widget_list * widget)1849 void Questlog_Window::scroll_click_handler(widget_list *widget)
1850 {
1851 int scroll = vscrollbar_get_pos (get_id_MW(MW_QUESTLOG), widget->id);
1852 goto_entry(scroll);
1853 }
1854
1855
1856 // Move to quest entry after scroll drag
scroll_drag_handler(widget_list * widget)1857 void Questlog_Window::scroll_drag_handler(widget_list *widget)
1858 {
1859 int scroll = vscrollbar_get_pos (get_id_MW(MW_QUESTLOG), widget->id);
1860 goto_entry(scroll);
1861 }
1862
1863
1864 // Move to entry of click wheel button in window
click_handler(window_info * win,Uint32 flags)1865 int Questlog_Window::click_handler(window_info *win, Uint32 flags)
1866 {
1867 int questlog_win = get_id_MW(MW_QUESTLOG);
1868 if(flags&ELW_WHEEL_UP) {
1869 vscrollbar_scroll_up(questlog_win, quest_scroll_id);
1870 goto_entry(vscrollbar_get_pos(questlog_win, quest_scroll_id));
1871 return 1;
1872 } else if(flags&ELW_WHEEL_DOWN) {
1873 vscrollbar_scroll_down(questlog_win, quest_scroll_id);
1874 goto_entry(vscrollbar_get_pos(questlog_win, quest_scroll_id));
1875 return 1;
1876 } else {
1877 return 0;
1878 }
1879 }
1880
1881
1882 // Keypress in window, check for find key
keypress_handler(window_info * win,SDL_Keycode key_code,Uint32 key_unicode,Uint16 key_mod)1883 int Questlog_Window::keypress_handler(window_info *win, SDL_Keycode key_code, Uint32 key_unicode, Uint16 key_mod)
1884 {
1885 char keychar = tolower(static_cast<char>(key_unicode));
1886 if (KEY_DEF_CMP(K_MARKFILTER, key_code, key_mod) || (keychar=='/'))
1887 {
1888 find_in_entry(win);
1889 return 1;
1890 }
1891 return 0;
1892 }
1893
1894
1895 // Register mouse over window
mouseover_handler(int my)1896 void Questlog_Window::mouseover_handler(int my)
1897 {
1898 if (my>=0)
1899 mouse_over_questlog = true;
1900 }
1901
1902
1903 // Scroll to the specified line
goto_entry(int ln)1904 void Questlog_Window::goto_entry(int ln)
1905 {
1906 if(ln <= 0)
1907 current_line = 0;
1908 else if (static_cast<size_t>(ln) >= active_entries.size())
1909 current_line = active_entries.size() - 1;
1910 else
1911 current_line = ln;
1912 vscrollbar_set_pos(get_id_MW(MW_QUESTLOG), quest_scroll_id, current_line);
1913 }
1914
1915
1916 // Create (or show existing) a stand alone quest log window.
1917 //
open(void)1918 void Questlog_Window::open(void)
1919 {
1920 int questlog_win = get_id_MW(MW_QUESTLOG);
1921
1922 if (questlog_win < 0)
1923 {
1924 questlog_win = create_window(tab_questlog, (not_on_top_now(MW_QUESTLOG) ?game_root_win : -1), 0,
1925 get_pos_x_MW(MW_QUESTLOG), get_pos_y_MW(MW_QUESTLOG), 0, 0, ELW_USE_UISCALE|ELW_WIN_DEFAULT);
1926 if (questlog_win < 0 || questlog_win >= windows_list.num_windows)
1927 return;
1928 set_id_MW(MW_QUESTLOG, questlog_win);
1929 set_window_custom_scale(questlog_win, MW_QUESTLOG);
1930 set_window_handler(questlog_win, ELW_HANDLER_DISPLAY, (int (*)())display_questlog_handler);
1931 set_window_handler(questlog_win, ELW_HANDLER_CLICK, (int (*)())questlog_click);
1932 set_window_handler(questlog_win, ELW_HANDLER_MOUSEOVER, (int (*)())&mouseover_questlog_handler );
1933 set_window_handler(questlog_win, ELW_HANDLER_KEYPRESS, (int (*)())&keypress_questlog_handler );
1934 set_window_handler(questlog_win, ELW_HANDLER_SHOW, (int (*)())&show_questlog_handler );
1935 set_window_handler(questlog_win, ELW_HANDLER_UI_SCALE, (int (*)())&ui_scale_questlog_handler );
1936 set_window_handler(questlog_win, ELW_HANDLER_FONT_CHANGE, (int (*)())&change_questlog_font_handler);
1937
1938 window_info *win = &windows_list.window[questlog_win];
1939 ui_scale_questlog_handler(win);
1940 check_proportional_move(MW_QUESTLOG);
1941
1942 size_t last_entry = active_entries.size()-1;
1943 quest_scroll_id = vscrollbar_add_extended (questlog_win, quest_scroll_id, NULL, qlwinwidth - win->box_size, win->box_size, win->box_size, qlwinheight - win->box_size, 0, 1.0, last_entry, 1, last_entry);
1944 goto_entry(last_entry);
1945
1946 widget_set_OnClick (questlog_win, quest_scroll_id, (int (*)())questlog_scroll_click);
1947 widget_set_OnDrag (questlog_win, quest_scroll_id, (int (*)())questlog_scroll_drag);
1948
1949 if (!cm_valid(cm_questlog_id))
1950 {
1951 cm_questlog_id = cm_create(cm_questlog_menu_str, cm_quest_handler);
1952 cm_add_window(cm_questlog_id, questlog_win);
1953 cm_set_pre_show_handler(cm_questlog_id, cm_questlog_pre_show_handler);
1954 init_ipu(&ipu_questlog, -1, 1, 1, 1, NULL, NULL);
1955 }
1956 }
1957 else
1958 show_window(questlog_win);
1959
1960 questlist.check_auto_open();
1961 }
1962
1963
1964 // When entries are added or the filter changes, recreate the active entry list.
1965 //
rebuild_active_entries(size_t desired_top_entry)1966 void Questlog_Container::rebuild_active_entries(size_t desired_top_entry)
1967 {
1968 size_t new_current_line = 0;
1969 active_entries.clear();
1970 for (std::vector<Quest_Entry>::size_type entry=0; entry<quest_entries.size(); ++entry)
1971 {
1972 if (entry == desired_top_entry)
1973 new_current_line = active_entries.size();
1974 bool useit = false;
1975 switch (active_filter)
1976 {
1977 case QLFLT_NONE: useit = true; break;
1978 case QLFLT_NPC: if (npc_filter.is_set(quest_entries[entry].get_npc())) useit = true; break;
1979 case QLFLT_QUEST:
1980 if ((questlist.get_selected() == Quest::UNSET_ID) ||
1981 (questlist.get_selected() == quest_entries[entry].get_id()))
1982 useit = true;
1983 break;
1984 case QLFLT_SEL:
1985 if (selected_entries.find(entry) != selected_entries.end())
1986 useit = true;
1987 break;
1988 }
1989 if (useit)
1990 active_entries.push_back(entry);
1991 }
1992 questlog_window.update_scrollbar_len();
1993 questlog_window.goto_entry(new_current_line);
1994 }
1995
1996
1997 // If needed, save the complete questlog.
1998 //
save(void)1999 void Questlog_Container::save(void)
2000 {
2001 if (!need_to_save)
2002 return;
2003 std::ofstream out(filename.c_str(), std::ios_base::out | std::ios_base::binary | std::ios_base::trunc);
2004 if (!out)
2005 {
2006 std::string error_str = std::string(file_write_error_str) + ' ' + filename;
2007 LOG_TO_CONSOLE(c_red2, error_str.c_str());
2008 LOG_ERROR("%s: %s \"%s\"\n", reg_error_str, file_write_error_str, filename.c_str());
2009 return;
2010 }
2011 for (std::vector<Quest_Entry>::const_iterator entry=quest_entries.begin(); entry!=quest_entries.end(); ++entry)
2012 if (!entry->get_deleted())
2013 entry->save(out);
2014 need_to_save = false;
2015
2016 LOG_DEBUG("Wrote questlog to file '%s'", filename.c_str());
2017 }
2018
2019
2020 // Reset the filters and active lists so that all entries are shown.
2021 //
show_all_entries(void)2022 void Questlog_Container::show_all_entries(void)
2023 {
2024 active_filter = QLFLT_NONE;
2025 // set all NPC entries
2026 npc_filter.set_all();
2027 // clean a quest filter
2028 questlist.set_selected(Quest::UNSET_ID);
2029 rebuild_active_entries(questlog_window.get_current_entry());
2030 }
2031
2032
2033 // Add a new entry to the end of the quest log. The entry will either
2034 // have been read from the file or about to be appended to the end.
2035 // Note:
2036 // Rebuilding the active_entry list must be done by the caller after this.
2037 //
add_line(const ustring & t,const unsigned char * npcprefix)2038 void Questlog_Container::add_line(const ustring& t, const unsigned char *npcprefix)
2039 {
2040 quest_entries.push_back(Quest_Entry());
2041 if (*npcprefix)
2042 quest_entries.back().set(t, ustring(npcprefix));
2043 else
2044 quest_entries.back().set(t);
2045 }
2046
2047
2048 // Called when a new quest entry is received from the server. Add to
2049 // the end of the entries and append to the questlog file immediately.
2050 //
add_entry(const unsigned char * t,int len)2051 void Questlog_Container::add_entry(const unsigned char *t, int len)
2052 {
2053 add_line(ustring(t, len), (const unsigned char*)npc_name);
2054
2055 if (questlist.waiting_for_entry())
2056 {
2057 quest_entries.back().set_id(questlist.get_next_id());
2058 questlist.clear_next_id();
2059 }
2060
2061 if ((active_filter == QLFLT_QUEST) && (questlist.get_selected() != Quest::UNSET_ID))
2062 questlist.set_selected(quest_entries.back().get_id());
2063 rebuild_active_entries(quest_entries.size()-1);
2064
2065 std::ofstream out(filename.c_str(), std::ios_base::out | std::ios_base::binary | std::ios_base::app);
2066 if (!out)
2067 {
2068 std::string error_str = std::string(file_write_error_str) + ' ' + filename;
2069 LOG_TO_CONSOLE(c_red2, error_str.c_str());
2070 LOG_ERROR("%s: %s \"%s\"\n", reg_error_str, file_write_error_str, filename.c_str());
2071 return;
2072 }
2073 quest_entries.back().save(out);
2074 flash_icon(tt_questlog, 5);
2075 }
2076
2077
2078 // Load the questlog, use the playname tag file but fall back to the
2079 // old file name if the new does not exist. If reading from an old
2080 // named file, write the new file as we go.
2081 //
load(void)2082 void Questlog_Container::load(void)
2083 {
2084 questlist.load();
2085
2086 // If the quest log is already loaded, just make sure we're saved.
2087 // This will take place when relogging after disconnection.
2088 if (!quest_entries.empty())
2089 {
2090 save();
2091 return;
2092 }
2093
2094 filename = std::string(get_path_config()) + "quest_" + std::string(get_lowercase_username()) + ".log";
2095
2096 std::ifstream in(filename.c_str(), std::ios_base::in | std::ios_base::binary);
2097 std::ofstream out;
2098 if (!in)
2099 {
2100 in.clear();
2101 in.open((std::string(get_path_config()) + "quest.log").c_str(), std::ios_base::in | std::ios_base::binary);
2102 if (!in)
2103 return;
2104 out.open(filename.c_str(), std::ios_base::out | std::ios_base::binary | std::ios_base::trunc);
2105 }
2106
2107 std::string line;
2108 while (getline(in, line))
2109 {
2110 if (!line.empty())
2111 {
2112 add_line(ustring(line.begin(), line.end()),
2113 reinterpret_cast<const unsigned char*>(""));
2114 if (out)
2115 quest_entries.back().save(out);
2116 }
2117 }
2118
2119 LOG_DEBUG("Read questlog from file '%s'", filename.c_str());
2120
2121 rebuild_active_entries(quest_entries.size()-1);
2122 }
2123
2124
add_questlog(const unsigned char * t,int len)2125 extern "C" void add_questlog(const unsigned char *t, int len)
2126 {
2127 questlog_container.add_entry(t, len);
2128 }
2129
2130
load_questlog(void)2131 extern "C" void load_questlog(void)
2132 {
2133 questlog_container.load();
2134 }
2135
unload_questlog()2136 extern "C" void unload_questlog()
2137 {
2138 questlog_container.save();
2139 questlist.save();
2140 }
2141
display_questlog(void)2142 extern "C" void display_questlog(void)
2143 {
2144 questlog_window.open();
2145 }
2146
set_next_quest_entry_id(Uint16 id)2147 extern "C" void set_next_quest_entry_id(Uint16 id)
2148 {
2149 //char buf[80];
2150 //safe_snprintf(buf, 80, "Received NEXT_NPC_MESSAGE_IS_QUEST with id=%d", id);
2151 //LOG_TO_CONSOLE(c_green1, buf);
2152 //if (waiting_for_questlog_entry())
2153 // LOG_TO_CONSOLE(c_red2, "Previous NEXT_NPC_MESSAGE_IS_QUEST was unused");
2154 questlist.set_next_id(id);
2155 questlist.add(id);
2156 }
2157
2158
2159 // Return true if a NEXT_NPC_MESSAGE_IS_QUEST message has been received
2160 // but has not yet been used.
2161 //
waiting_for_questlog_entry(void)2162 extern "C" int waiting_for_questlog_entry(void)
2163 {
2164 return questlist.waiting_for_entry();
2165 }
2166
2167
2168 // Make sure we are not expecting a new questlog entry
2169 //
clear_waiting_for_questlog_entry(void)2170 extern "C" void clear_waiting_for_questlog_entry(void)
2171 {
2172 questlist.clear_next_id();
2173 }
2174
2175
set_quest_title(const char * data,int len)2176 extern "C" void set_quest_title(const char *data, int len)
2177 {
2178 char buf[256];
2179 //LOG_TO_CONSOLE(c_green1, "Received HERE_IS_QUEST_ID");
2180 safe_strncpy2(buf, data, 255, len);
2181 //LOG_TO_CONSOLE(c_green1, buf);
2182 questlist.set_requested_title(buf);
2183 }
2184
2185
set_quest_finished(Uint16 id)2186 extern "C" void set_quest_finished(Uint16 id)
2187 {
2188 //char buf[80];
2189 //safe_snprintf(buf, 80, "Received QUEST_FINISHED with id=%d", id);
2190 //LOG_TO_CONSOLE(c_green1, buf);
2191 questlist.set_completed(id, true);
2192 }
2193
2194
get_options_questlog(void)2195 extern "C" unsigned int get_options_questlog(void)
2196 {
2197 return questlist.get_options();
2198 }
2199
2200
set_options_questlog(unsigned int cfg_options)2201 extern "C" void set_options_questlog(unsigned int cfg_options)
2202 {
2203 questlist.set_options(cfg_options);
2204 }
2205
2206
2207 #ifdef JSON_FILES
write_options_questlog(const char * dict_name)2208 extern "C" void write_options_questlog(const char *dict_name)
2209 {
2210 questlist.write_options(dict_name);
2211 }
2212
2213
read_options_questlog(const char * dict_name)2214 extern "C" void read_options_questlog(const char *dict_name)
2215 {
2216 questlist.read_options(dict_name);
2217 }
2218 #endif
2219