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 &copy_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 &copy_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