1 /*
2 	Display windows showing players achievement icons and text.
3 
4 	Author bluap/pjbroad (an unusually chilly) November 2010
5 */
6 
7 #include <iostream>
8 #include <sstream>
9 #include <vector>
10 #include <list>
11 
12 #include "actors.h"
13 #include "asc.h"
14 #include "achievements.h"
15 #include "context_menu.h"
16 #include "elwindows.h"
17 #include "errors.h"
18 #include "elconfig.h"
19 #include "interface.h"
20 #include "gamewin.h"
21 #include "gl_init.h"
22 #include "hud.h"
23 #include "init.h"
24 #include "sound.h"
25 #include "text.h"
26 #include "textures.h"
27 #include "io/elfilewrapper.h"
28 #include "image_loading.h"
29 
30 /*
31  *	TO DO
32  * 		fully comment
33  * 		fix issue with mouse over open causing other windows to flicker
34  * 		check create window status and warn if out of slots - fix handling elsewhere
35 */
36 
37 using namespace eternal_lands;
38 
39 //	A class to hold a single achievement object.
40 //
41 class Achievement
42 {
43 	public:
44 		Achievement(int achievement_id, int image_id, const char *title, const char *text);
45 		void show(std::ostream & out) const;
get_text() const46 		const ustring& get_text() const { return lines; }
get_title(void) const47 		const std::string & get_title(void) const { return title; }
get_id(void) const48 		size_t get_id(void) const { return image_id; }
49 		static const size_t npos;
50 		void prepare(int win_x, float zoom, int border, bool force=false);
get_num_lines() const51 		size_t get_num_lines() const { return nr_lines; }
52 	private:
53 		ustring text;
54 		std::string title;
55 		size_t achievement_id;
56 		size_t image_id;
57 		bool prepared;
58 		ustring lines;
59 		size_t nr_lines;
60 };
61 
62 
63 const size_t Achievement::npos = static_cast<size_t>(-1);
64 
65 
66 //	Construct a new achievement object
67 //
Achievement(int achievement_id,int image_id,const char * title,const char * text)68 Achievement::Achievement(int achievement_id, int image_id, const char *title, const char *text)
69  : prepared(false)
70 {
71 	this->achievement_id = achievement_id;
72 	this->image_id = image_id;
73 	this->title = title;
74 	this->text = (const unsigned char*)text;
75 }
76 
77 
78 //	Show achievement object data
79 //
show(std::ostream & out) const80 void Achievement::show(std::ostream & out) const
81 {
82 	out << "ID=" << achievement_id << " IMG=" << image_id;
83 	out << " Title=[" << title << "] Text=["
84 		<< reinterpret_cast<const char*>(text.c_str()) << "]";
85 }
86 
87 
88 //	Process the main text into lines that fix into the pop-up window
89 //
prepare(int win_x,float zoom,int border,bool force)90 void Achievement::prepare(int win_x, float zoom, int border, bool force)
91 {
92 	if (!prepared || force)
93 	{
94 		TextDrawOptions options = TextDrawOptions().set_max_width(win_x - 2 * border)
95 			.set_zoom(zoom);
96 		std::tie(lines, nr_lines) = FontManager::get_instance().reset_soft_breaks(
97 			FontManager::Category::UI_FONT, text, options);
98 		prepared = true;
99 	}
100 }
101 
102 
103 //	A single achievement window, handles all the rendering and interation
104 //
105 class Achievements_Window
106 {
107 	public:
Achievements_Window(void)108 		Achievements_Window(void) : win_mouse_x(-1), win_mouse_y(-1), clicked(false), ctrl_clicked(false), main_win_id(-1),
109 			child_win_id(-1), logical_rows(0), physical_rows(0), first(0), last_over(Achievement::npos) {}
110 		~Achievements_Window(void);
111 		void set_achievements(const std::vector<Uint32> & data);
112 		void set_name(const std::string & name);
get_name(void) const113 		const std::string & get_name(void) const { return their_name; }
114 		void open(int win_pos_x, int win_pos_y);
115 		void open_child(void);
116 		int display_handler(window_info *win);
117 		void ui_scale_handler(window_info *win);
118 		int font_change_handler(window_info *win, FontManager::Category cat);
shown(void) const119 		bool shown(void) const { return get_show_window(main_win_id); }
hide(void) const120 		void hide(void) const { hide_window(main_win_id); }
set_mouse_over(int mx,int my)121 		void set_mouse_over(int mx, int my) { win_mouse_x = mx; win_mouse_y = my; }
window_clicked(void)122 		void window_clicked(void) { clicked = true; }
window_ctrl_clicked(void)123 		void window_ctrl_clicked(void)  { ctrl_clicked = true; }
get_window_id(void) const124 		int get_window_id(void) const { return main_win_id; }
125 	private:
126 		std::vector<size_t> their_achievements;
127 		std::string their_name;
128 		int win_mouse_x, win_mouse_y;
129 		bool clicked;
130 		bool ctrl_clicked;
131 		int main_win_id, child_win_id;
132 		int logical_rows, physical_rows;
133 		size_t first;
134 		size_t last_over;
135 };
136 
137 
138 
139 //	Controlling class, holds collections of achievement objects & windows and
140 //	the common properties and functions.  Reads the xml file and creates the
141 //	achievement objects.  Handles requests for new windows, creating the window
142 //	objects.
143 //
144 class Achievements_System
145 {
146 	public:
147 		Achievements_System(void);
148 		~Achievements_System(void);
149 		const Achievement * achievement(size_t index) const;
150 		void prepare_details(size_t index, bool force=false);
151 		void new_data(const Uint32 *data, size_t word_count);
152 		void new_name(const char *name, int len);
requested(int mouse_pos_x,int mouse_pos_y,int control_used)153 		void requested(int mouse_pos_x, int mouse_pos_y, int control_used) { win_pos_x = mouse_pos_x; win_pos_y = mouse_pos_y; this->control_used = control_used; }
154 		static Achievements_System * get_instance(void);
155 		void show(void) const;
156 		int texture(size_t index) const;
157 		bool hide_all(void) const;
get_size(void) const158 		int get_size(void) const { return size; }
set_current_scale(float scale)159 		void set_current_scale(float scale) { current_scale = scale; }
get_current_scale(void) const160 		float get_current_scale(void) const { return current_scale; }
get_font_width(const std::string & str) const161 		int get_font_width(const std::string& str) const
162 		{
163 			return get_string_width_zoom(reinterpret_cast<const unsigned char*>(str.c_str()),
164 				UI_FONT, current_scale * DEFAULT_SMALL_RATIO);
165 		}
get_font_y() const166 		int get_font_y() const
167 		{
168 			return get_line_height(UI_FONT, current_scale * DEFAULT_SMALL_RATIO);
169 		}
get_display(void) const170 		int get_display(void) const { return static_cast<int>(0.5 + display * current_scale); }
get_y_win_offset(void) const171 		int get_y_win_offset(void) const { return static_cast<int>(0.5 + y_win_offset * current_scale); }
get_per_row(void) const172 		int get_per_row(void) const { return per_row; }
get_max_rows(void) const173 		int get_max_rows(void) const { return max_rows; }
get_border(void) const174 		int get_border(void) const { return static_cast<int>(0.5 + border * current_scale); }
main_win_x(void) const175 		int main_win_x(void) const { return per_row * get_display() + 3 * get_border(); }
176 		int get_child_win_x(void) const;
get_child_win_y(void) const177 		int get_child_win_y(void) const { return get_font_y() * (1 + max_detail_lines) + 2 * get_border(); }
get_prev(void) const178 		const std::string & get_prev(void) const { return prev; }
get_next(void) const179 		const std::string & get_next(void) const { return next; }
get_close(void) const180 		const std::string & get_close(void) const { return close; }
get_close_help(void) const181 		const char * get_close_help(void) const { return close_help.c_str(); }
get_prev_help(void) const182 		const char * get_prev_help(void) const { return prev_help.c_str(); }
get_no_prev_help(void) const183 		const char * get_no_prev_help(void) const { return no_prev_help.c_str(); }
get_next_help(void) const184 		const char * get_next_help(void) const { return next_help.c_str(); }
get_no_next_help(void) const185 		const char * get_no_next_help(void) const { return no_next_help.c_str(); }
rewrap_achievement_texts()186 		void rewrap_achievement_texts()
187 		{
188 			max_detail_lines = 2;
189 			for (size_t i = 0; i < achievements.size(); ++i)
190 				prepare_details(i, true);
191 		}
192 	private:
193 		void get_int_props(const xmlNodePtr cur, int *props_p[], const char *props_s[], size_t num);
194 		void get_string_props(const xmlNodePtr cur, std::string * strings_p[], const char* strings[], size_t count);
195 		std::vector<Achievement *> achievements;
196 		std::list<Achievements_Window *> windows;
197 		std::vector<Uint32> last_data;
198 		std::vector<int> textures;
199 		int size, display, y_win_offset;
200 		int per_row, min_rows, max_rows, border;
201 		std::string prev, next, close;
202 		std::string too_many, xml_fail, texture_fail;
203 		std::string close_help, no_next_help, no_prev_help, next_help, prev_help;
204 		int widest_title_id;
205 		size_t max_detail_lines;
206 		size_t max_windows;
207 		int win_pos_x, win_pos_y;
208 		bool control_used;
209 		float current_scale;
210 };
211 
212 
213 //	Work out if the specified point of the specified window is on top of the window stack.
214 //	Method to traverse layers copied from elwindow.c this could probably be a general window function.
215 //
is_window_coord_top(int window_id,int coord_x,int coord_y)216 bool is_window_coord_top(int window_id, int coord_x, int coord_y)
217 {
218 	bool have_seen_window = false;
219 	int id = 0;
220 	int i;
221 	while (1)
222 	{
223 		int next_id = 9999;
224 		for (i = 0; i < windows_list.num_windows; i++)
225 		{
226 			// only look at displayed windows
227 			if (windows_list.window[i].displayed > 0)
228 			{
229 				// at this level?
230 				if (windows_list.window[i].order == id)
231 				{
232 					if (i == window_id)
233 						have_seen_window = true;
234 					else if (have_seen_window)
235 					{
236 						if (mouse_in_window(i, coord_x, coord_y))
237 							return false;
238 					}
239 				}
240 				// try to find the next level
241 				else if (windows_list.window[i].order > id && windows_list.window[i].order < next_id)
242 					next_id = windows_list.window[i].order;
243 			}
244 		}
245 		if (next_id >= 9999)
246 			break;
247 		else
248 			id = next_id;
249 	}
250 	return true;
251 }
252 
253 
254 //	Read the achievements xml file and create all the achievement objects,
255 //	the global settings and the textures.
256 //
Achievements_System(void)257 Achievements_System::Achievements_System(void)
258  : size(32), display(32), y_win_offset(10), per_row(5), min_rows(1), max_rows(12), border(2),
259 	prev("[<]"), next("[>]"), close("[close]"),
260 	too_many("Too many achievement windows open already"),
261 	xml_fail("Failed to load achievement data"),
262 	texture_fail("Failed to load achievement texture"),
263 	close_help("Close or +ctrl close all"),
264 	no_next_help("No more pages"),
265 	no_prev_help("No previous page"),
266 	next_help("Next page"),
267 	prev_help("Previous page"),
268 	widest_title_id(0),
269 	max_detail_lines(2), max_windows(15), win_pos_x(100), win_pos_y(50), control_used(false), current_scale(1.0)
270 {
271 	xmlDocPtr doc;
272 	xmlNodePtr cur;
273 	char const *error_prefix = "Reading xml: ";
274 
275 	std::ostringstream langpath;
276 	langpath << "languages/" << lang << "/achievements.xml";
277 	if ((doc = xmlReadFile(langpath.str().c_str(), NULL, 0)) == NULL)
278 	{
279 		const char *path = "languages/en/achievements.xml";
280 		if ((doc = xmlReadFile(path, NULL, 0)) == NULL)
281 		{
282 			LOG_ERROR("%sCan't open file [%s]\n", error_prefix, path );
283 			return;
284 		}
285 	}
286 
287 	if ((cur = xmlDocGetRootElement (doc)) == NULL)
288 	{
289 		LOG_ERROR("%sEmpty xml document\n", error_prefix );
290 		xmlFreeDoc(doc);
291 		return;
292 	}
293 
294 	if (xmlStrcasecmp (cur->name, (const xmlChar *) "achievements_system"))
295 	{
296 		LOG_ERROR("%sNot achievements system.\n", error_prefix );
297 		xmlFreeDoc(doc);
298 		return;
299 	}
300 
301 	int max_title_width = 0;
302 	for (cur = cur->xmlChildrenNode; cur; cur = cur->next)
303 	{
304 		if (!xmlStrcasecmp(cur->name, (const xmlChar *)"achievement"))
305 		{
306 			char *text = (char*)(cur->children ? cur->children->content : NULL);
307 			char *title = (char*)xmlGetProp(cur, (xmlChar *)"title");
308 
309 			int achievement_id = -1, image_id = -1;
310 			int *props_p[2] = { &achievement_id, &image_id };
311 			const char *props_s[2] =  { "achievement_id", "image_id" };
312 			get_int_props(cur, props_p, props_s, 2);
313 
314 			if ((text == NULL) || (achievement_id < 0) || (image_id < 0) || (title == NULL))
315 			{
316 				LOG_WARNING("%sInvalid achievements node\n", error_prefix );
317 				continue;
318 			}
319 
320 			char *proc_title = 0;
321 			char *proc_text = 0;
322 			MY_XMLSTRCPY(&proc_title, title);
323 			MY_XMLSTRCPY(&proc_text, text);
324 			xmlFree(title);
325 
326 			if ((achievement_id < 0) || (achievement_id >= MAX_ACHIEVEMENTS))
327 				LOG_ERROR("%sInvalid achievement id=%lu\n", error_prefix, achievement_id );
328 			else
329 			{
330 				achievements.resize(achievement_id+1, 0);
331 				if (achievements[achievement_id])
332 					LOG_ERROR("%sDuplicate achievement id=%lu\n", error_prefix, achievement_id );
333 				else
334 				{
335 					achievements[achievement_id] = new Achievement(achievement_id, image_id, proc_title, proc_text);
336 					int title_width = get_font_width(achievements[achievement_id]->get_title());
337 					if (title_width > max_title_width)
338 					{
339 						max_title_width = title_width;
340 						widest_title_id = achievement_id;
341 					}
342 				}
343 			}
344 
345 			if (proc_title) free(proc_title);
346 			if (proc_text) free(proc_text);
347 		}
348 		else if (!xmlStrcasecmp(cur->name, (const xmlChar *)"image_settings"))
349 		{
350 			int *props_p[2] = { &size, &display };
351 			const char * props_s[2] =  { "size", "display" };
352 			get_int_props(cur, props_p, props_s, 2);
353 		}
354 		else if (!xmlStrcasecmp(cur->name, (const xmlChar *)"window_settings"))
355 		{
356 			int *props_p[4] = { &per_row, &min_rows, &max_rows, &border };
357 			const char *props_s[4] =  { "per_row", "min_rows", "max_rows", "border" };
358 			get_int_props(cur, props_p, props_s, 4);
359 			std::string * ctrl_p[3] = { &prev, &next, &close };
360 			const char* ctrl_s[3] = { "prev", "next", "close" };
361 			get_string_props(cur, ctrl_p, ctrl_s, 3);
362 		}
363 		else if (!xmlStrcasecmp(cur->name, (const xmlChar *)"strings"))
364 		{
365 			const size_t count = 8;
366 			std::string * strings_p[count] = { &too_many, &xml_fail, &texture_fail,
367 				&close_help, &no_next_help, &no_prev_help, &next_help, &prev_help };
368 			const char* strings_s[count] = { "too_many", "xml_fail", "texture_fail",
369 				"close_help", "no_next_help", "no_prev_help", "next_help", "prev_help" };
370 			get_string_props(cur, strings_p, strings_s, count);
371 		}
372 		else if (!xmlStrcasecmp(cur->name, (const xmlChar *)"texture"))
373 		{
374 			char *path = (char*)(cur->children ? cur->children->content : NULL);
375 			char buffer[1024];
376 
377 			if (check_image_name(path, sizeof(buffer), buffer) == 1)
378 			{
379 				textures.push_back(load_texture_cached(buffer, tt_gui));
380 			}
381 		}
382 	}
383 	xmlFreeDoc(doc);
384 }
385 
386 
387 //	Destroy all the achievement objects and windows.
388 //
~Achievements_System(void)389 Achievements_System::~Achievements_System(void)
390 {
391 	for (std::list<Achievements_Window *>::iterator i = windows.begin(); i!= windows.end(); ++i)
392 		delete *i;
393 	for (std::vector<Achievement *>::iterator i = achievements.begin(); i!= achievements.end(); ++i)
394 		delete *i;
395 }
396 
397 
398 //	Return an achievement object.
399 //
achievement(size_t index) const400 const Achievement * Achievements_System::achievement(size_t index) const
401 {
402 	if (index < achievements.size())
403 		return achievements[index];
404 	else
405 		return 0;
406 }
407 
408 
409 //	Return a texture.
410 //
texture(size_t index) const411 int Achievements_System::texture(size_t index) const
412 {
413 	if (index<textures.size())
414 		return textures[index];
415 	else
416 		return -1;
417 }
418 
419 
420 //	Return the width of the popup window
get_child_win_x(void) const421 int Achievements_System::get_child_win_x(void) const
422 {
423 	int proposed = get_font_width(achievements[widest_title_id]->get_title()) + 2 * get_border();
424 	return std::max(proposed, main_win_x());
425 }
426 
427 
428 //	Save the data for the next window
429 //
new_data(const Uint32 * data,size_t word_count)430 void Achievements_System::new_data(const Uint32 *data, size_t word_count)
431 {
432 	last_data.resize(word_count);
433 	for (size_t i=0; i<word_count; ++i)
434 		last_data[i] = data[i];
435 }
436 
437 
438 //	Now we have the data and a name, create a new window.
439 //
new_name(const char * player_name,int len)440 void Achievements_System::new_name(const char *player_name, int len)
441 {
442 	if (achievements.empty())
443 	{
444 		LOG_TO_CONSOLE(c_red1, xml_fail.c_str());
445 		return;
446 	}
447 	if (textures.empty())
448 	{
449 		LOG_TO_CONSOLE(c_red1, texture_fail.c_str());
450 		return;
451 	}
452 
453 	if (achievements_ctrl_click && !control_used)
454 	{
455 		last_data.clear();
456 		return;
457 	}
458 
459 	std::string name;
460 	if (player_name && (len > 0))
461 	{
462 		char *tmp = new char[len+1];
463 		safe_strncpy2(tmp, player_name, len+1, len);
464 		name = tmp;
465 
466 		// check for already open window for player and delete it
467 		for (std::list<Achievements_Window *>::iterator i = windows.begin(); i!= windows.end(); ++i)
468 			if (*i && (*i)->get_name() == name)
469 			{
470 				delete *i;
471 				*i = 0;
472 				break;
473 			}
474 
475 		delete [] tmp;
476 	}
477 
478 	// remove any closed windows
479 	for (std::list<Achievements_Window *>::iterator i = windows.begin(); i!= windows.end(); ++i)
480 		if (*i && !(*i)->shown())
481 		{
482 			delete *i;
483 			*i = 0;
484 		}
485 	windows.remove(0);
486 
487 	if (last_data.empty())
488 		return;
489 
490 	// limit the number of windows so that we don't run out for everyone else
491 	if (windows.size() < max_windows)
492 	{
493 		windows.push_back(new Achievements_Window);
494 		windows.back()->set_achievements(last_data);
495 		windows.back()->set_name(name);
496 		windows.back()->open(win_pos_x, win_pos_y);
497 	}
498 	else
499 		LOG_TO_CONSOLE(c_red1, too_many.c_str());
500 
501 	last_data.clear();
502 }
503 
504 
505 //	Ctrl-click close on any window closes them all, hides now but destroys later
506 //
hide_all(void) const507 bool Achievements_System::hide_all(void) const
508 {
509 	bool return_value = false;
510 	for (std::list<Achievements_Window *>::const_iterator i = windows.begin(); i!= windows.end(); ++i)
511 	{
512 		if ((*i)->shown())
513 			return_value = true;
514 		(*i)->hide();
515 	}
516 	return return_value;
517 }
518 
519 
520 //	Show global properties read form the xml file.
521 //
show(void) const522 void Achievements_System::show(void) const
523 {
524 	std::cout << "image props size=" << size << " display=" << get_display() << std::endl;
525 	std::cout << "window props per_row=" << per_row << " min_rows=" << min_rows << " max_rows=" << max_rows << " border=" << get_border() << std::endl;
526 	std::cout << "window strings prev=" << prev << " next=" << next << " close=" << close << std::endl;
527 }
528 
529 
530 //	The evil singelton mechanism.
531 //
get_instance(void)532 Achievements_System * Achievements_System::get_instance(void)
533 {
534 	static Achievements_System as;
535 	static SDL_threadID creation_thread = SDL_ThreadID();
536 	if (SDL_ThreadID() != creation_thread)
537 		std::cerr << __FUNCTION__ << ": Achievements system called by non-creator thread." << std::endl;
538 	return &as;
539 }
540 
541 
542 //	A helper function for reading xml integer properties
543 //
get_int_props(const xmlNodePtr cur,int * props_p[],const char * props_s[],size_t num)544 void Achievements_System::get_int_props(const xmlNodePtr cur, int *props_p[], const char *props_s[], size_t num)
545 {
546 	for (size_t i=0; i<num; ++i)
547 	{
548 		char *prop = (char*)xmlGetProp(cur, (xmlChar *)props_s[i]);
549 		if (prop == NULL)
550 			continue;
551 		int val=atoi(prop);
552 		if (val>=0)
553 			*props_p[i] = val;
554 		xmlFree(prop);
555 	}
556 }
557 
558 
559 //	A helper function for reading xml string properties
560 //
get_string_props(const xmlNodePtr cur,std::string * strings_p[],const char * strings[],size_t count)561 void Achievements_System::get_string_props(const xmlNodePtr cur, std::string * strings_p[], const char* strings[], size_t count)
562 {
563 	for (size_t i=0; i<count; ++i)
564 	{
565 		char *tmp = (char*)xmlGetProp(cur, (xmlChar *)strings[i]);
566 		char *parsed = 0;
567 		if (!tmp)
568 			continue;
569 		MY_XMLSTRCPY(&parsed, tmp);
570 		if (parsed)
571 		{
572 			*(strings_p[i]) = parsed;
573 			free(parsed);
574 		}
575 		xmlFree(tmp);
576 	}
577 }
578 
579 
580 //	First time we're asked to display some achievement details, prepare for the window size etc.
581 //
prepare_details(size_t index,bool force)582 void Achievements_System::prepare_details(size_t index, bool force)
583 {
584 	if ((index >= achievements.size()) || !achievements[index])
585 		return;
586 	achievements[index]->prepare(get_child_win_x(), get_current_scale() * DEFAULT_SMALL_RATIO,
587 		get_border(), force);
588 	if (achievements[index]->get_num_lines() > max_detail_lines)
589 		max_detail_lines = achievements[index]->get_num_lines();
590 }
591 
592 
593 //	Destory achievement windows, done when hidden and garbage collect next time we create one.
594 //
~Achievements_Window(void)595 Achievements_Window::~Achievements_Window(void)
596 {
597 	if (main_win_id >= 0)
598 		destroy_window(main_win_id);
599 	if (child_win_id >= 0)
600 		destroy_window(child_win_id);
601 }
602 
603 
604 //	When the mouse is over an achievement, display the details window.
605 //
achievements_child_display_handler(window_info * win)606 static int achievements_child_display_handler(window_info *win)
607 {
608 	if (!win || !win->data)
609 		return 0;
610 	size_t index = *reinterpret_cast<size_t *>(win->data);
611 	Achievements_System *as = Achievements_System::get_instance();
612 
613 	as->prepare_details(index);
614 
615 	const Achievement * achievement = as->achievement(index);
616 	if (achievement)
617 	{
618 		glColor3fv(gui_color);
619 		draw_string_small_zoomed_centered(win->len_x/2, as->get_border(),
620 			reinterpret_cast<const unsigned char *>(achievement->get_title().c_str()), 1, as->get_current_scale());
621 
622 		glColor3f(1.0f, 1.0f, 1.0f);
623 		draw_string_small_zoomed(as->get_border(), as->get_font_y(),
624 			achievement->get_text().c_str(), achievement->get_num_lines(), as->get_current_scale());
625 	}
626 	else
627 	{
628 		glColor3fv(gui_color);
629 		std::ostringstream buf;
630 		buf << "Undefined " << index;
631 		draw_string_small_zoomed_centered(win->len_x/2, as->get_border(),
632 			reinterpret_cast<const unsigned char *>(buf.str().c_str()), 1, as->get_current_scale());
633 	}
634 
635 	return 1;
636 }
637 
638 
639 //	When the mouse is over an achievement, create a window to display the details.
640 //
open_child(void)641 void Achievements_Window::open_child(void)
642 {
643 	window_info *parent = &windows_list.window[main_win_id];
644 	if (child_win_id < 0)
645 	{
646 		child_win_id = create_window("child", parent->window_id, 0, 0, 0, 0, 0,
647 			ELW_USE_BACKGROUND|ELW_USE_BORDER|ELW_SHOW|ELW_ALPHA_BORDER|ELW_SWITCHABLE_OPAQUE);
648 		set_window_custom_scale(child_win_id, MW_ACHIEVE);
649 		set_window_handler(child_win_id, ELW_HANDLER_DISPLAY, (int (*)())&achievements_child_display_handler );
650 	}
651 	else
652 		show_window(child_win_id);
653 
654 	Achievements_System *as = Achievements_System::get_instance();
655 	window_info *win = &windows_list.window[child_win_id];
656 	win->data = reinterpret_cast<void *>(&last_over);
657 	win->opaque = parent->opaque;
658 	resize_window(win->window_id, as->get_child_win_x(), as->get_child_win_y());
659 	move_window(win->window_id, parent->window_id, 0,
660 		parent->pos_x + (parent->len_x - as->get_child_win_x()) / 2, parent->pos_y  + parent->len_y + as->get_y_win_offset());
661 }
662 
663 
664 //	Display the main window.  All the players achievemnt icons and the controls.
665 //	Handle the mouse over events for the icons and the controls.
666 //
display_handler(window_info * win)667 int Achievements_Window::display_handler(window_info *win)
668 {
669 	Achievements_System *as = Achievements_System::get_instance();
670 
671 	int icon_per = (256 / as->get_size());
672 	int icon_per_texture = icon_per * icon_per;
673 	bool another_page = false;
674 
675 	glEnable(GL_TEXTURE_2D);
676 	glColor3f(1.0f,1.0f,1.0f);
677 
678 	for (size_t i=first; i<their_achievements.size(); ++i)
679 	{
680 		size_t shown_num = i-first;
681 
682 		if ((static_cast<int>(shown_num)/as->get_per_row()) >= physical_rows)
683 		{
684 			another_page = true;
685 			break;
686 		}
687 
688 		int texture = -1;
689 		const Achievement * achievement = as->achievement(their_achievements[i]);
690 		if (achievement)
691 			texture = as->texture(achievement->get_id() / icon_per_texture);
692 
693 		if (texture >= 0)
694 		{
695 			int cur_item = achievement->get_id() % icon_per_texture;
696 
697 			float u_start = 1.0f/static_cast<float>(icon_per) * (cur_item % icon_per);
698 			float u_end = u_start + static_cast<float>(as->get_size())/256;
699 			float v_start = 1.0f/static_cast<float>(icon_per) * (cur_item / icon_per);
700 			float v_end = v_start + static_cast<float>(as->get_size()) / 256;
701 			int start_x = as->get_border() + as->get_display() * (shown_num % as->get_per_row());
702 			int start_y = as->get_border() + as->get_display() * (shown_num / as->get_per_row());
703 
704 			bind_texture(texture);
705 			glEnable(GL_ALPHA_TEST);
706 			glAlphaFunc(GL_GREATER, 0.05f);
707 			glBegin(GL_QUADS);
708 			draw_2d_thing( u_start, v_start, u_end, v_end,
709 				start_x, start_y, start_x+as->get_display(), start_y+as->get_display() );
710 			glEnd();
711 			glDisable(GL_ALPHA_TEST);
712 		}
713 		else
714 		{
715 			int pos_x = as->get_border() + (shown_num % as->get_per_row()) * as->get_display()
716 				+ as->get_display()/2;
717 			int pos_y = as->get_border() + (shown_num / as->get_per_row()) * as->get_display()
718 				+ (as->get_display() - win->default_font_len_y) / 2;
719 			draw_string_zoomed_centered(pos_x, pos_y, (const unsigned char *)"?", 1, win->current_scale);
720 		}
721 	}
722 
723 	size_t now_over = Achievement::npos;
724 
725 	if ((cm_window_shown() == CM_INIT_VALUE) && (win_mouse_x > as->get_border()) && (win_mouse_y > as->get_border()))
726 	{
727 		int row = (win_mouse_y - as->get_border()) / as->get_display();
728 		int col = (win_mouse_x - as->get_border()) / as->get_display();
729 		if ((row >= 0) && (row < physical_rows) && (col >= 0) && (col < as->get_per_row()))
730 		{
731 			size_t current_index = first + row * as->get_per_row() + col;
732 			if (current_index < their_achievements.size())
733 				now_over = their_achievements[current_index];
734 		}
735 	}
736 
737 	if (now_over != last_over)
738 	{
739 		if (last_over != Achievement::npos)
740 			hide_window(child_win_id);
741 		last_over = now_over;
742 		if (last_over != Achievement::npos)
743 			open_child();
744 	}
745 
746 	int prev_start = as->get_border();
747 	int prev_end = prev_start + as->get_font_width(as->get_prev());
748 	int next_start = prev_end + 2 * as->get_border();
749 	int next_end = next_start + as->get_font_width(as->get_next());
750 	int close_start = win->len_x
751 		- (as->get_border() + as->get_font_width(as->get_close()));
752 	int close_end = win->len_x - as->get_border();
753 
754 	bool over_controls = (win_mouse_y > (win->len_y - (as->get_font_y() + as->get_border())));
755 	bool over_close = (over_controls && (win_mouse_x > close_start) && (win_mouse_x < close_end));
756 	bool over_prev = (over_controls && (win_mouse_x > prev_start) && (win_mouse_x < prev_end));
757 	bool over_next = (over_controls && (win_mouse_x > next_start) && (win_mouse_x < next_end));
758 
759 	float active_colour[3] = { 1.0f, 1.0f, 1.0f };
760 	float inactive_colour[3] =  { 0.5f, 0.5f, 0.5f };
761 	float mouse_over_colour[3] = { 1.0f, 0.5f, 0.0f };
762 
763 	glColor3fv((first) ?((over_prev) ?mouse_over_colour :active_colour) :inactive_colour);
764 	draw_string_small_zoomed(prev_start, win->len_y - (as->get_font_y() + as->get_border()),
765 		reinterpret_cast<const unsigned char *>(as->get_prev().c_str()), 1, as->get_current_scale());
766 
767 	glColor3fv((another_page) ?((over_next) ?mouse_over_colour :active_colour) :inactive_colour);
768 	draw_string_small_zoomed(next_start, win->len_y - (as->get_font_y() + as->get_border()),
769 		reinterpret_cast<const unsigned char *>(as->get_next().c_str()), 1, as->get_current_scale());
770 
771 	glColor3fv((over_close) ?mouse_over_colour :active_colour);
772 	draw_string_small_zoomed(close_start, win->len_y - (as->get_font_y() + as->get_border()),
773 		reinterpret_cast<const unsigned char *>(as->get_close().c_str()), 1, as->get_current_scale());
774 
775 	if (over_close && ctrl_clicked)
776 		as->hide_all();
777 	if (over_close && clicked)
778 		hide_window(main_win_id);
779 	else if (over_prev && first && clicked)
780 		first -= physical_rows * as->get_per_row();
781 	else if (over_next && another_page && clicked)
782 		first += physical_rows * as->get_per_row();
783 
784 	if (clicked && (over_prev || over_next))
785 		do_click_sound();
786 	if ((ctrl_clicked || clicked) && over_close)
787 		do_window_close_sound();
788 
789 	if (over_controls && show_help_text)
790 	{
791 		if (over_close)
792 			show_help(as->get_close_help(), 0, win->len_y + as->get_y_win_offset(), win->current_scale);
793 		else if (over_prev)
794 			show_help((first)?as->get_prev_help() :as->get_no_prev_help(), 0, win->len_y + as->get_y_win_offset(), win->current_scale);
795 		else if (over_next)
796 			show_help((another_page)?as->get_next_help() :as->get_no_next_help(), 0, win->len_y + as->get_y_win_offset(), win->current_scale);
797 	}
798 
799 	win_mouse_x = win_mouse_y = -1;
800 	ctrl_clicked = clicked = false;
801 
802 	return 1;
803 }
804 
805 
806 //	A common display handler callback that used my all the windows and calls the specific handler.
807 //
achievements_display_handler(window_info * win)808 static int achievements_display_handler(window_info *win)
809 {
810 	if (!win || !win->data)
811 		return 0;
812 	Achievements_Window *object = reinterpret_cast<Achievements_Window *>(win->data);
813 	return object->display_handler(win);
814 }
815 
816 
817 //	A common display handler callback for mouse over activity.
818 //
achievements_mouseover_handler(window_info * win,int mx,int my)819 static int achievements_mouseover_handler(window_info *win, int mx, int my)
820 {
821 	if (!win || !win->data)
822 		return 0;
823 	if (!is_window_coord_top(win->window_id, mouse_x, mouse_y))
824 		return 0;
825 	Achievements_Window *object = reinterpret_cast<Achievements_Window *>(win->data);
826 	object->set_mouse_over(mx, my);
827 	return 0;
828 }
829 
830 
831 //	A common display handler callback for keypress activity.
832 //
achievements_keypress_handler(window_info * win,int mx,int my,SDL_Keycode key_code,Uint32 key_unicode,Uint16 key_mod)833 int achievements_keypress_handler(window_info *win, int mx, int my, SDL_Keycode key_code, Uint32 key_unicode, Uint16 key_mod)
834 {
835 	if (!win)
836 		return 0;
837 	if (key_code == SDLK_ESCAPE) // close window if Escape pressed
838 	{
839 		do_window_close_sound();
840 		if (key_mod & KMOD_CTRL)
841 			Achievements_System::get_instance()->hide_all();
842 		else
843 			hide_window(win->window_id);
844 		return 1;
845 	}
846 	return 0;
847 }
848 
849 
850 //	A common display handler callback for mouse click activity.
851 //
achievements_click_handler(window_info * win,int mx,int my,Uint32 flags)852 static int achievements_click_handler(window_info *win, int mx, int my, Uint32 flags)
853 {
854 	if (!win || !win->data)
855 		return 0;
856 	if (my < 0)
857 		return 0;
858 	Achievements_Window *object = reinterpret_cast<Achievements_Window *>(win->data);
859 	if ((flags & ELW_LEFT_MOUSE) && (flags & KMOD_CTRL))
860 		object->window_ctrl_clicked();
861 	else if (flags & ELW_LEFT_MOUSE)
862 		object->window_clicked();
863 	return 1;
864 }
865 
866 
867 //	Called when UI scaling changes
ui_scale_handler(window_info * win)868 void Achievements_Window::ui_scale_handler(window_info *win)
869 {
870 	Achievements_System *as = Achievements_System::get_instance();
871 	as->set_current_scale(win->current_scale);
872 	resize_window(win->window_id, as->main_win_x(), physical_rows * as->get_display() + as->get_font_y() + 2 * as->get_border());
873 }
achievements_ui_scale_handler(window_info * win)874 static int achievements_ui_scale_handler(window_info *win)
875 {
876 	Achievements_Window *object = reinterpret_cast<Achievements_Window *>(win->data);
877 	object->ui_scale_handler(win);
878 	return 1;
879 }
880 
font_change_handler(window_info * win,FontManager::Category cat)881 int Achievements_Window::font_change_handler(window_info *win, FontManager::Category cat)
882 {
883 	if (cat != FontManager::Category::UI_FONT)
884 		return 0;
885 
886 	Achievements_System *as = Achievements_System::get_instance();
887 	resize_window(win->window_id, as->main_win_x(), physical_rows * as->get_display() + as->get_font_y() + 2 * as->get_border());
888 	as->rewrap_achievement_texts();
889 
890 	return 1;
891 }
change_achievements_font_handler(window_info * win,font_cat cat)892 static int change_achievements_font_handler(window_info *win, font_cat cat)
893 {
894 	Achievements_Window *object = reinterpret_cast<Achievements_Window*>(win->data);
895 	return object->font_change_handler(win, cat);
896 }
897 
898 //	When creating a new window, strip any guild from the playe name.
899 //
set_name(const std::string & name)900 void Achievements_Window::set_name(const std::string & name)
901 {
902 	if (name.empty())
903 		their_name = "<?>";
904 	else
905 	{
906 		their_name = name;
907 		size_t space_pos = their_name.find(" ");
908 		if (space_pos != std::string::npos)
909 			their_name = their_name.substr(0, space_pos);
910 	}
911 }
912 
913 
914 //	Process the achievement bits and save ids for later display.
915 //
set_achievements(const std::vector<Uint32> & data)916 void Achievements_Window::set_achievements(const std::vector<Uint32> & data)
917 {
918 	// bit position w1(lsb) ....w5(msb) map to achievement id 0...159
919 	for (size_t i=0; i<data.size(); ++i)
920 	{
921 		Uint32 word = data[i];
922 		for (size_t j=0; j<sizeof(Uint32)*8; ++j)
923 		{
924 			if (word & 1)
925 				their_achievements.push_back(i*sizeof(Uint32)*8+j);
926 			word >>= 1;
927 		}
928 	}
929 }
930 
931 
932 //	Create a new achievement window.
933 //
open(int win_pos_x,int win_pos_y)934 void Achievements_Window::open(int win_pos_x, int win_pos_y)
935 {
936 	if (main_win_id >= 0)
937 	{
938 		show_window(main_win_id);
939 		return;
940 	}
941 
942 	Achievements_System *as = Achievements_System::get_instance();
943 
944 	logical_rows = (their_achievements.size() + (as->get_per_row() - 1)) / as->get_per_row();
945 	physical_rows = (logical_rows > as->get_max_rows()) ?as->get_max_rows() :logical_rows;
946 
947 	main_win_id = create_window(their_name.c_str(), -1, 0, win_pos_x, win_pos_y, 0, 0,
948 		ELW_USE_UISCALE|ELW_TITLE_BAR|ELW_DRAGGABLE|ELW_USE_BACKGROUND|ELW_USE_BORDER|ELW_SHOW|ELW_TITLE_NAME|ELW_ALPHA_BORDER|ELW_SWITCHABLE_OPAQUE);
949 	set_window_custom_scale(main_win_id, MW_ACHIEVE);
950 	set_window_handler(main_win_id, ELW_HANDLER_DISPLAY, (int (*)())&achievements_display_handler );
951 	set_window_handler(main_win_id, ELW_HANDLER_CLICK, (int (*)())&achievements_click_handler );
952 	set_window_handler(main_win_id, ELW_HANDLER_MOUSEOVER, (int (*)())&achievements_mouseover_handler );
953 	set_window_handler(main_win_id, ELW_HANDLER_KEYPRESS, (int (*)())&achievements_keypress_handler );
954 	set_window_handler(main_win_id, ELW_HANDLER_UI_SCALE, (int (*)())&achievements_ui_scale_handler );
955 	set_window_handler(main_win_id, ELW_HANDLER_FONT_CHANGE, (int (*)())&change_achievements_font_handler);
956 
957 	window_info *win = &windows_list.window[main_win_id];
958 	win->data = reinterpret_cast<void *>(this);
959 	ui_scale_handler(win);
960 }
961 
962 int achievements_ctrl_click = 0;
963 
964 //	We have a new player name from the server.
965 //
achievements_player_name(const char * name,int len)966 extern "C" void achievements_player_name(const char *name, int len)
967 {
968 	Achievements_System::get_instance()->new_name(name, len);
969 }
970 
971 
972 //	We have new achievement data from the server.
973 //
achievements_data(Uint32 * data,size_t word_count)974 extern "C" void achievements_data(Uint32 *data, size_t word_count)
975 {
976 	Achievements_System::get_instance()->new_data(data, word_count);
977 }
978 
979 
980 //	We may be about to get some achievement data, remember the mouse position.
981 //
achievements_requested(int mouse_pos_x,int mouse_pos_y,int control_used)982 extern "C" void achievements_requested(int mouse_pos_x, int mouse_pos_y, int control_used)
983 {
984 	Achievements_System::get_instance()->requested(mouse_pos_x, mouse_pos_y, control_used);
985 }
986 
987 
988 //	Close any open windows, they will get destroyed later.
989 //	If no windows are open return 0, otherwise return 1.
990 //
achievements_close_all(void)991 extern "C" int achievements_close_all(void)
992 {
993 	return (Achievements_System::get_instance()->hide_all()) ?1: 0;
994 }
995