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