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