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