1 #include <stdlib.h>
2 #include <string.h>
3 #include <SDL_endian.h>
4 #include "context_menu.h"
5 #include "console.h"
6 #include "elconfig.h"
7 #include "dialogues.h"
8 #include "asc.h"
9 #include "elwindows.h"
10 #include "filter.h"
11 #include "io/elpathwrapper.h"
12 #include "gamewin.h"
13 #include "init.h"
14 #include "hud.h"
15 #include "interface.h"
16 #include "multiplayer.h"
17 #include "paste.h"
18 #include "sound.h"
19 #include "textures.h"
20 #include "translate.h"
21 #ifdef OPENGL_TRACE
22 #include "gl_init.h"
23 #endif
24 
25 /*!
26  * response structure used in dialogues with NPCs. It contains the data of a response from some NPC.
27  */
28 typedef struct{
29 	unsigned char text[200]; /*!< text of the response */
30 
31 	int width_in_char;  /*!< the length of the respon option in characters */
32 
33     /*!
34 	 * \name calculate response coordinates
35 	 * @{
36 	 */
37     int pos_x, pos_y, width;
38     /*! @} */
39 
40 	int to_actor; /*!< id of the actor to which this response is directed */
41 	int response_id; /*!< unique id of the response */
42 	int in_use; /*!< flag whether this response is in use or not */
43 	int mouse_over; /*!< flag whether the mouse is over this response */
44 }response;
45 
46 #define MAX_RESPONSES 40 /* max. number of response entries in \see dialogue_responces */
47 #define	MAX_PORTRAITS_TEXTURES	16
48 #define MAX_MESS_LINES 9
49 #define MAX_SAVED_RESPONSES 8
50 #define MAX_DIALOGUE_TEXT 2048
51 
52 static unsigned char dialogue_string[MAX_DIALOGUE_TEXT];
53 unsigned char npc_name[NPC_NAME_BUF_LEN] = "";
54 char npc_mark_str[NPC_NAME_BUF_LEN] = "%s (npc)";
55 int cur_portrait=8;
56 int portraits_tex[MAX_PORTRAITS_TEXTURES];
57 
58 response dialogue_responces[MAX_RESPONSES];
59 
60 static int char_frame_size = 0;
61 static int char_size = 0;
62 static int border_x_space = 0;
63 static int border_y_space = 0;
64 static const int available_text_width = 70 * SMALL_FIXED_FONT_WIDTH;
65 static int bot_line_height = 0;
66 
67 static int show_keypress_letters = 0;
68 static int recalc_option_positions = 1;
69 int autoclose_storage_dialogue=0;
70 int auto_select_storage_option=0;
71 int dialogue_copy_excludes_responses=0;
72 int dialogue_copy_excludes_newlines=0;
73 static int done_auto_storage_select=0;
74 static Uint32 copy_end_highlight_time = 0;
75 static Uint32 repeat_end_highlight_time = 0;
76 static int close_str_width = -1;
77 static int copy_str_width = -1;
78 static int repeat_str_width = -1;
79 static int close_pos_x = 0;
80 static int copy_pos_x = 0;
81 static int repeat_pos_x = 0;
82 static int highlight_close = 0;
83 static int highlight_copy = 0;
84 static int highlight_repeat = 0;
85 static int mouse_over_name = 0;
86 static int response_y_offset = 0;
87 static size_t cm_npcname_id = CM_INIT_VALUE;
88 static size_t cm_dialog_copy_id = CM_INIT_VALUE;
89 static size_t cm_dialog_repeat_id = CM_INIT_VALUE;
90 static int new_dialogue = 1;
91 static int npc_name_x_start = 0;
92 static int npc_name_width = 0;
93 static size_t saved_response_list_top = 0;
94 static size_t saved_response_list_bot = 0;
95 static size_t saved_response_list_cur = 0;
96 static size_t saved_response_init = 0;
97 static response saved_responses[MAX_SAVED_RESPONSES];
98 static int cm_dialogue_repeat_handler(window_info *win, int widget_id, int mx, int my, int option);
99 static void send_response(window_info *win, const response *the_response);
100 
101 typedef struct
102 {
103 	char name[NPC_NAME_BUF_LEN];
104 	int do_logging;
105 } text_log_table_type;
106 
107 static text_log_table_type * text_log_table = NULL;
108 static size_t text_log_table_size = 0;
109 static int text_log_enabled = 0;
110 static int new_text_to_log = 0;
111 static const char text_log_filename[] = "npc_log_list.txt";
112 
cleanup_dialogues(void)113 void cleanup_dialogues(void)
114 {
115 	if (text_log_table != NULL)
116 	{
117 		free(text_log_table);
118 		text_log_table = NULL;
119 	}
120 }
121 
load_dialogue_portraits(void)122 void load_dialogue_portraits(void)
123 {
124 	int i;
125 	for(i=0; i<MAX_PORTRAITS_TEXTURES; i++)
126 	{
127 		char buffer[256];
128 		safe_snprintf(buffer, sizeof(buffer), "textures/portraits%d.dds", i+1);
129 		if (check_image_name(buffer, sizeof(buffer), buffer) != 0)
130 			portraits_tex[i] = load_texture_cached(buffer, tt_gui);
131 	}
132 }
133 
clear_dialogue_responses(void)134 void clear_dialogue_responses(void)
135 {
136 	int i;
137 	for(i=0;i<MAX_RESPONSES;i++)
138 		dialogue_responces[i].in_use=0;
139 }
140 
build_response_entries(const Uint8 * data,int total_length)141 void build_response_entries (const Uint8 *data, int total_length)
142 {
143 	int i = 0;
144 	int len = 0;
145 	int last_index=0;
146 
147 	//first, clear the previous dialogue entries
148 	clear_dialogue_responses();
149 	recalc_option_positions = new_dialogue = new_text_to_log = 1;
150 
151 	for(i=0; i < MAX_RESPONSES;i++)
152 	{
153 		// break if we don't have a length field
154 		if (last_index + 3 > total_length)
155 			break;
156 		len = SDL_SwapLE16(*((Uint16 *)(data + last_index)));
157 		// break if we don't have a complete response
158 		if (last_index + 3 + len + 2 + 2 > total_length)
159 			break;
160 
161 		dialogue_responces[i].in_use = 1;
162 		safe_strncpy((char*)dialogue_responces[i].text, (char*)&data[last_index + 2], len);
163 		dialogue_responces[i].response_id = SDL_SwapLE16(*((Uint16 *)(data + last_index + 2 + len)));
164 		dialogue_responces[i].to_actor = SDL_SwapLE16(*((Uint16 *)(data + last_index + 2 + 2 + len)));
165 		// apply any local or global filters and set the final length of the text
166 		dialogue_responces[i].width_in_char = filter_text((char *)dialogue_responces[i].text, len, sizeof (dialogue_responces[i].text));
167 		last_index += len + 3 * 2;
168 	}
169 
170 	/* remove any previous saved responses */
171 	if (saved_response_init)
172 		for(i=0;i<MAX_RESPONSES;i++)
173 			if (dialogue_responces[i].in_use && (dialogue_responces[i].to_actor != saved_responses[saved_response_list_top].to_actor))
174 			{
175 				saved_response_list_bot = saved_response_list_top = saved_response_init = saved_response_list_cur = 0;
176 				cm_set(cm_dialog_repeat_id, "--\n", NULL);
177 				break;
178 			}
179 }
180 
strip_dialogue_text(char * to_str,size_t to_str_max,const char * prefix,int excludes_newlines)181 static void strip_dialogue_text(char *to_str, size_t to_str_max, const char *prefix, int excludes_newlines)
182 {
183 	size_t from_str_len = strlen((char *)dialogue_string);
184 	int from_index = 0;
185 	int to_index = 0;
186 	if (prefix != NULL && strlen(prefix))
187 	{
188 		safe_strncpy2(to_str, prefix, to_str_max, strlen(prefix));
189 		to_index += strlen(prefix);
190 	}
191 	while(from_index<from_str_len && to_index<to_str_max-1)
192 	{
193 		if (!is_color (dialogue_string[from_index]) && dialogue_string[from_index] != '\r' &&
194 			(!excludes_newlines || dialogue_string[from_index] != '\n'))
195 			to_str[to_index++] = dialogue_string[from_index];
196 		from_index++;
197 	}
198 	to_str[to_index] = '\0';
199 }
200 
text_log_add_new_npc(const char * the_name)201 static void text_log_add_new_npc(const char *the_name)
202 {
203 	if (the_name == NULL || (strlen(the_name) < 1) || (strlen(the_name) >= NPC_NAME_BUF_LEN))
204 		return;
205 	text_log_table = realloc(text_log_table, (text_log_table_size + 1) * sizeof(text_log_table_type));
206 	safe_strncpy(text_log_table[text_log_table_size].name, the_name, NPC_NAME_BUF_LEN);
207 	text_log_table[text_log_table_size].do_logging = 1;
208 	text_log_table_size++;
209 }
210 
text_log_load_npc_list(void)211 static void text_log_load_npc_list(void)
212 {
213 	FILE *fp = NULL;
214 	char *line = NULL;
215 	const size_t line_len = 256;
216 	if (text_log_table != NULL)
217 		return;
218 	text_log_table_size = 0;
219 	if ((fp = open_file_config(text_log_filename, "r")) == NULL)
220 		return;
221 	line = malloc(line_len);
222 	while (!feof(fp))
223 	{
224 		if ((fgets(line, line_len, fp) != NULL) && (strlen(line) > 0) && (strlen(line) < NPC_NAME_BUF_LEN))
225 		{
226 			size_t i;
227 			for (i=0; i<strlen(line); i++)
228 				if ((line[i] == '\n') || (line[i] == '\r'))
229 				{
230 					line[i] = '\0';
231 					break;
232 				}
233 			text_log_add_new_npc(line);
234 		}
235 	}
236 	fclose(fp);
237 	free(line);
238 }
239 
text_log_get_npc_setting(void)240 static void text_log_get_npc_setting(void)
241 {
242 	int i;
243 	text_log_enabled = 0;
244 	if (text_log_table == NULL)
245 		text_log_load_npc_list();
246 	for (i=0; i<text_log_table_size; i++)
247 		if (strcmp((const char *)npc_name, text_log_table[i].name) == 0)
248 		{
249 			text_log_enabled = text_log_table[i].do_logging;
250 			break;
251 		}
252 }
253 
text_log_write(void)254 static void text_log_write(void)
255 {
256 	const size_t str_len = NPC_NAME_BUF_LEN + 3;
257 	char * str = malloc(str_len);
258 	const size_t to_str_max = MAX_DIALOGUE_TEXT * 1.5;
259 	char *to_str = (char *)malloc(to_str_max);
260 	safe_snprintf(str, str_len, "%s: %c", npc_name, to_color_char(c_grey1));
261 	strip_dialogue_text(to_str, to_str_max, str, 1);
262 	LOG_TO_CONSOLE(c_blue1, to_str);
263 	free(to_str);
264 	free(str);
265 }
266 
text_log_modify_npc_setting(void)267 static void text_log_modify_npc_setting(void)
268 {
269 	FILE *fp = NULL;
270 	int found = 0;
271 	int i;
272 	for (i=0; i<text_log_table_size; i++)
273 		if (strcmp((const char *)npc_name, text_log_table[i].name) == 0)
274 		{
275 			text_log_table[i].do_logging = text_log_enabled;
276 			found = 1;
277 			break;
278 		}
279 	if (text_log_enabled)
280 		text_log_write();
281 	if (!found && text_log_enabled)
282 		text_log_add_new_npc((const char *)npc_name);
283 	if ((fp = open_file_config(text_log_filename, "w")) == NULL)
284 		return;
285 	for (i=0; i<text_log_table_size; i++)
286 	{
287 		if (text_log_table[i].do_logging)
288 		{
289 			fputs(text_log_table[i].name, fp);
290 			fputs("\n", fp);
291 		}
292 	}
293 	fclose(fp);
294 }
295 
calculate_option_positions(window_info * win)296 static void calculate_option_positions(window_info *win)
297 {
298 	int i = 0;
299 	int start_x = border_x_space;
300 	int start_y = response_y_offset;
301 	int width = 0;
302 	int width_extra = (show_keypress_letters) ? 3*win->small_font_max_len_x : 0;
303 	for(i=0; i < MAX_RESPONSES;i++)
304 	{
305 		if (!dialogue_responces[i].in_use)
306 			break;
307 		width = get_string_width_zoom(dialogue_responces[i].text,
308 			win->font_category, win->current_scale_small);
309 		if (i < 36)
310 			width += width_extra;
311 		if ((start_x + width) > (win->len_x - 2 * border_x_space))
312 		{
313 			start_x = border_x_space;
314 			start_y += win->small_font_len_y;
315 		}
316 		dialogue_responces[i].pos_x = start_x;
317 		dialogue_responces[i].pos_y = start_y;
318 		dialogue_responces[i].width = width;
319 		start_x += width + 2 * win->small_font_max_len_x;
320 	}
321 	recalc_option_positions = 0;
322 }
323 
display_dialogue_handler(window_info * win)324 static int display_dialogue_handler(window_info *win)
325 {
326 	static int last_show_keypress_letters = -1;
327 	int i;
328 	int last_pos_y = 0;
329 	unsigned char str[128];
330 
331 	// auto select storage option
332 	if (auto_select_storage_option && !done_auto_storage_select)
333 	{
334 		for(i=0;i<MAX_RESPONSES;i++)
335 			if (dialogue_responces[i].in_use && (strcmp((const char*)dialogue_responces[i].text, open_storage_str) == 0))
336 			{
337 				send_response(win, &dialogue_responces[i]);
338 				done_auto_storage_select = 1;
339 				break;
340 			}
341 	}
342 
343 	if (last_show_keypress_letters != show_keypress_letters)
344 	{
345 		last_show_keypress_letters = show_keypress_letters;
346 		recalc_option_positions = 1;
347 	}
348 	if (recalc_option_positions)
349 		calculate_option_positions(win);
350 
351 	//calculate the npc_name_x_start (to have it centered on the screen)
352 	npc_name_width = get_string_width_zoom(npc_name, win->font_category, win->current_scale_small);
353 	npc_name_x_start = (win->len_x - npc_name_width) / 2;
354 
355 	glDisable(GL_TEXTURE_2D);
356 	//draw the character frame
357 	glColor3f(0.0f,1.0f,1.0f);
358 	glBegin(GL_LINE_LOOP);
359 	glVertex3i(0,0,0);
360 	glVertex3i(char_frame_size, 0, 0);
361 	glVertex3i(char_frame_size, char_frame_size, 0);
362 	glVertex3i(0, char_frame_size, 0);
363 	glEnd();
364 	glEnable(GL_TEXTURE_2D);
365 
366 	glColor3f(1.0f,1.0f,1.0f);
367 	//ok, now let's draw the portrait
368 	if(cur_portrait!=-1)
369 	{
370 		float u_start,v_start,u_end,v_end;
371 		int x_start,x_end,y_start,y_end;
372 		int this_texture;
373 
374 		//get the UV coordinates.
375 		u_start = 0.25f * (cur_portrait % 4);
376 		u_end = u_start + 0.25f;
377 		v_start = 0.25f * (cur_portrait / 4);
378 		v_end = v_start + 0.25f;
379 
380 		//get the x and y
381 		x_start=1;
382 		x_end = x_start + char_size;
383 		y_start=1;
384 		y_end = y_start + char_size;
385 
386 		//get the texture this item belongs to
387 		this_texture=cur_portrait/16;
388 		this_texture=portraits_tex[this_texture];
389 
390 		bind_texture(this_texture);
391 		glBegin(GL_QUADS);
392 		draw_2d_thing(u_start,v_start,u_end,v_end,x_start,y_start,x_end,y_end);
393 		glEnd();
394 	}
395 
396 	//draw the main text
397 	draw_string_small_zoomed(char_frame_size + border_x_space, border_y_space, dialogue_string, MAX_MESS_LINES, win->current_scale);
398 
399 	//ok, now draw the responses
400 	for(i=0;i<MAX_RESPONSES;i++)
401 	{
402 		if(dialogue_responces[i].in_use)
403 		{
404 			if(dialogue_responces[i].mouse_over)
405 				glColor3f(1.0f,0.5f,0.0f);
406 			else
407 				glColor3f(1.0f,1.0f,0.0f);
408 			if(show_keypress_letters)
409 			{
410 				if(i>=0 && i<=8) // 1-9
411 					safe_snprintf((char*)str,sizeof(str),"%c] %s",49+i, dialogue_responces[i].text);
412 				else if(i==9) //0
413 					safe_snprintf((char*)str,sizeof(str),"0] %s", dialogue_responces[i].text);
414 				else if(i>=10 && i<=35) // A-Z
415 					safe_snprintf((char*)str,sizeof(str),"%c] %s",55+i, dialogue_responces[i].text);
416 				else // too many dialogue options, you have to click these
417 					 safe_snprintf((char*)str,sizeof(str),"%s", dialogue_responces[i].text);
418 			}
419 			else
420 				safe_snprintf((char*)str,sizeof(str),"%s", dialogue_responces[i].text);
421 			last_pos_y = dialogue_responces[i].pos_y;
422 			draw_string_small_zoomed(dialogue_responces[i].pos_x, last_pos_y, str, 1, win->current_scale);
423 		}
424 		else
425 			break;
426 	}
427 
428 	// automatically resize window if there are more options than space
429 	if ((last_pos_y + 3 * win->small_font_len_y + 1) > win->len_y)
430 	{
431 		resize_window(win->window_id, win->len_x, last_pos_y + 3 * win->small_font_len_y + 1);
432 		new_dialogue = 1;
433 	}
434 
435 	//now, draw the character name
436 	glColor3f(1.0f,1.0f,1.0f);
437 	draw_string_small_zoomed(npc_name_x_start, win->len_y - bot_line_height, npc_name, 1, win->current_scale);
438 
439 	if (highlight_close)
440 		glColor3f(1.0f,0.5f,0.0f);
441 	else
442 		glColor3f(1.0f,1.0f,1.0f);
443 	draw_string_small_zoomed(close_pos_x, win->len_y - bot_line_height, (unsigned char*)close_str, 1, win->current_scale);
444 
445 	if (copy_end_highlight_time > SDL_GetTicks())
446 		glColor3f(1.0f,0.25f,0.0f);
447 	else if (highlight_copy)
448 		glColor3f(1.0f,0.5f,0.0f);
449 	else
450 		glColor3f(1.0f,1.0f,1.0f);
451 	draw_string_small_zoomed(copy_pos_x, win->len_y - bot_line_height, (unsigned char*)dialogue_copy_str, 1, win->current_scale);
452 
453 	if (!saved_response_init)
454 		glColor3f(0.5f,0.5f,0.5f);
455 	else if (repeat_end_highlight_time > SDL_GetTicks())
456 		glColor3f(1.0f,0.25f,0.0f);
457 	else if (highlight_repeat)
458 		glColor3f(1.0f,0.5f,0.0f);
459 	else
460 		glColor3f(1.0f,1.0f,1.0f);
461 	draw_string_small_zoomed(repeat_pos_x, win->len_y - bot_line_height, (unsigned char*)dialogue_repeat_str, 1, win->current_scale);
462 
463 	// display help text if appropriate
464 	if ((show_help_text) && (highlight_repeat || highlight_copy || mouse_over_name))
465 		show_help(cm_help_options_str, 0, win->len_y+10, win->current_scale);
466 
467 	show_keypress_letters = highlight_close = highlight_copy = highlight_repeat = mouse_over_name = 0;
468 
469 	// if this is the first time we displayed this dialogue, do first time stuff
470 	if (new_dialogue)
471 	{
472 		new_dialogue = 0;
473 		done_auto_storage_select = 0;
474 		cm_remove_regions(win->window_id);
475 		cm_add_region(cm_npcname_id, win->window_id, npc_name_x_start,
476 			win->len_y - bot_line_height, npc_name_width, win->small_font_len_y);
477 		cm_add_region(cm_dialog_copy_id, win->window_id, copy_pos_x,
478 			win->len_y - bot_line_height, copy_str_width, win->small_font_len_y);
479 		cm_add_region(cm_dialog_repeat_id, win->window_id, repeat_pos_x,
480 			win->len_y - bot_line_height, repeat_str_width, win->small_font_len_y);
481 		if (text_log_enabled && new_text_to_log)
482 		{
483 			text_log_write();
484 			new_text_to_log = 0;
485 		}
486 	}
487 
488 #ifdef OPENGL_TRACE
489 CHECK_GL_ERRORS();
490 #endif //OPENGL_TRACE
491 	return 0;
492 }
493 
close_dialogue(void)494 void close_dialogue(void)
495 {
496 	hide_window_MW(MW_DIALOGUE);
497 }
498 
mouseover_dialogue_handler(window_info * win,int mx,int my)499 static int mouseover_dialogue_handler(window_info *win, int mx, int my)
500 {
501 	int i;
502 
503 	show_keypress_letters = 0;
504 	if(use_keypress_dialogue_boxes)
505 	{
506 	 	if(use_full_dialogue_window || ((mx >= 0 && mx <= char_size) && (my >= 0 && my <= char_size)))
507 			show_keypress_letters = 1;
508 	}
509 
510 	if(mx >= close_pos_x && mx < (close_pos_x + close_str_width) && my >= (win->len_y - bot_line_height))
511 		highlight_close = 1;
512 	if(mx > copy_pos_x && mx < (copy_pos_x + copy_str_width) && my >= (win->len_y - bot_line_height))
513 		highlight_copy = 1;
514 	if(mx > repeat_pos_x && mx < (repeat_pos_x + repeat_str_width) &&  my >= (win->len_y - bot_line_height))
515 		highlight_repeat = 1;
516 	if (mx > npc_name_x_start && mx < (npc_name_x_start + npc_name_width) && my >= (win->len_y - bot_line_height))
517 		mouse_over_name = 1;
518 
519 	//first, clear the mouse overs
520 	for(i=0;i<MAX_RESPONSES;i++)
521 		dialogue_responces[i].mouse_over=0;
522 
523 	for(i=0;i<MAX_RESPONSES;i++)
524 	{
525 		if(dialogue_responces[i].in_use)
526 		{
527 			if (mx >= dialogue_responces[i].pos_x && mx <= (dialogue_responces[i].pos_x + dialogue_responces[i].width) &&
528 				my >= dialogue_responces[i].pos_y && my <= (dialogue_responces[i].pos_y + win->small_font_len_y))
529 			{
530 				dialogue_responces[i].mouse_over=1;
531 				return 0;
532 			}
533 		}
534 		else
535 			break;
536 	}
537 	return 0;
538 }
539 
540 
541 #if 0
542 /* just for debug */
543 static void show_response_list(void)
544 {
545 	size_t i;
546 	printf("Responses:-\n");
547 	for (i=saved_response_list_top; saved_response_list_bot<MAX_SAVED_RESPONSES; i--)
548 	{
549 		printf("[%s] %s\n", saved_responses[i].text, ((i==saved_response_list_cur) ?"*" :""));
550 		if (i == saved_response_list_bot)
551 			break;
552 		if (i == 0)
553 			i = MAX_SAVED_RESPONSES;
554 	}
555 	printf("\n");
556 }
557 #endif
558 
save_response(const response * last_response)559 static void save_response(const response *last_response)
560 {
561 	size_t i;
562 
563 	/* not the first time for this dialogue */
564 	if (saved_response_init)
565 	{
566 		/* check for repeats */
567 		for (i=saved_response_list_top; saved_response_list_bot<MAX_SAVED_RESPONSES; i--)
568 		{
569 			if (saved_responses[i].response_id == last_response->response_id)
570 			{
571 				saved_response_list_cur = i;
572 				return;
573 			}
574 			if (i == saved_response_list_bot)
575 				break;
576 			if (i == 0)
577 				i = MAX_SAVED_RESPONSES;
578 		}
579 		/* move indexes */
580 		if (++saved_response_list_top >= MAX_SAVED_RESPONSES)
581 			saved_response_list_top = 0;
582 		if (saved_response_list_top == saved_response_list_bot)
583 			if (++saved_response_list_bot >= MAX_SAVED_RESPONSES)
584 				saved_response_list_bot = 0;
585 	}
586 	/* first time for this dialogue */
587 	else
588 		saved_response_init = 1;
589 
590 	/* save the response - memcpy() ok as no pointers */
591 	saved_response_list_cur = saved_response_list_top;
592 	memcpy(&saved_responses[saved_response_list_top], last_response, sizeof(response));
593 
594 	/* rebuild the context menu from current saved responses, newest first */
595 	cm_set(cm_dialog_repeat_id, "", cm_dialogue_repeat_handler);
596 	for (i=saved_response_list_top; saved_response_list_bot<MAX_SAVED_RESPONSES; i--)
597 	{
598 		cm_add(cm_dialog_repeat_id, (const char*)saved_responses[i].text, NULL);
599 		if (i == saved_response_list_bot)
600 			break;
601 		if (i == 0)
602 			i = MAX_SAVED_RESPONSES;
603 	}
604 }
605 
606 
send_response(window_info * win,const response * the_response)607 static void send_response(window_info *win, const response *the_response)
608 {
609 	Uint8 str[16];
610 	str[0]=RESPOND_TO_NPC;
611 	*((Uint16 *)(str+1))=SDL_SwapLE16((short)the_response->to_actor);
612 	*((Uint16 *)(str+3))=SDL_SwapLE16((short)the_response->response_id);
613 	my_tcp_send(my_socket,str,5);
614 	if (autoclose_storage_dialogue && strcmp((const char*)the_response->text, open_storage_str) == 0)
615  		hide_window(win->window_id);
616 	save_response(the_response);
617 }
618 
619 
copy_dialogue_text(void)620 static void copy_dialogue_text(void)
621 {
622 	size_t to_str_max = MAX_DIALOGUE_TEXT * 1.5;
623 	char *to_str = (char *)malloc(to_str_max);
624 
625 	strip_dialogue_text(to_str, to_str_max, NULL, dialogue_copy_excludes_newlines);
626 
627 	if (!dialogue_copy_excludes_responses)
628 	{
629 		size_t response_str_len = 128;
630 		char *response_str = (char *)malloc(response_str_len);
631 		int response_num = 1;
632 		int i;
633 		// if there are responses, add some new lines between the text.
634 		for(i=0;i<MAX_RESPONSES;i++)
635 			if(dialogue_responces[i].in_use)
636 			{
637 				safe_strcat(to_str, "\n\n", to_str_max);
638 				break;
639 			}
640 		// add the numbered response strings, one per line
641 		for(i=0;i<MAX_RESPONSES;i++)
642 		{
643 			if(dialogue_responces[i].in_use)
644 			{
645 				safe_snprintf(response_str, response_str_len, "%d) %s\n", response_num++, dialogue_responces[i].text);
646 				safe_strcat(to_str, response_str, to_str_max);
647 			}
648 		}
649 		free(response_str);
650 	}
651 
652 	copy_to_clipboard(to_str);
653 	free(to_str);
654 }
655 
send_repeat(window_info * win)656 static void send_repeat(window_info *win)
657 {
658 	if (!saved_response_init)
659 		return;
660 	repeat_end_highlight_time = SDL_GetTicks() + 500;
661 	send_response(win, &saved_responses[saved_response_list_cur]);
662 	do_click_sound();
663 }
664 
do_copy(void)665 static void do_copy(void)
666 {
667 	copy_end_highlight_time = SDL_GetTicks() + 500;
668 	copy_dialogue_text();
669 	do_click_sound();
670 }
671 
click_dialogue_handler(window_info * win,int mx,int my,Uint32 flags)672 static int click_dialogue_handler(window_info *win, int mx, int my, Uint32 flags)
673 {
674 	int i;
675 
676 	// only handle mouse button clicks, not scroll wheels moves
677 	if ( (flags & ELW_MOUSE_BUTTON) == 0) return 0;
678 
679 	for(i=0;i<MAX_RESPONSES;i++)
680 		{
681 			if(dialogue_responces[i].in_use && dialogue_responces[i].mouse_over)
682 				{
683 					send_response(win, &dialogue_responces[i]);
684 					do_click_sound();
685 					return 1;
686 				}
687 		}
688 	if(mx >= close_pos_x && mx < (close_pos_x + close_str_width) && my >= (win->len_y - bot_line_height))
689 		{
690 			do_window_close_sound();
691 			hide_window(win->window_id);
692 			return 1;
693 		}
694 	if((flags & ELW_LEFT_MOUSE) && mx > copy_pos_x && mx < (copy_pos_x + copy_str_width) && my >= (win->len_y - bot_line_height))
695 		{
696 			do_copy();
697 			return 1;
698 		}
699 	if((flags & ELW_LEFT_MOUSE) && mx > repeat_pos_x && mx < (repeat_pos_x + repeat_str_width) && my >= (win->len_y - bot_line_height))
700 		{
701 			send_repeat(win);
702 			return 1;
703 		}
704 
705 	return 0;
706 }
707 
keypress_dialogue_handler(window_info * win,int mx,int my,SDL_Keycode key_code,Uint32 key_unicode,Uint16 key_mod)708 static int keypress_dialogue_handler (window_info *win, int mx, int my, SDL_Keycode key_code, Uint32 key_unicode, Uint16 key_mod)
709 {
710 	Uint8 ch;
711 
712 	if (key_code == SDLK_ESCAPE) // close window if Escape pressed
713 	{
714 		do_window_close_sound();
715 		hide_window(win->window_id);
716 		return 1;
717 	}
718 
719 	if(!use_keypress_dialogue_boxes)
720 	{
721 		return 0;
722 	}
723 
724 	if ((use_full_dialogue_window == 0) && (mx<0 || mx>char_size || my<0 || my>char_size))
725 	{
726 		return 0;
727 	}
728 
729 	if (key_mod & KMOD_ALT)
730 	{
731 		if ((strlen(dialogue_repeat_str)>1) && (key_code == (Uint8)tolower(dialogue_repeat_str[1])))
732 		{
733 			send_repeat(win);
734 			return 1;
735 		}
736 		if ((strlen(dialogue_copy_str)>1) && (key_code == (Uint8)tolower(dialogue_copy_str[1])))
737 		{
738 			do_copy();
739 			return 1;
740 		}
741 	}
742 
743 	if((key_mod & KMOD_ALT) || (key_mod & KMOD_CTRL)) //Do not process Ctrl or Alt keypresses
744 	{
745 		return 0;
746 	}
747 
748 	ch = key_to_char (key_unicode);
749 
750 	if(ch<'0' || ch>'z') // do not send special keys
751 	{
752 		return 0;
753 	}
754 	if(ch>='a' && ch<='z')
755 		ch-=87; //a-z->10-35
756 	else if(ch>='A' && ch<='Z')
757 		ch-=55; //A-Z->10-35
758 	else if(ch>='1' && ch<='9')
759 		ch-=49; //1-9->0-8
760 	else if(ch=='0') //0->9
761 		ch=9;
762 	else //out of range
763 	{
764 		return 0;
765 	}
766 
767 	if(MAX_RESPONSES-1<ch)//pressed a key that the client is not expecting, ignore it
768 	{
769 		return 1;
770 	}
771 	if(dialogue_responces[ch].in_use)
772 	{
773 		do_click_sound();
774 		send_response(win, &dialogue_responces[ch]);
775 		return 1;
776 	}
777 	return 0;
778 }
779 
cm_dialogue_repeat_handler(window_info * win,int widget_id,int mx,int my,int option)780 static int cm_dialogue_repeat_handler(window_info *win, int widget_id, int mx, int my, int option)
781 {
782 	if (saved_response_init && (option < MAX_SAVED_RESPONSES))
783 	{
784 		size_t i = saved_response_list_top;
785 		if (option > i)
786 			i += MAX_SAVED_RESPONSES;
787 		i -= option;
788 		send_response(win, &saved_responses[i]);
789 		return 1;
790 	}
791 	else
792 		return 0;
793 }
794 
cm_npcname_handler(window_info * win,int widget_id,int mx,int my,int option)795 static int cm_npcname_handler(window_info *win, int widget_id, int mx, int my, int option)
796 {
797 	if (option == 0)
798 		copy_to_clipboard((const char *)npc_name);
799 	else if (option == 1)
800 	{
801 		char *str = NULL, *del_pos = NULL;
802 		const char *delim = "%s";
803 		size_t delim_len = strlen(delim);
804 		size_t npc_name_len = 0, start_len = 0, end_len = 0, str_len = 0;
805 
806 		if (!strlen(npc_mark_str) ||
807 			!(npc_name_len = strlen((char *)npc_name)) ||
808 			((del_pos = strstr(npc_mark_str, delim)) == NULL))
809 		{
810 			LOG_TO_CONSOLE(c_red1, invalidnpcmark_str);
811 			return 0;
812 		}
813 
814 		start_len = del_pos - npc_mark_str;
815 		end_len = strlen(&npc_mark_str[start_len + delim_len]);
816 		str_len = start_len + npc_name_len + end_len + 1;
817 
818 		if ((str = (char *)malloc(str_len)) == NULL)
819 			return 0;
820 
821 		// previously used sprintf() but its unsafe to use a user specified target string
822 		safe_strncpy2(str, npc_mark_str, str_len, start_len);
823 		str[start_len] = 0;
824 		safe_strcat(str, (char *)npc_name, str_len);
825 		safe_strcat(str, &npc_mark_str[start_len + delim_len], str_len);
826 
827 		command_unmark_special(str, strlen(str), 0);
828 		command_mark(str, strlen(str));
829 
830 		free(str);
831 	}
832 	else if (option == 2)
833 		text_log_modify_npc_setting();
834 	else
835 		return 0;
836 	return 1;
837 }
838 
ui_scale_dialogue_handler(window_info * win)839 int ui_scale_dialogue_handler(window_info *win)
840 {
841 	int dialogue_menu_x_len = (int)(0.5 + win->current_scale * available_text_width);
842 	int dialogue_menu_y_len = (int)(0.5 + win->current_scale * 220);
843 
844 	// We need to recompute line breaks on UI scale change, as different scale factors can lead to
845 	// different roundings of window size, and more importantly, character widths. For this reason,
846 	// scaling the UI is not a simple scale operation as far as text is concerned.
847 	reset_soft_breaks(dialogue_string, strlen((const char*)dialogue_string), sizeof(dialogue_string),
848 		win->font_category, win->current_scale_small, (int)(0.5 + win->current_scale * available_text_width), NULL, NULL);
849 
850 	char_size = (int)(0.5 + win->current_scale * 64);
851 	char_frame_size = char_size + 2;
852 	border_x_space = (int)(0.5 + win->current_scale * win->small_font_max_len_x);
853 	border_y_space = (int)(0.5 + win->current_scale * 5);
854 	dialogue_menu_x_len += 2 * border_x_space + char_frame_size;
855 	response_y_offset = 2 * border_y_space + MAX_MESS_LINES * win->small_font_len_y;
856 	bot_line_height = win->small_font_len_y + 1;
857 
858 	copy_pos_x = border_x_space;
859 	copy_str_width = get_string_width_zoom((unsigned char*)dialogue_copy_str,
860 		win->font_category, win->current_scale_small);
861 	close_str_width = get_string_width_zoom((unsigned char*)close_str,
862 		win->font_category, win->current_scale_small);
863 	close_pos_x = dialogue_menu_x_len - close_str_width - border_x_space;
864 	repeat_str_width = get_string_width_zoom((unsigned char*)dialogue_repeat_str,
865 		win->font_category, win->current_scale_small);
866 	repeat_pos_x = copy_pos_x + copy_str_width + 2 * border_x_space;
867 
868 	resize_window(win->window_id, dialogue_menu_x_len, dialogue_menu_y_len);
869 	recalc_option_positions = new_dialogue = 1;
870 
871 	return 1;
872 }
873 
change_dialogue_font_handler(window_info * win,font_cat cat)874 int change_dialogue_font_handler(window_info *win, font_cat cat)
875 {
876 	if (cat != win->font_category)
877 		return 0;
878 	ui_scale_dialogue_handler(win);
879 	return 1;
880 }
881 
display_dialogue(const Uint8 * in_data,int data_length)882 void display_dialogue(const Uint8 *in_data, int data_length)
883 {
884 	int dialogue_win = get_id_MW(MW_DIALOGUE);
885 
886 	if (!get_show_window(dialogue_win))
887 		do_icon_click_sound();
888 
889 	if(dialogue_win < 0)
890 	{
891 		dialogue_win = create_window("Dialogue", game_root_win, 0,
892 			get_pos_x_MW(MW_DIALOGUE), get_pos_y_MW(MW_DIALOGUE), 0, 0, (ELW_USE_UISCALE|ELW_WIN_DEFAULT)^ELW_CLOSE_BOX);
893 		set_id_MW(MW_DIALOGUE, dialogue_win);
894 
895 		set_window_custom_scale(dialogue_win, MW_DIALOGUE);
896 		set_window_handler(dialogue_win, ELW_HANDLER_DISPLAY, &display_dialogue_handler );
897 		set_window_handler(dialogue_win, ELW_HANDLER_MOUSEOVER, &mouseover_dialogue_handler );
898 		set_window_handler(dialogue_win, ELW_HANDLER_KEYPRESS, (int (*)())&keypress_dialogue_handler );
899 		set_window_handler(dialogue_win, ELW_HANDLER_CLICK, &click_dialogue_handler );
900 		set_window_handler(dialogue_win, ELW_HANDLER_UI_SCALE, &ui_scale_dialogue_handler );
901 		set_window_handler(dialogue_win, ELW_HANDLER_FONT_CHANGE, &change_dialogue_font_handler);
902 
903 		cm_add(windows_list.window[dialogue_win].cm_id, cm_dialog_menu_str, NULL);
904 		cm_add(windows_list.window[dialogue_win].cm_id, cm_dialog_options_str, NULL);
905 		cm_bool_line(windows_list.window[dialogue_win].cm_id, ELW_CM_MENU_LEN+1, &use_keypress_dialogue_boxes, "use_keypress_dialog_boxes");
906 		cm_bool_line(windows_list.window[dialogue_win].cm_id, ELW_CM_MENU_LEN+2, &use_full_dialogue_window, "use_full_dialogue_window");
907 		cm_bool_line(windows_list.window[dialogue_win].cm_id, ELW_CM_MENU_LEN+3, &autoclose_storage_dialogue, NULL);
908 		cm_bool_line(windows_list.window[dialogue_win].cm_id, ELW_CM_MENU_LEN+4, &auto_select_storage_option, NULL);
909 
910 		cm_npcname_id = cm_create(cm_npcname_menu_str, cm_npcname_handler);
911 		cm_dialog_copy_id = cm_create(cm_dialog_copy_menu_str, NULL);
912 		cm_bool_line(cm_npcname_id, 2, &text_log_enabled, NULL);
913 		cm_dialog_repeat_id = cm_create("--\n", cm_dialogue_repeat_handler);
914 		cm_bool_line(cm_dialog_copy_id, 0, &dialogue_copy_excludes_responses, NULL);
915 		cm_bool_line(cm_dialog_copy_id, 1, &dialogue_copy_excludes_newlines, NULL);
916 
917 	}
918 	else
919 	{
920 		show_window(dialogue_win);
921 		select_window(dialogue_win);
922 	}
923 
924 	text_log_get_npc_setting();
925 
926 	// set the dialogue text and filter apply any local or global filters
927 	safe_strncpy2((char*)dialogue_string, (const char*)in_data, sizeof(dialogue_string), data_length);
928 	filter_text((char *)dialogue_string, data_length, sizeof (dialogue_string));
929 
930 	if (dialogue_win >=0 && dialogue_win < windows_list.num_windows)
931 		ui_scale_dialogue_handler(&windows_list.window[dialogue_win]);
932 	check_proportional_move(MW_DIALOGUE);
933 
934 	recalc_option_positions = new_dialogue = new_text_to_log = 1;
935 }
936 
937