1 #include <algorithm>
2 #include <string>
3 #include <unordered_map>
4 #include <vector>
5 #include "asc.h"
6 #include "books.h"
7 #include "elconfig.h"
8 #include "elloggingwrapper.h"
9 #include "exceptions/extendedexception.hpp"
10 #include "font.h"
11 #include "knowledge.h"
12 #include "multiplayer.h"
13 #include "new_character.h"
14 #include "textures.h"
15 #include <cmath> // for std::round()
16
17 namespace eternal_lands
18 {
19
find_line(const ustring & text,size_t n,size_t pos=0)20 size_t find_line(const ustring& text, size_t n, size_t pos=0)
21 {
22 static const ustring nl(reinterpret_cast<const unsigned char*>("\r\n"));
23
24 for ( ; n > 0; --n)
25 {
26 pos = text.find_first_of(nl, pos);
27 if (pos == ustring::npos)
28 break;
29 ++pos;
30 }
31
32 return pos;
33 }
34
BookImage(const std::string & file_name,int x,int y,int width,int height,float u_start,float v_start,float u_end,float v_end)35 BookImage::BookImage(const std::string& file_name, int x, int y, int width, int height,
36 float u_start, float v_start, float u_end, float v_end):
37 _file_name(file_name), _x(x), _y(y), _width(width), _height(height),
38 _texture(load_texture_cached(_file_name.c_str(), tt_image)),
39 _u_start(u_start), _v_start(v_start), _u_end(u_end), _v_end(v_end) {}
40
display() const41 void BookImage::display() const
42 {
43 glColor4f(1.0f, 1.0f, 1.0f, 0.5f);
44 bind_texture(_texture);
45 glEnable(GL_ALPHA_TEST);
46 glAlphaFunc(GL_GREATER, 0.05f);
47 glBegin(GL_QUADS);
48 glTexCoord2f(_u_start, _v_end); glVertex2i(_x, _y+_height);
49 glTexCoord2f(_u_end, _v_end); glVertex2i(_x + _width, _y + _height);
50 glTexCoord2f(_u_end, _v_start); glVertex2i(_x + _width, _y);
51 glTexCoord2f(_u_start, _v_start); glVertex2i(_x, _y);
52 glEnd();
53 glDisable(GL_ALPHA_TEST);
54 #ifdef OPENGL_TRACE
55 CHECK_GL_ERRORS();
56 #endif //OPENGL_TRACE
57 }
58
59
display() const60 void TextBlock::display() const
61 {
62 TextDrawOptions options = TextDrawOptions().set_zoom(_zoom)
63 .set_line_spacing(_line_spacing);
64 switch (_type)
65 {
66 case TITLE:
67 case AUTHOR:
68 options.set_alignment(TextDrawOptions::Alignment::CENTER);
69 break;
70 case PAGE_NR:
71 options.set_foreground(0.385f, 0.285f, 0.19f);
72 break;
73 case CAPTION:
74 options.set_zoom(_zoom * DEFAULT_SMALL_RATIO);
75 // fallthrough
76 default:
77 options.set_foreground(0.34f, 0.25f, 0.16f);
78 break;
79 }
80 FontManager::get_instance().draw(BOOK_FONT, _text.c_str(), _text.length(), _x, _y, options);
81 }
82
83
occupy_range(int y_begin,int y_end)84 void Page::occupy_range(int y_begin, int y_end)
85 {
86 if (y_begin >= y_end)
87 return;
88
89 std::vector<std::pair<int, int>>::iterator it = std::upper_bound(
90 _free_ranges.begin(), _free_ranges.end(),
91 y_begin, [](int y, const std::pair<int,int>& range) { return y < range.second; });
92 while (it != _free_ranges.end())
93 {
94 if (it->first >= y_end)
95 break;
96 if (it->first < y_begin)
97 {
98 if (it->second <= y_end)
99 {
100 it->second = y_begin;
101 ++it;
102 }
103 else
104 {
105 it = _free_ranges.insert(it, std::make_pair(it->first, y_begin));
106 ++it;
107 it->first = y_end;
108 ++it;
109 }
110 }
111 else
112 {
113 if (it->second <= y_end)
114 {
115 it = _free_ranges.erase(it);
116 }
117 else
118 {
119 it->first = y_end;
120 ++it;
121 }
122 }
123 }
124 }
125
126
add_page(int page_width,int page_height,float zoom)127 Page* Book::add_page(int page_width, int page_height, float zoom)
128 {
129 _pages.push_back(Page(page_height));
130 Page *page = &_pages.back();
131
132 std::ostringstream os;
133 os << _pages.size();
134 std::string tmp = os.str();
135 ustring page_nr(tmp.begin(), tmp.end());
136 page->add_text_block(TextBlock(page_nr, PAGE_NR, page_width / 2, page_height + 2,
137 page_width, line_height(zoom), zoom), 0, 0);
138
139 return page;
140 }
141
layout_text(ContentType content_type,const ustring & text,int page_width,int page_height,float zoom)142 void Book::layout_text(ContentType content_type, const ustring& text,
143 int page_width, int page_height, float zoom)
144 {
145 if (text.empty())
146 return;
147
148 int line_h = line_height(zoom, line_spacing);
149 if (line_h > page_height)
150 // Line is higher than a full page. Give up.
151 return;
152
153 ustring lines;
154 int nr_lines;
155 int x = 0;
156 TextDrawOptions options = TextDrawOptions().set_max_width(page_width).set_zoom(zoom);
157 std::tie(lines, nr_lines) = FontManager::get_instance().reset_soft_breaks(
158 BOOK_FONT, text, options);
159 if (content_type == AUTHOR)
160 {
161 lines = to_color_char(c_orange3) + lines + static_cast<unsigned char>('\n');
162 ++nr_lines;
163 x = page_width / 2;
164 }
165 else if (content_type == TITLE)
166 {
167 lines = to_color_char(c_orange4) + lines + static_cast<unsigned char>('\n');
168 ++nr_lines;
169 x = page_width / 2;
170 }
171
172 Page* page = text_page(page_width, page_height, zoom);
173 size_t offset = 0;
174 while (offset < lines.length())
175 {
176 int y_begin, y_end;
177 std::tie(y_begin, y_end) = page->find_free_range_aligned(line_h);
178 while (y_begin < 0)
179 {
180 page = next_text_page(page_width, page_height, zoom);
181 std::tie(y_begin, y_end) = page->find_free_range_aligned(line_h);
182 }
183
184 int nr_lines_this_page = (y_end - y_begin) / line_h;
185 size_t end;
186 if (nr_lines <= nr_lines_this_page)
187 {
188 nr_lines_this_page = nr_lines;
189 end = lines.length() + 1;
190 }
191 else
192 {
193 end = find_line(lines, nr_lines_this_page, offset);
194 if (end == ustring::npos)
195 end = lines.length() + 1;
196 }
197
198 int height = nr_lines_this_page * line_h;
199 TextBlock block(lines.substr(offset, end - 1 - offset), content_type,
200 x, y_begin, page_width, height, zoom, line_spacing);
201 page->add_text_block(std::move(block), y_begin, y_begin + height);
202
203 offset = end;
204 nr_lines -= nr_lines_this_page;
205 }
206 }
207
caption_text(const BookImage & image,const ustring & text,int page_width,int page_height,float zoom) const208 std::vector<std::pair<TextBlock, bool>> Book::caption_text(const BookImage& image,
209 const ustring& text, int page_width, int page_height, float zoom) const
210 {
211 std::vector<std::pair<TextBlock, bool>> res;
212 if (text.empty())
213 return res;
214
215 bool do_left, do_right;
216 if (image.y() + image.height() > page_height
217 || image.x() + image.width() > page_width)
218 {
219 // Image is too big for the page. Don't show it, but do show the caption.
220 // There is need to flow around the image though, so:
221 do_left = do_right = false;
222 }
223 else
224 {
225 // Only show text beside the image if there is at least one third of
226 // the page width free.
227 do_left = image.x() > page_width / 3;
228 do_right = image.x() + image.width() < 2 * page_width / 3;
229 }
230
231 float caption_zoom = zoom * DEFAULT_SMALL_RATIO;
232 int caption_line_height = line_height(caption_zoom);
233 TextDrawOptions options = TextDrawOptions().set_zoom(caption_zoom);
234 ustring lines;
235 int nr_lines;
236 int nr_lines_side = (image.height() + caption_line_height - 1) / caption_line_height;
237 if (image.y() + nr_lines_side * caption_line_height > page_height)
238 --nr_lines_side;
239 if (do_left && do_right)
240 {
241 ustring left_str, right_str;
242 int x_left = 0;
243 int x_right = image.x() + image.width();
244 int left_width = image.x() - image_margin;
245 int right_width = page_width - x_right;
246
247 lines = text;
248 for (int i = 0; i < nr_lines_side; ++i)
249 {
250 options.set_max_width(left_width);
251 std::tie(lines, nr_lines) = FontManager::get_instance().reset_soft_breaks(
252 BOOK_FONT, lines, options);
253 size_t off = find_line(lines, 1);
254 left_str.append(lines.substr(0, off));
255 lines = lines.substr(off);
256 if (lines.empty())
257 break;
258
259 options.set_max_width(right_width);
260 std::tie(lines, nr_lines) = FontManager::get_instance().reset_soft_breaks(
261 BOOK_FONT, lines, options);
262 off = find_line(lines, 1);
263 right_str.append(lines.substr(0, off));
264 lines = lines.substr(off);
265 if (lines.empty())
266 break;
267 }
268
269 TextBlock left_block(left_str, CAPTION, x_left, image.y(), left_width,
270 nr_lines_side * caption_line_height, zoom);
271 res.push_back(std::make_pair(left_block, false));
272 TextBlock right_block(right_str, CAPTION, x_right, image.y(), right_width,
273 nr_lines_side * caption_line_height, zoom);
274 res.push_back(std::make_pair(right_block, false));
275 }
276 else if (do_left)
277 {
278 int x = 0;
279 options.set_max_width(image.x() - image_margin);
280 std::tie(lines, nr_lines) = FontManager::get_instance().reset_soft_breaks(
281 BOOK_FONT, text, options);
282 if (nr_lines <= nr_lines_side)
283 {
284 TextBlock block(lines, CAPTION, x, image.y(), options.max_width(),
285 nr_lines_side * caption_line_height, zoom);
286 res.push_back(std::make_pair(block, false));
287 lines.clear();
288 }
289 else
290 {
291 size_t off = find_line(lines, nr_lines_side);
292 TextBlock block(lines.substr(0, off-1), CAPTION, x, image.y(), options.max_width(),
293 nr_lines * caption_line_height, zoom);
294 res.push_back(std::make_pair(block, false));
295 lines = lines.substr(off);
296 }
297 }
298 else if (do_right)
299 {
300 int x = image.x() + image.width() + image_margin;
301 options.set_max_width(page_width - x);
302 std::tie(lines, nr_lines) = FontManager::get_instance().reset_soft_breaks(
303 BOOK_FONT, text, options);
304 if (nr_lines <= nr_lines_side)
305 {
306 TextBlock block(lines, CAPTION, x, image.y(), options.max_width(),
307 nr_lines * caption_line_height, zoom);
308 res.push_back(std::make_pair(block, false));
309 lines.clear();
310 }
311 else
312 {
313 size_t off = find_line(lines, nr_lines_side);
314 TextBlock block(lines.substr(0, off-1), CAPTION, x, image.y(), options.max_width(),
315 nr_lines_side * caption_line_height, zoom);
316 res.push_back(std::make_pair(block, false));
317 lines = lines.substr(off);
318 }
319 }
320 else
321 {
322 lines = text;
323 }
324
325 int y = image.y() + nr_lines_side * caption_line_height;
326 options.set_max_width(page_width);
327 bool new_page = false;
328 while (!lines.empty())
329 {
330 int max_nr_lines = (page_height - y) / caption_line_height;
331 std::tie(lines, nr_lines) = FontManager::get_instance().reset_soft_breaks(
332 BOOK_FONT, lines, options);
333 if (nr_lines <= max_nr_lines)
334 {
335 TextBlock block(lines, CAPTION, 0, y, page_width,
336 nr_lines * caption_line_height, zoom);
337 res.push_back(std::make_pair(block, new_page));
338 break;
339 }
340 else
341 {
342 size_t off = find_line(lines, nr_lines_side);
343 TextBlock block(lines.substr(0, off-1), CAPTION, 0, y, page_width,
344 nr_lines_side * caption_line_height, zoom);
345 res.push_back(std::make_pair(block, new_page));
346
347 lines = lines.substr(off);
348 y = 0;
349 new_page = true;
350 }
351 }
352
353 return res;
354 }
355
layout_image(const BookImage & image,const ustring & caption,int page_width,int page_height,float zoom)356 void Book::layout_image(const BookImage &image, const ustring& caption,
357 int page_width, int page_height, float zoom)
358 {
359 BookImage scaled_img = image.scaled(zoom);
360 bool image_fits = (scaled_img.y() + scaled_img.height() <= page_height
361 && scaled_img.x() + scaled_img.width() <= page_width);
362 if (!image_fits && caption.empty())
363 return;
364
365 int margin = std::round(image_margin * zoom);
366 int y_begin = std::max(scaled_img.y() - margin, 0);
367 int y_end = image_fits ? scaled_img.y() + scaled_img.height() : scaled_img.y();
368 std::vector<std::pair<TextBlock, bool>> blocks
369 = caption_text(scaled_img, caption, page_width, page_height, zoom);
370 for (const auto& tup: blocks)
371 {
372 if (!tup.second)
373 // Text block goes on same page as image
374 y_end = std::max(y_end, tup.first.y() + tup.first.height());
375 }
376 y_end += margin;
377
378 Page *page = last_page(page_width, page_height, zoom);
379 if (!page->has_free_range(y_begin, y_end))
380 page = add_page(page_width, page_height, zoom);
381
382 page->add_image(std::move(scaled_img), y_begin, y_end);
383 for (auto &tup: blocks)
384 {
385 if (tup.second)
386 page = add_page(page_width, page_height, zoom);
387 TextBlock &block = tup.first;
388 page->add_text_block(std::move(block), block.y() - margin,
389 block.y() + block.height() + margin);
390 }
391 }
392
layout(int page_width,int page_height,float zoom)393 void Book::layout(int page_width, int page_height, float zoom)
394 {
395 _pages.clear();
396 _active_text_page = 0;
397
398 for (const BookItem& item: _items)
399 {
400 if (item.type() == IMAGE)
401 layout_image(item.image(), item.text(), page_width, page_height, zoom);
402 else
403 layout_text(item.type(), item.text(), page_width, page_height, zoom);
404 }
405
406 if (active_page_nr() > nr_pages())
407 turn_to_page(nr_pages() - 1);
408 _laid_out = true;
409 }
410
read_book(const std::string & file_name,PaperType paper_type,int id)411 Book Book::read_book(const std::string& file_name, PaperType paper_type, int id)
412 {
413 std::string path = std::string("languages") + '/' + lang + '/' + file_name;
414 xmlDoc *doc = xmlReadFile(path.c_str(), 0, 0);
415 if (!doc)
416 {
417 // Fall back on English if the book is not available in the user's language
418 path = std::string("languages") + "/en/" + file_name;
419 doc = xmlReadFile(path.c_str(), 0, 0);
420 if (!doc)
421 {
422 char err[200];
423 safe_snprintf(err, sizeof(err), book_open_err_str, path.c_str());
424 LOG_TO_CONSOLE(c_red1, err);
425 EXTENDED_EXCEPTION(ExtendedException::ec_file_not_found, err);
426 }
427 }
428
429 xmlNode *root = xmlDocGetRootElement(doc);
430 if (!root)
431 {
432 xmlFreeDoc(doc);
433 EXTENDED_EXCEPTION(ExtendedException::ec_invalid_parameter,
434 "Error while parsing: " << path);
435 }
436 if (xmlStrcasecmp(root->name, reinterpret_cast<const xmlChar*>("book")))
437 {
438 xmlFreeDoc(doc);
439 EXTENDED_EXCEPTION(ExtendedException::ec_invalid_parameter,
440 "Root element in " << path << " is not <book>");
441 }
442
443 xmlChar* title = xmlGetProp(root, reinterpret_cast<const xmlChar*>("title"));
444 if (!title)
445 {
446 xmlFreeDoc(doc);
447 EXTENDED_EXCEPTION(ExtendedException::ec_invalid_parameter,
448 "Root element in " << path << " does not contain a title=\"<short title>\" property.");
449 }
450
451 Book book(paper_type, reinterpret_cast<const char*>(title), id);
452 book.add_xml_content(root->children);
453
454 xmlFree(title);
455 xmlFreeDoc(doc);
456
457 return book;
458 }
459
add_xml_image(const xmlNode * node)460 void Book::add_xml_image(const xmlNode *node)
461 {
462 char *file_name = reinterpret_cast<char*>(xmlGetProp(node,
463 reinterpret_cast<const xmlChar*>("src")));
464 if (!file_name)
465 return;
466
467 int x = xmlGetInt(node, "x");
468 int y = xmlGetInt(node, "y");
469 int width = xmlGetInt(node, "w");
470 int height = xmlGetInt(node, "h");
471
472 float u_start = xmlGetFloat(node, "u_start", 0.0);
473 float u_end = xmlGetFloat(node, "u_end", 1.0);
474 float v_start = xmlGetFloat(node, "v_start", 1.0);
475 float v_end = xmlGetFloat(node, "v_end", 0.0);
476
477 BookImage image(file_name, x, y, width, height, u_start, v_start, u_end, v_end);
478
479 xmlFree(file_name);
480
481 ustring text;
482 if (node->children && node->children->content)
483 {
484 char *text_ptr = 0;
485 MY_XMLSTRCPY(&text_ptr, reinterpret_cast<const char*>(node->children->content));
486 text.assign(reinterpret_cast<const unsigned char*>(text_ptr));
487 free(text_ptr);
488 }
489
490 add_item(std::move(image), text);
491 }
492
add_xml_text(ContentType type,const xmlNode * node)493 void Book::add_xml_text(ContentType type, const xmlNode *node)
494 {
495 if (node->children && node->children->content)
496 {
497 char* text = 0;
498 if (MY_XMLSTRCPY(&text, reinterpret_cast<const char*>(node->children->content)) != -1)
499 {
500 add_item(type, reinterpret_cast<const unsigned char*>(text));
501 }
502 else
503 {
504 #ifndef OSX
505 LOG_ERROR("An error occured when parsing the content of the <%s>-tag on line %d - Check it for letters that cannot be translated into iso8859-1\n",
506 node->name, node->line);
507 #else
508 LOG_ERROR("An error occured when parsing the content of the <%s>-tag - Check it for letters that cannot be translated into iso8859-1\n",
509 node->name);
510 #endif
511 }
512 free(text);
513 }
514 }
515
add_xml_page(const xmlNode * node)516 void Book::add_xml_page(const xmlNode *node)
517 {
518 for ( ; node; node = node->next)
519 {
520 if (node->type != XML_ELEMENT_NODE)
521 continue;
522
523 if (!xmlStrcasecmp(node->name, reinterpret_cast<const xmlChar*>("title")))
524 add_xml_text(TITLE, node);
525 else if (!xmlStrcasecmp(node->name, reinterpret_cast<const xmlChar*>("author")))
526 add_xml_text(AUTHOR, node);
527 else if (!xmlStrcasecmp(node->name, reinterpret_cast<const xmlChar*>("text")))
528 add_xml_text(TEXT, node);
529 else if (!xmlStrcasecmp(node->name, reinterpret_cast<const xmlChar*>("image")))
530 add_xml_image(node);
531 }
532 }
533
add_xml_content(const xmlNode * node)534 void Book::add_xml_content(const xmlNode *node)
535 {
536 for (const xmlNode *cur = node; cur; cur = cur->next)
537 {
538 if (cur->type == XML_ELEMENT_NODE
539 && !xmlStrcasecmp(cur->name, reinterpret_cast<const xmlChar*>("page")))
540 {
541 add_xml_page(cur->children);
542 }
543 }
544 }
545
add_server_image(const unsigned char * data,size_t len)546 void Book::add_server_image(const unsigned char* data, size_t len)
547 {
548 if (len < 2)
549 {
550 EXTENDED_EXCEPTION(ExtendedException::ec_invalid_parameter,
551 "Incomplete image file name length in server book");
552 }
553
554 size_t fname_len = size_t(data[0]) | size_t(data[1]) << 8;
555 if (len < 2 + fname_len)
556 {
557 EXTENDED_EXCEPTION(ExtendedException::ec_invalid_parameter,
558 "Incomplete image file name data in server book");
559 }
560 std::string file_name(data + 2, data + 2 + fname_len);
561
562 size_t off = 2 + fname_len;
563 if (len < off + 2)
564 {
565 EXTENDED_EXCEPTION(ExtendedException::ec_invalid_parameter,
566 "Incomplete caption length in server book");
567 }
568
569 size_t caption_len = size_t(data[off]) | size_t(data[off+1]) << 8;
570 if (len < off + 2 + caption_len)
571 {
572 EXTENDED_EXCEPTION(ExtendedException::ec_invalid_parameter,
573 "Incomplete image caption in server book");
574 }
575 ustring caption(data + off + 2, caption_len);
576
577 off += 2 + caption_len;
578 if (len < off + 12)
579 {
580 EXTENDED_EXCEPTION(ExtendedException::ec_invalid_parameter,
581 "Incomplete image coordinates in server book");
582 }
583
584 int x = int(data[off]) | int(data[off+1]) << 8;
585 int y = int(data[off+2]) | int(data[off+3]) << 8;
586 int width = int(data[off+4]) | int(data[off+5]) << 8;
587 int height = int(data[off+6]) | int(data[off+7]) << 8;
588
589 float u_start = data[off + 8];
590 float u_end = data[off + 9];
591 float v_start = data[off + 10];
592 float v_end = data[off + 11];
593
594 BookImage image(file_name, x, y, width, height, u_start, v_start, u_end, v_end);
595 add_item(std::move(image), caption);
596 }
597
add_server_content(const unsigned char * data,size_t len)598 void Book::add_server_content(const unsigned char* data, size_t len)
599 {
600 size_t idx = 0;
601 while (idx < len)
602 {
603 size_t item_len = size_t(data[idx+1]) | size_t(data[idx+2]) << 8;
604 if (idx + item_len + 3 > len)
605 {
606 EXTENDED_EXCEPTION(ExtendedException::ec_invalid_parameter,
607 "Incomplete server book item");
608 }
609
610 switch (data[idx])
611 {
612 case 0:
613 add_item(TITLE, ustring(data + idx + 3, item_len));
614 break;
615 case 1:
616 add_item(AUTHOR, ustring(data + idx + 3, item_len));
617 break;
618 case 2:
619 add_item(TEXT, ustring(data + idx + 3, item_len));
620 break;
621 case 3:
622 add_server_image(data + idx + 3, item_len);
623 break;
624 case 6:
625 // New page. Ignored.
626 break;
627 default:
628 LOG_ERROR("Unknown book item type %d", int(data[idx]));
629 }
630 idx += item_len + 3;
631 }
632
633 renew_layout();
634 }
635
display(float zoom) const636 void Book::display(float zoom) const
637 {
638 if (_active_page < 0 || size_t(_active_page) >= _pages.size())
639 return;
640
641 if (_paper_type == BOOK)
642 {
643 _pages[_active_page].display();
644 if (size_t(_active_page + 1) < _pages.size())
645 {
646 glPushMatrix();
647 glTranslatef(std::round(zoom * BookWindow::x_half_book), 0.0, 0.0);
648 _pages[_active_page+1].display();
649 glPopMatrix();
650 }
651 }
652 else
653 {
654 _pages[_active_page].display();
655 }
656 #ifdef OPENGL_TRACE
657 CHECK_GL_ERRORS();
658 #endif //OPENGL_TRACE
659 }
660
turn_to_page(int nr)661 void Book::turn_to_page(int nr)
662 {
663 if (_paper_type == BOOK)
664 nr = 2 * (nr / 2);
665 if (nr >= 0 && nr < nr_pages())
666 _active_page = nr;
667
668 if (last_page_visible()
669 && server_book_incomplete()
670 && !_waiting_on_server_page)
671 {
672 // Last page of server book is visible, request new page
673 _waiting_on_server_page = true;
674 BookCollection::request_server_page(_id, _nr_server_pages_obtained);
675 }
676 }
677
678
TextLink(int target,const char * text,int x,int y,float zoom,TextDrawOptions::Alignment alignment)679 TextLink::TextLink(int target, const char* text, int x, int y, float zoom,
680 TextDrawOptions::Alignment alignment):
681 _target(target), _text(reinterpret_cast<const unsigned char*>(text)),
682 _y_begin(y), _y_end(y + FontManager::get_instance().line_height(UI_FONT, zoom)),
683 _zoom(zoom)
684 {
685 int width = FontManager::get_instance().line_width(UI_FONT, _text.c_str(),
686 _text.length(), zoom);
687 switch (alignment)
688 {
689 case TextDrawOptions::Alignment::LEFT:
690 _x_begin = x;
691 break;
692 case TextDrawOptions::Alignment::CENTER:
693 _x_begin = x - width / 2;
694 break;
695 case TextDrawOptions::Alignment::RIGHT:
696 _x_begin = x - width;
697 break;
698 }
699 _x_end = _x_begin + width;
700 }
701
display() const702 void TextLink::display() const
703 {
704 TextDrawOptions options = TextDrawOptions().set_zoom(_zoom);
705 if (_under_mouse)
706 options.set_foreground(gui_bright_color[0], gui_bright_color[1], gui_bright_color[2]);
707 else
708 options.set_foreground(gui_color[0], gui_color[1], gui_color[2]);
709 FontManager::get_instance().draw(UI_FONT, _text.c_str(), _text.length(),
710 _x_begin, _y_begin, options);
711 }
712
713 const int BookWindow::page_width_book = std::round(0.40 * BookWindow::book_width);
714 const int BookWindow::page_height_book = std::round(0.80 * BookWindow::book_height);
715 const int BookWindow::page_width_paper = std::round(0.80 * BookWindow::paper_width);
716 const int BookWindow::page_height_paper = std::round(0.80 * BookWindow::paper_height);
717
718 const int BookWindow::x_offset_book = std::round(0.078 * BookWindow::book_width);
719 const int BookWindow::y_offset_book = std::round(0.098 * BookWindow::book_height);
720 const int BookWindow::x_half_book = std::round(0.46 * BookWindow::book_width);
721 const int BookWindow::x_offset_paper = std::round(0.098 * BookWindow::paper_width);
722 const int BookWindow::y_offset_paper = std::round(0.098 * BookWindow::paper_height);
723
get_book()724 Book* BookWindow::get_book()
725 {
726 try
727 {
728 return &BookCollection::get_instance().get_book(_book_id);
729 }
730 catch (const ExtendedException&)
731 {
732 return nullptr;
733 }
734 }
735
add_links(window_info * win)736 void BookWindow::add_links(window_info *win)
737 {
738 Book *book = get_book();
739
740 _links.clear();
741
742 float zoom = win->current_scale;
743 int x, y_buttons = win->len_y - _ui_margin - _ui_font_height;
744 int target = book->active_page_nr() - book->page_delta();
745 if (book && target >= 0)
746 {
747 x = std::round(zoom * x_margin_button);
748 _links.emplace_back(target, "<-", x, y_buttons, zoom);
749 }
750
751 x = win->len_x / 2;
752 _links.emplace_back(-1, "[X]", x, y_buttons, zoom, TextDrawOptions::Alignment::CENTER);
753
754 target = book->active_page_nr() + book->page_delta();
755 if (book && target < book->nr_pages())
756 {
757 x = std::round(win->len_x - zoom * x_margin_button);
758 _links.emplace_back(target, "->", x, y_buttons, zoom, TextDrawOptions::Alignment::RIGHT);
759 }
760
761 int button_width = FontManager::get_instance().line_width(UI_FONT,
762 reinterpret_cast<const unsigned char*>("<-"), 2, zoom);
763 int dx = std::round((win->len_x - 2 * zoom * x_margin_button - 2 * button_width) / 10);
764 for (int j = -4; j < 5; ++j)
765 {
766 if (j == 0)
767 continue;
768 target = book->active_page_nr() + j * book->page_delta();
769 if (target >= 0 && target < book->nr_pages())
770 {
771 std::string lbl = std::to_string(target + 1);
772 x = win->len_x / 2 + j * dx;
773 _links.emplace_back(target, lbl.c_str(), x, y_buttons, zoom, TextDrawOptions::Alignment::CENTER);
774 }
775 }
776 }
777
display_handler(window_info * win)778 int BookWindow::display_handler(window_info *win)
779 {
780 Book *book = get_book();
781 if (!book)
782 {
783 hide_window(win->window_id);
784 return 0;
785 }
786
787 float zoom = win->current_scale;
788 if (!book->is_laid_out())
789 {
790 int page_width = book->paper_type() == Book::BOOK
791 ? std::round(zoom * page_width_book)
792 : std::round(zoom * page_width_paper);
793 int page_height = book->paper_type() == Book::BOOK
794 ? std::round(zoom * page_height_book)
795 : std::round(zoom * page_height_paper);
796 book->layout(page_width, page_height, zoom);
797 add_links(win);
798 }
799
800 int y_buttons = win->len_y - _ui_margin - _ui_font_height;
801 int y_bottom = y_buttons - _ui_margin;
802 bind_texture(book->paper_type() == Book::BOOK ? _book_texture : _paper_texture);
803 glBegin(GL_QUADS);
804 glTexCoord2f(0.0f, 1.0f); glVertex2i(0, 0);
805 glTexCoord2f(0.0f, 0.0f); glVertex2i(0, y_bottom);
806 glTexCoord2f(1.0f, 0.0f); glVertex2i(win->len_x, y_bottom);
807 glTexCoord2f(1.0f, 1.0f); glVertex2i(win->len_x, 0);
808 glEnd();
809
810 float dx = std::round(
811 zoom * (book->paper_type() == Book::BOOK ? x_offset_book : x_offset_paper)
812 );
813 float dy = std::round(
814 zoom * (book->paper_type() == Book::BOOK ? y_offset_book : y_offset_paper)
815 );
816
817 glPushMatrix();
818 glTranslatef(dx, dy, 0.0);
819 book->display(zoom);
820 glPopMatrix();
821
822 for (const auto& link: _links)
823 link.display();
824
825 return 1;
826 }
827
static_display_handler(window_info * win)828 int BookWindow::static_display_handler(window_info *win)
829 {
830 BookWindow *window = reinterpret_cast<BookWindow*>(win->data);
831 if (!window)
832 return 0;
833 return window->display_handler(win);
834 }
835
mouseover_handler(int mx,int my)836 int BookWindow::mouseover_handler(int mx, int my)
837 {
838 for (auto& link: _links)
839 link.mouseover(mx, my);
840 return 0;
841 }
842
static_mouseover_handler(window_info * win,int mx,int my)843 int BookWindow::static_mouseover_handler(window_info *win, int mx, int my)
844 {
845 BookWindow *window = reinterpret_cast<BookWindow*>(win->data);
846 if (!window)
847 return 0;
848 return window->mouseover_handler(mx, my);
849 }
850
click_handler(window_info * win,int mx,int my,Uint32 flags)851 int BookWindow::click_handler(window_info *win, int mx, int my, Uint32 flags)
852 {
853 if ((flags & ELW_MOUSE_BUTTON) == 0)
854 return 0;
855
856 Book *book = get_book();
857 if (!book)
858 return 0;
859
860 for (const auto& link: _links)
861 {
862 if (link.is_under(mx, my))
863 {
864 if (link.target() < 0)
865 {
866 hide_window(win->window_id);
867 }
868 else
869 {
870 book->turn_to_page(link.target());
871 add_links(win);
872 }
873
874 return 1;
875 }
876 }
877
878 return 0;
879 }
880
static_click_handler(window_info * win,int mx,int my,Uint32 flags)881 int BookWindow::static_click_handler(window_info *win, int mx, int my, Uint32 flags)
882 {
883 BookWindow *window = reinterpret_cast<BookWindow*>(win->data);
884 if (!window)
885 return 0;
886 return window->click_handler(win, mx, my, flags);
887 }
888
ui_scale_handler(window_info * win)889 int BookWindow::ui_scale_handler(window_info *win)
890 {
891 _ui_margin = std::round(win->current_scale * ui_margin);
892 _ui_font_height = FontManager::get_instance().line_height(UI_FONT, win->current_scale);
893 int ui_height = 2 * _ui_margin + _ui_font_height;
894 if (win->window_id == _book_win)
895 {
896 int width = std::round(win->current_scale*book_width);
897 int height = std::round(win->current_scale*book_height + ui_height);
898 resize_window(_book_win, width, height);
899 }
900 else if (win->window_id == _paper_win)
901 {
902 int width = std::round(win->current_scale*paper_width);
903 int height = std::round(win->current_scale*paper_height + ui_height);
904 resize_window(_paper_win, width, height);
905 }
906
907 Book *book = get_book();
908 if (book)
909 book->renew_layout();
910
911 return 1;
912 }
913
static_ui_scale_handler(window_info * win)914 int BookWindow::static_ui_scale_handler(window_info *win)
915 {
916 BookWindow *window = reinterpret_cast<BookWindow*>(win->data);
917 if (!window)
918 return 0;
919 return window->ui_scale_handler(win);
920 }
921
font_change_handler(window_info * win,FontManager::Category cat)922 int BookWindow::font_change_handler(window_info *win, FontManager::Category cat)
923 {
924 if (cat == FontManager::Category::UI_FONT)
925 {
926 // Resize the window
927 ui_scale_handler(win);
928 return 1;
929 }
930 if (cat == FontManager::Category::BOOK_FONT)
931 {
932 Book *book = get_book();
933 if (!book)
934 return 0;
935 book->renew_layout();
936 return 1;
937 }
938 return 0;
939 }
940
static_font_change_handler(window_info * win,FontManager::Category cat)941 int BookWindow::static_font_change_handler(window_info *win, FontManager::Category cat)
942 {
943 BookWindow *window = reinterpret_cast<BookWindow*>(win->data);
944 if (!window)
945 return 0;
946 return window->font_change_handler(win, cat);
947 }
948
display(Book & book)949 void BookWindow::display(Book &book)
950 {
951 if (_book_texture == Uint32(-1))
952 _book_texture = load_texture_cached("textures/book1.dds", tt_image);
953 if (_paper_texture == Uint32(-1))
954 _paper_texture = load_texture_cached("textures/paper1.dds", tt_image);
955
956 // Close existing windows, to prevent opening both the book and paper window
957 close();
958
959 int &win_id = book.paper_type() == Book::BOOK ? _book_win : _paper_win;
960 if (win_id >= 0)
961 {
962 if (_book_id != book.id())
963 {
964 window_info *win = &windows_list.window[win_id];
965 safe_strncpy(win->window_name, book.title().c_str(), sizeof(win->window_name));
966 _book_id = book.id();
967 add_links(win);
968 }
969 show_window(win_id);
970 }
971 else
972 {
973 _book_id = book.id();
974 win_id = create_window(book.title().c_str(), -1, 0, window_x, window_y,
975 0, 0, (ELW_USE_UISCALE|ELW_WIN_DEFAULT)^ELW_CLOSE_BOX);
976 if (win_id >= 0 && win_id < windows_list.num_windows)
977 {
978 window_info *win = &windows_list.window[win_id];
979 win->data = reinterpret_cast<void*>(this);
980 ui_scale_handler(win);
981 }
982
983 set_window_handler(win_id, ELW_HANDLER_DISPLAY, (int (*)())&static_display_handler);
984 set_window_handler(win_id, ELW_HANDLER_MOUSEOVER, (int (*)())&static_mouseover_handler);
985 set_window_handler(win_id, ELW_HANDLER_CLICK, (int (*)())&static_click_handler);
986 set_window_handler(win_id, ELW_HANDLER_UI_SCALE, (int (*)())&static_ui_scale_handler);
987 set_window_handler(win_id, ELW_HANDLER_FONT_CHANGE, (int (*)())&static_font_change_handler);
988 }
989
990 select_window(win_id);
991 }
992
993
initialize()994 void BookCollection::initialize()
995 {
996 static const std::array<std::pair<std::string, int>, 6> race_books = {
997 std::make_pair("books/races/human.xml", book_human),
998 std::make_pair("books/races/dwarf.xml", book_dwarf),
999 std::make_pair("books/races/elf.xml", book_elf),
1000 std::make_pair("books/races/gnome.xml", book_gnome),
1001 std::make_pair("books/races/orchan.xml", book_orchan),
1002 std::make_pair("books/races/draegoni.xml", book_draegoni)
1003 };
1004
1005 for (const auto& tup: race_books)
1006 {
1007 try
1008 {
1009 add_book(Book::read_book(tup.first, Book::BOOK, tup.second));
1010 }
1011 CATCH_AND_LOG_EXCEPTIONS
1012 }
1013
1014 try
1015 {
1016 read_knowledge_book_index();
1017 }
1018 CATCH_AND_LOG_EXCEPTIONS
1019 }
1020
get_book(int id)1021 Book& BookCollection::get_book(int id)
1022 {
1023 try
1024 {
1025 return _books.at(id);
1026 }
1027 catch (std::out_of_range&)
1028 {
1029 EXTENDED_EXCEPTION(ExtendedException::ec_item_not_found,
1030 "Book with id " << id << " not found");
1031 }
1032 }
1033
add_book(Book && book)1034 void BookCollection::add_book(Book&& book)
1035 {
1036 int id = book.id();
1037 auto res = _books.emplace(id, book);
1038 if (!res.second)
1039 {
1040 LOG_ERROR("A book with ID %d was already present", id);
1041 }
1042 }
1043
parse_knowledge_item(const xmlNode * node)1044 void BookCollection::parse_knowledge_item(const xmlNode *node)
1045 {
1046 for (const xmlNode *cur = node; cur; cur = cur->next)
1047 {
1048 if (cur->type != XML_ELEMENT_NODE
1049 || xmlStrcasecmp(cur->name, reinterpret_cast<const xmlChar*>("Knowledge"))
1050 || !cur->children || !cur->children->content)
1051 continue;
1052
1053 xmlChar* id_str = xmlGetProp(cur, reinterpret_cast<const xmlChar*>("ID"));
1054 if (!id_str)
1055 {
1056 LOG_ERROR("Knowledge Item does not contain an ID property.");
1057 continue;
1058 }
1059
1060 char* fname = 0;
1061 if (MY_XMLSTRCPY(&fname, reinterpret_cast<const char*>(cur->children->content)) != -1)
1062 {
1063 int id = atoi(reinterpret_cast<const char*>(id_str));
1064 try
1065 {
1066 add_book(Book::read_book(fname, Book::BOOK, id + knowledge_book_offset));
1067 knowledge_list[id].has_book = 1;
1068 }
1069 catch (const ExtendedException& e)
1070 {
1071 LOG_ERROR("%s(): %s", __func__, e.what());
1072 }
1073 free(fname);
1074 }
1075 else
1076 {
1077 #ifndef OSX
1078 LOG_ERROR("An error occured when parsing the content of the <%s>-tag on line %d - Check it for letters that cannot be translated into iso8859-1\n",
1079 cur->name, cur->line);
1080 #else
1081 LOG_ERROR("An error occured when parsing the content of the <%s>-tag - Check it for letters that cannot be translated into iso8859-1\n",
1082 cur->name);
1083 #endif
1084 }
1085
1086 xmlFree(id_str);
1087 }
1088 }
1089
read_knowledge_book_index()1090 void BookCollection::read_knowledge_book_index()
1091 {
1092 static const std::string path("knowledge.xml");
1093
1094 xmlDoc *doc = xmlReadFile(path.c_str(), NULL, 0);
1095 if (!doc)
1096 {
1097 static const std::string err = "Can't open knowledge book index";
1098 LOG_TO_CONSOLE(c_red1, err.c_str());
1099 EXTENDED_EXCEPTION(ExtendedException::ec_file_not_found, err);
1100 }
1101
1102 xmlNode *root = xmlDocGetRootElement(doc);
1103 if (!root)
1104 {
1105 xmlFreeDoc(doc);
1106 EXTENDED_EXCEPTION(ExtendedException::ec_invalid_parameter,
1107 "Error while parsing: " << path);
1108 }
1109 if (xmlStrcasecmp(root->name, reinterpret_cast<const xmlChar*>("Knowledge_Books")))
1110 {
1111 xmlFreeDoc(doc);
1112 EXTENDED_EXCEPTION(ExtendedException::ec_file_not_found,
1113 "Root element in " << path << " is not <Knowledge_Books>");
1114 }
1115
1116 parse_knowledge_item(root->children);
1117
1118 xmlFreeDoc(doc);
1119 }
1120
request_server_page(int id,int page)1121 void BookCollection::request_server_page(int id, int page)
1122 {
1123 unsigned char msg[] = { SEND_BOOK, static_cast<unsigned char>(id & 0xff),
1124 static_cast<unsigned char>((id >> 8) & 0xff),
1125 static_cast<unsigned char>(page & 0xff),
1126 static_cast<unsigned char>((page >> 8) & 0xff) };
1127 my_tcp_send(my_socket, msg, 5);
1128 }
1129
open_book(int id)1130 void BookCollection::open_book(int id)
1131 {
1132 try
1133 {
1134 Book& book = get_book(id);
1135 _window.display(book);
1136 }
1137 catch (const ExtendedException&)
1138 {
1139 request_server_page(id, 0);
1140 }
1141 }
1142
read_local_book(const unsigned char * data,size_t len)1143 void BookCollection::read_local_book(const unsigned char* data, size_t len)
1144 {
1145 if (len < 3)
1146 {
1147 EXTENDED_EXCEPTION(ExtendedException::ec_invalid_parameter,
1148 "Incomplete local book description from the server");
1149 }
1150
1151 int id = int(data[1]) | int(data[2]) << 8;
1152 try
1153 {
1154 Book& book = get_book(id);
1155 _window.display(book);
1156 }
1157 catch (const ExtendedException&)
1158 {
1159 Book::PaperType paper_type = (data[0] == 1 ? Book::PAPER : Book::BOOK);
1160 std::string file_name(data + 3, data + len);
1161 add_book(Book::read_book(file_name, paper_type, id));
1162 Book& book = get_book(id);
1163 _window.display(book);
1164 }
1165 }
1166
read_server_book(const unsigned char * data,size_t len)1167 void BookCollection::read_server_book(const unsigned char* data, size_t len)
1168 {
1169 if (len < 6)
1170 {
1171 LOG_ERROR("Incomplete server book description from the server");
1172 return;
1173 }
1174
1175 int id = int(data[1]) | int(data[2]) << 8;
1176 size_t title_len = size_t(data[4]) | size_t(data[5]) << 8;
1177
1178 Book *book;
1179 try
1180 {
1181 book = &get_book(id);
1182 }
1183 catch (const ExtendedException&)
1184 {
1185 Book::PaperType paper_type = (data[0] == 1 ? Book::PAPER : Book::BOOK);
1186 if (title_len + 6 > len)
1187 {
1188 LOG_ERROR("Invalid title length in server book description");
1189 return;
1190 }
1191 std::string title(data + 6, data + 6 + title_len);
1192 add_book(Book(paper_type, title, id));
1193 book = &get_book(id);
1194 }
1195
1196 book->add_server_page(data[3]);
1197 book->add_server_content(data + 6 + title_len, len - 6 - title_len);
1198
1199 _window.display(*book);
1200 }
1201
read_network_book(const unsigned char * data,size_t len)1202 void BookCollection::read_network_book(const unsigned char* data, size_t len)
1203 {
1204 if (len < 1)
1205 return;
1206
1207 try
1208 {
1209 switch (*data)
1210 {
1211 case LOCAL:
1212 read_local_book(data + 1, len - 1);
1213 break;
1214 case SERVER:
1215 read_server_book(data + 1, len - 1);
1216 break;
1217 default:
1218 EXTENDED_EXCEPTION(ExtendedException::ec_invalid_parameter,
1219 "Unknown book source ID " << int(*data));
1220 }
1221 }
1222 CATCH_AND_LOG_EXCEPTIONS
1223 }
1224
1225 } // namespace eternal_lands
1226
1227 extern "C"
1228 {
1229
1230 using namespace eternal_lands;
1231
init_books(void)1232 void init_books(void)
1233 {
1234 BookCollection::get_instance().initialize();
1235 }
1236
open_book(int id)1237 void open_book(int id)
1238 {
1239 BookCollection::get_instance().open_book(id);
1240 }
1241
close_book(int id)1242 void close_book(int id)
1243 {
1244 BookCollection::get_instance().close_book(id);
1245 }
1246
book_is_open(int id)1247 int book_is_open(int id)
1248 {
1249 return BookCollection::get_instance().book_is_open(id);
1250 }
1251
book_window_is_open()1252 int book_window_is_open()
1253 {
1254 return BookCollection::get_instance().window_is_open();
1255 }
1256
select_book_window()1257 void select_book_window()
1258 {
1259 BookCollection::get_instance().select_window();
1260 }
1261
close_book_window()1262 void close_book_window()
1263 {
1264 BookCollection::get_instance().close_window();
1265 }
1266
read_network_book(const unsigned char * data,size_t len)1267 void read_network_book(const unsigned char* data, size_t len)
1268 {
1269 BookCollection::get_instance().read_network_book(data, len);
1270 }
1271
1272
1273 } // extern "C"
1274