chat.c 30.8 KB
Newer Older
1
/*
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 *
 *   Copyright (c) 1994, 2002, 2003 Johannes Prix
 *   Copyright (c) 1994, 2002 Reinhard Prix
 *   Copyright (c) 2004-2010 Arthur Huillet
 *
 *
 *  This file is part of Freedroid
 *
 *  Freedroid is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  Freedroid is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
21 22
 *  along with Freedroid; see the file COPYING. If not, write to the
 *  Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
 *  MA  02111-1307  USA
 *
 */

/**
 * This file contains all functions dealing with the dialog interface,
 * including blitting the chat log to the screen and drawing the
 * right portrait images to the screen.
 */

#define _chat_c

#include "system.h"

#include "defs.h"
#include "struct.h"
#include "proto.h"
#include "global.h"
41
#include "lua.h"
42 43 44

#include "widgets/widgets.h"

45 46 47
/* Stack of chat contexts. The current context is at the top of the stack */
static struct list_head chat_context_stack = LIST_HEAD_INIT(chat_context_stack);
static int chat_context_stack_size = 0;
48

49 50 51 52
static struct widget_group *chat_menu = NULL;
struct widget_text *chat_log = NULL;
struct widget_text_list *chat_selector = NULL;
static struct widget_group *chat_wait = NULL;
53

54 55
static struct image **droid_portrait = NULL;
static SDL_Rect chat_selector_inner_rect;
56 57

/**
58
 * Print an error message and stop the game if too many dialogs are stacked.
59
 */
60
static void check_chat_context_stack_size()
61
{
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
	struct auto_string *chat_stack_str = alloc_autostr(64);
	struct chat_context *ctx;
	int first = TRUE;

	if (chat_context_stack_size < CHAT_CONTEXT_STACK_SIZE) {
		free_autostr(chat_stack_str);
		return;
	}

	list_for_each_entry_reverse(ctx, &chat_context_stack, stack_node) {
		autostr_append(chat_stack_str, "%s%s", (first) ? "" : " -> ", ctx->npc->dialog_basename);
		first = FALSE;
	}

	error_message(__FUNCTION__, "The chat context stack reached its maximum (%d).\n"
			                   "It could mean that we are under an infinite recursion attack, so our last defense is to stop the game.\n"
			                   "Current dialog stack (from first to last stacked dialog):\n%s",
			                   PLEASE_INFORM | IS_FATAL | NO_TERMINATE, CHAT_CONTEXT_STACK_SIZE, chat_stack_str->value);

	free_autostr(chat_stack_str);
	Terminate(EXIT_FAILURE);
83 84 85
}

/**
86 87 88
 * Return the current chat context.
 *
 * The current context is the one at top of the contexts stack.
89
 *
90
 * @return A pointer to the current chat context, or NULL if no context is stacked.
91
 */
92
struct chat_context *chat_get_current_context(void)
93
{
94 95
	if (list_empty(&chat_context_stack))
		return NULL;
96

97
	return (struct chat_context *)list_entry(chat_context_stack.next, struct chat_context, stack_node);
98 99 100
}

/**
101 102 103 104 105 106 107
 * Push a chat context on top of the stack.
 *
 * The pushed context becomes the current one.
 *
 * @param chat_context Pointer to the chat context to push.
 *
 * @return TRUE if on success, else return FALSE
108
 */
109
int chat_push_context(struct chat_context *chat_context)
110
{
111 112 113 114 115 116 117 118
	check_chat_context_stack_size();
	list_add(&chat_context->stack_node, &chat_context_stack);
	chat_context_stack_size++;

	if (!call_lua_func(LUA_DIALOG, "FDdialog", "push_dialog", "sS", NULL, chat_context->npc->dialog_basename, &chat_context->npc->enabled_nodes)) {
		list_del(chat_context_stack.next);
		chat_context_stack_size--;
		return FALSE;
119 120
	}

121 122
	return TRUE;
}
123 124

/**
125
 * Pop from the chat contexts stack.
126
 *
127 128
 * This function deletes the context at the top of the list.
 * The new top context becomes the current one.
129
 */
130
static void chat_pop_context()
131
{
132 133 134
	struct chat_context *top_chat_context = chat_get_current_context();
	if (top_chat_context) {
		// clear the enabled_nodes dynarray, before to fill it with new values
135
		int i;
136 137 138 139 140 141
		for (i = 0; i < top_chat_context->npc->enabled_nodes.size; i++) {
			char **ptr = dynarray_member(&top_chat_context->npc->enabled_nodes, i, sizeof(char *));
			free(*ptr);
			*ptr = NULL;
		}
		dynarray_free(&top_chat_context->npc->enabled_nodes);
142

143 144 145
		// Tell lua dialog engine to pop the current dialog, and get the currently enabled
		// nodes (so that they will be saved along with the npc data)
		call_lua_func(LUA_DIALOG, "FDdialog", "pop_dialog", NULL, "S", &top_chat_context->npc->enabled_nodes);
146

147 148 149 150 151 152
		// remove the dialog from the chat context stack
		list_del(chat_context_stack.next);
		chat_delete_context(top_chat_context);
		chat_context_stack_size--;
	}
}
153 154

/**
155 156 157 158 159 160
 * Create a chat context and initialize it.
 *
 * @param partner     Pointer to the enemy to talk with
 * @param npc         Pointer to the npc containing the dialogue definition.
 * @param dialog_name Name of the dialog
 * @return Pointer to the allocated chat context.
161
 */
162
struct chat_context *chat_create_context(enemy *partner, struct npc *npc, const char *dialog_name)
163
{
164 165 166 167 168 169 170 171 172 173 174 175 176 177
	struct chat_context *new_chat_context = (struct chat_context *)MyMalloc(sizeof(struct chat_context));

	// Init chat control variables.
	// MyMalloc() already zeroed the struct, so we only initialize 'non zero' fields.

	new_chat_context->partner = partner;
	new_chat_context->npc = npc;
	new_chat_context->partner_started = partner->will_rush_tux;

	// The first time a dialog is open, the init script has to be run first.
	// The next times the dialog is open, directly run the startup script.
	new_chat_context->state = LOAD_INIT_SCRIPT;
	if (npc->chat_character_initialized) {
		new_chat_context->state = LOAD_STARTUP_SCRIPT;
178
	}
179 180 181 182 183

	// No dialog option currently selected by the user
	new_chat_context->current_option = -1;

	return new_chat_context;
184 185 186
}

/**
187 188 189
 * Delete a chat_context (free all the memory allocations)
 *
 * @param chat_context Pointer to the caht_context to delete.
190
 */
191 192 193
void chat_delete_context(struct chat_context *chat_context)
{
	free(chat_context);
194 195 196
}

/**
197
 * Fill the chat selector widget with the selected options of the current dialog
198
 */
199
static void fill_chat_selector()
200
{
201
	char *empty_entries[] = { NULL };
202 203
	int i;

204
	widget_text_list_init(chat_selector, empty_entries, NULL);
205

206 207 208
	// Call FDdialog.get_options() to get the list of option texts to display.
	// Note: call_lua_func() can not be used here, because extracting data from a
	// returned table is not yet implemented.
209

210
	lua_State *L = get_lua_state(LUA_DIALOG);
211

212 213 214 215 216 217 218
	lua_getglobal(L, "FDdialog");
	lua_getfield(L, -1, "get_options");
	lua_remove(L, -2);
	if (lua_pcall(L, 0, 1, 0)) {
		DebugPrintf(-1, "error while calling FDdialog.get_options(): %s", lua_tostring(L, -1));
		error_message(__FUNCTION__, "Aborting chat selector filling.", NO_REPORT);
		return;
219 220
	}

221 222 223 224 225
	if (!lua_istable(L, -1)) {
		DebugPrintf(-1, "function FDdialog.get_options() must return a table");
		error_message(__FUNCTION__, "Aborting chat selector filling.", NO_REPORT);
		return;
	}
226

227 228 229 230 231 232 233 234 235 236 237
	for (i = 1; i <= lua_rawlen(L, -1);) {
		lua_rawgeti(L, -1, i);
		int index = lua_to_int(lua_tointeger(L, -1));
		lua_pop(L, 1);
		i++;
		lua_rawgeti(L, -1, i);
		const char *text = lua_tostring(L, -1);
		widget_text_list_dupadd(chat_selector, text, index);
		lua_pop(L, 1);
		i++;
	}
238

239 240
	lua_pop(L, 1);
}
241

242 243 244 245 246 247 248
/**
 * This function is used for darkening the screen area outside the chat screen.
 */
static void display_dark_background(struct widget *w)
{
	draw_rectangle(&w->rect, 0, 0, 0, 50);
}
249

250 251 252 253 254 255 256
/*
 * Return TRUE if we are waiting for the user to click, after a response was
 * added in the chat log.
 */
static int waiting_user_interaction(struct chat_context *context)
{
	int waiting = FALSE;
257

258 259
	waiting = context->wait_user_click ||
	          ((context->state == SELECT_NEXT_NODE) && (context->current_option == -1));
260

261 262
	return waiting;
}
263

264 265 266 267 268 269 270 271 272
/*
 * Unset the wait_user_click flag in the current chat context
 */
static void stop_wait_user_click(struct widget_button *w)
{
	struct chat_context *context = chat_get_current_context();
	if (context)
		context->wait_user_click = FALSE;
}
273

274 275 276 277 278 279 280 281 282 283 284 285
/*
 * Called when an option is selected in the chat_selector.
 * Get the index of the selected dialog node, and store it in the current
 * chat context.
 */
static void dialog_option_selected(struct widget_text_list *wl)
{
	int user_data = widget_text_list_get_data(wl, wl->selected_entry);
	if (user_data >= 0) {
		struct chat_context *context = chat_get_current_context();
		if (context)
			context->current_option = user_data;
286 287 288
	}
}

289
static void scroll_up_chat_log(struct widget_button *wb)
290
{
291
	widget_text_scroll_up(chat_log);
292 293
}

294
static void scroll_down_chat_log(struct widget_button *wb)
295
{
296
	widget_text_scroll_down(chat_log);
297 298
}

299
static void scroll_up_chat_selector(struct widget_button *wb)
300
{
301 302
	widget_text_list_scroll_up(chat_selector);
}
303

304 305 306 307
static void scroll_down_chat_selector(struct widget_button *wb)
{
	widget_text_list_scroll_down(chat_selector);
}
308

309 310 311
//
// Widgets update callbacks
//
312

313 314 315
static void _enable_if_waiting_user_click(struct widget *w)
{
	w->enabled = chat_get_current_context()->wait_user_click;
316 317
}

318
static void _enable_if_not_waiting_user_click(struct widget *w)
319
{
320 321
	w->enabled = !chat_get_current_context()->wait_user_click;
}
322

323 324 325 326
static void _enable_if_chat_log_can_scroll_up(struct widget *w)
{
	WIDGET_BUTTON(w)->active = widget_text_can_scroll_up(chat_log);
}
327

328 329 330 331
static void _enable_if_chat_log_can_scroll_down(struct widget *w)
{
	WIDGET_BUTTON(w)->active = widget_text_can_scroll_down(chat_log);
}
332

333 334 335 336
static void _enable_if_chat_selector_can_scroll_up(struct widget *w)
{
	WIDGET_BUTTON(w)->active = widget_text_list_can_scroll_up(chat_selector);
}
337

338 339 340
static void _enable_if_chat_selector_can_scroll_down(struct widget *w)
{
	WIDGET_BUTTON(w)->active = widget_text_list_can_scroll_down(chat_selector);
341 342 343
}

/**
344
 * This function builds the chat interface if it hasn't already been initialized.
345
 */
346
struct widget_group *create_chat_dialog()
347
{
348 349 350 351
	int left_padding, right_padding, top_padding, bottom_padding;
	int font_height;
	int nb_lines;
	int inner_height, actual_inner_height;
352

353 354
	if (chat_menu)
		return chat_menu;
355

356
	chat_menu = widget_group_create();
357

358 359 360 361 362 363 364 365 366 367 368 369
	//
	// Dark background
	//

	struct widget *dark_background = widget_create();
	widget_set_rect(dark_background, 0, 0, GameConfig.screen_width, GameConfig.screen_height);
	dark_background->display = display_dark_background;
	widget_group_add(chat_menu, dark_background);

	//
	// Entry text selector
	//
370

371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
	int chat_selector_h = UNIVERSAL_COORD_H(170);
	int chat_selector_w = UNIVERSAL_COORD_W(640);
	int chat_selector_x = (GameConfig.screen_width - chat_selector_w) / 2;
	int chat_selector_y = GameConfig.screen_height - chat_selector_h;

	/* Default padding for text area */
	left_padding = 35;
	right_padding = 35;
	top_padding = 24;
	bottom_padding = 24;
	inner_height = chat_selector_h - top_padding - bottom_padding;

	struct widget_background *chat_selector_bkg = widget_background_create();
	widget_set_rect(WIDGET(chat_selector_bkg), chat_selector_x, chat_selector_y, chat_selector_w, chat_selector_h);
	widget_background_load_3x3_tiles(chat_selector_bkg, "widgets/chat_typo");
	widget_group_add(chat_menu, WIDGET(chat_selector_bkg));

	chat_selector = widget_text_list_create();

	/* Adjust padding of the text area, to adapt the vertically padding to the available height */
	font_height = get_font_height(chat_selector->font);
	nb_lines = floor((float)inner_height / (float)font_height);
	actual_inner_height = nb_lines * font_height;
	top_padding += (inner_height - actual_inner_height) / 2;
	bottom_padding = chat_selector_h - actual_inner_height - top_padding;

	chat_selector_inner_rect.x = chat_selector_x + left_padding;
	chat_selector_inner_rect.y = chat_selector_y + top_padding;
	chat_selector_inner_rect.h = chat_selector_h - top_padding - bottom_padding;
	chat_selector_inner_rect.w = chat_selector_w - left_padding - right_padding;

	widget_set_rect(WIDGET(chat_selector), chat_selector_inner_rect.x, chat_selector_inner_rect.y, chat_selector_inner_rect.w, chat_selector_inner_rect.h);
	chat_selector->process_entry = dialog_option_selected;
	WIDGET(chat_selector)->update =	_enable_if_not_waiting_user_click;
	widget_group_add(chat_menu, WIDGET(chat_selector));
406

407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
	//
	// Droid portrait
	//

	struct image *img_frame = widget_load_image_resource("widgets/chat_portrait_frame.png", NO_MOD);
	struct image *img_connector = widget_load_image_resource("widgets/chat_portrait_connector.png", NO_MOD);
	struct image *img_tube = widget_load_image_resource("widgets/chat_portrait_tube.png", NO_MOD);
	int chat_portrait_frame_x = 13;
	int chat_portrait_frame_y = 4;
	int chat_portrait_connector_x = chat_selector_x;
	int chat_portrait_connector_y = chat_selector_y - img_connector->h;
	int chat_portrait_tube_x = chat_portrait_connector_x + 86;
	int chat_portrait_tube_y = chat_portrait_frame_y + img_frame->h;
	int chat_portrait_tube_h = (chat_portrait_connector_y + 39) - chat_portrait_tube_y;

	struct widget_background *chat_portrait_connector = widget_background_create();
	widget_background_add(chat_portrait_connector, img_connector, chat_portrait_connector_x, chat_portrait_connector_y, img_connector->w, img_connector->h, 0);
	widget_group_add(chat_menu, WIDGET(chat_portrait_connector));

	if (chat_portrait_tube_h > 0) {
		int tube_repeat = ceil((float)chat_portrait_tube_h / (float)img_tube->h);
		chat_portrait_tube_h = img_tube->h * tube_repeat;
		chat_portrait_tube_y = (chat_portrait_connector_y + 39) - chat_portrait_tube_h;
		struct widget_background *chat_portrait_tube = widget_background_create();
		widget_background_add(chat_portrait_tube, img_tube, chat_portrait_tube_x, chat_portrait_tube_y, img_tube->w, chat_portrait_tube_h, REPEATED);
		widget_group_add(chat_menu, WIDGET(chat_portrait_tube));
	}
434

435 436 437
	struct widget_background *chat_portrait_frame = widget_background_create();
	widget_background_add(chat_portrait_frame, img_frame, chat_portrait_frame_x, chat_portrait_frame_y, img_frame->w, img_frame->h, 0);
	widget_group_add(chat_menu, WIDGET(chat_portrait_frame));
438

439 440 441 442
	struct widget_button *chat_portrait = widget_button_create();
	widget_set_rect(WIDGET(chat_portrait), chat_portrait_frame_x + 30, chat_portrait_frame_y + 32, 136, 183);
	widget_group_add(chat_menu, WIDGET(chat_portrait));
	droid_portrait = &chat_portrait->image[0][0];
443 444

	//
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518
	// Chat log
	//

	int chat_log_x = chat_portrait_connector_x + img_connector->w;
	int chat_log_y = 0;
	int chat_log_h = chat_selector_y - chat_log_y;
	int chat_log_w = chat_selector_x + chat_selector_w - chat_log_x;

	/* Default padding of the text area */
	left_padding = 50;
	right_padding = 30;
	top_padding = 24;
	bottom_padding = 48;
	inner_height = chat_log_h - top_padding - bottom_padding;

	struct widget_background *chat_log_bkg = widget_background_create();
	widget_set_rect(WIDGET(chat_log_bkg), chat_log_x, chat_log_y, chat_log_w, chat_log_h);
	widget_background_load_3x3_tiles(chat_log_bkg, "widgets/chat_log");
	widget_group_add(chat_menu, WIDGET(chat_log_bkg));

	chat_log = widget_text_create();
	chat_log->font = FPS_Display_Font;

	/* Adjust padding of the text area, to adapt the vertically padding to the available height */
	font_height = get_font_height(chat_log->font);
	nb_lines = floor((float)inner_height / (float)font_height);
	actual_inner_height = nb_lines * font_height;
	top_padding += (inner_height - actual_inner_height) / 2;
	bottom_padding = chat_log_h - actual_inner_height - top_padding;

	int chat_log_inner_rect_x = chat_log_x + left_padding;
	int chat_log_inner_rect_y = chat_log_y + top_padding;
	int chat_log_inner_rect_h = chat_log_h - top_padding - bottom_padding;
	int chat_log_inner_rect_w = chat_log_w - left_padding - right_padding;

	widget_set_rect(WIDGET(chat_log), chat_log_inner_rect_x, chat_log_inner_rect_y, chat_log_inner_rect_w, chat_log_inner_rect_h);
	widget_group_add(chat_menu, WIDGET(chat_log));

	struct {
		char *image[3];
		SDL_Rect rect;
		void (*activate_button)(struct widget_button *);
		void (*update) (struct widget *);
	} b[] = {
		// Chat Log scroll up
		{
			{"widgets/chat_scroll_up_off.png", NULL, "widgets/chat_scroll_up.png"},
			{chat_log_x + (chat_log_w - 158) / 2, chat_log_y + 2, 158, 22},
			scroll_up_chat_log,
			_enable_if_chat_log_can_scroll_up
		},
		// Chat Log scroll down
		{
			{"widgets/chat_scroll_down_off.png", NULL, "widgets/chat_scroll_down.png"},
			{chat_log_x + (chat_log_w - 158) / 2, chat_log_y + chat_log_h - 41, 158, 22},
			scroll_down_chat_log,
			_enable_if_chat_log_can_scroll_down
		},
		// Chat Selector Scroll up
		{
			{"widgets/chat_scroll_up_off.png", NULL, "widgets/chat_scroll_up.png"},
			{chat_selector_x + (chat_selector_w - 158) / 2, chat_selector_y - 2, 158, 22},
			scroll_up_chat_selector,
			_enable_if_chat_selector_can_scroll_up
		},
		// Chat Selector Scroll down
		{
			{"widgets/chat_scroll_down_off.png", NULL, "widgets/chat_scroll_down.png"},
			{chat_selector_x + (chat_selector_w - 158) / 2, chat_selector_y + chat_selector_h - 23, 158, 22},
			scroll_down_chat_selector,
			_enable_if_chat_selector_can_scroll_down
		}
	};

519
	int i;
520 521 522 523 524 525 526 527 528 529 530 531
	for (i = 0; i < sizeof(b) / sizeof(b[0]); i++) {
		struct widget_button *button = widget_button_create();

		WIDGET(button)->rect = b[i].rect;
		WIDGET(button)->update = b[i].update;

		button->image[0][DEFAULT] = widget_load_image_resource(b[i].image[0], NO_MOD);
		button->image[0][PRESSED] = widget_load_image_resource(b[i].image[1], NO_MOD);
		button->image[1][DEFAULT] = widget_load_image_resource(b[i].image[2], NO_MOD);
		button->activate_button = b[i].activate_button;

		widget_group_add(chat_menu, WIDGET(button));
532 533
	}

534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556
	//
	// Widget used when waiting for user click
	//

	chat_wait = widget_group_create();
	widget_set_rect(WIDGET(chat_wait), 0, 0, GameConfig.screen_width, GameConfig.screen_height);
	WIDGET(chat_wait)->enabled = FALSE;
	WIDGET(chat_wait)->update = _enable_if_waiting_user_click;

	struct widget_text* wait_text = widget_text_create();
	widget_set_rect(WIDGET(wait_text), chat_selector_inner_rect.x, chat_selector_inner_rect.y,
	                chat_selector_inner_rect.w, chat_selector_inner_rect.h);
	wait_text->font = FPS_Display_Font;
	widget_text_init(wait_text, _("Click anywhere to continue..."));
	widget_group_add(chat_wait, WIDGET(wait_text));

	struct widget_button *wait_but = widget_button_create();
	widget_set_rect(WIDGET(wait_but), 0, 0, GameConfig.screen_width, GameConfig.screen_height);
	WIDGET(wait_but)->display = NULL;
	wait_but->activate_button = stop_wait_user_click;
	widget_group_add(chat_wait, WIDGET(wait_but));

	widget_group_add(chat_menu, WIDGET(chat_wait));
557

558
	WIDGET(chat_menu)->enabled = FALSE;
559

560
	return chat_menu;
561 562
}

563
void chat_add_response(const char *response)
564
{
565 566
	autostr_append(chat_log->text, "%s", response);
	chat_log->scroll_offset = 0;
567 568 569
}

/**
570 571
 * Run the dialog defined by the chat contest on top of the stack.
 *
572
 * This is the most important subfunction of the whole chat with friendly
573 574
 * droids and characters.  After a chat context has been filled and pushed on
 * the stack, this function is invoked to handle the actual chat interaction
575 576
 * and the dialog flow.
 */
577
void chat_run()
578
{
579
	 char *empty_entries[] = { NULL };
580

581 582
	struct chat_context *current_chat_context = NULL;
	struct widget *chat_ui = WIDGET(chat_menu);
583

584
	Activate_Conservative_Frame_Computation();
585

586 587 588
	// Pump all remaining SDL events
	SDL_Event event;
	while (SDL_PollEvent(&event));
589

590 591
	// Activate chat screen
	chat_ui->enabled = TRUE;
592

593 594 595
	// Init some widgets
	widget_text_init(chat_log, "");
	widget_text_list_init(chat_selector, empty_entries, NULL);
596

597
	// Dialog engine main code.
598
	//
599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681
	// A dialog flow is decomposed into a sequence of several steps:
	// 1 - run the initialization lua code (only once per game)
	// 2 - run the startup lua code (each time a dialog is launched)
	// 3 - interaction step: let the user chose a dialog option (i.e. a dialog
	//     node) and run the associated lua script
	// 4 - loop on the next interaction step, unless the dialog is ended.
	//
	// In some cases, a lua script needs a user's interaction (wait for a click
	// in lua npc_say(), for instance). We then have to interrupt that lua
	// script, run an interaction loop, and resume the lua script after the
	// user's interaction.
	//
	// To implement a script interruption/resuming, we use the co-routine
	// yield/resume features of Lua. When a lua script is run, we thus have to
	// loop on its execution (i.e. resume it) until it ends.
	//
	// For calls to open a dialog inside a dialog (lua start_chat()), the
	// current lua script also has to be interrupted, the context of the new
	// dialog has to be extracted from the stack to run the new dialog flow, and
	// once the new dialog is ended the previous dialog script has to be resumed.
	//
	// All of these apparently intricate loops are implemented with one single
	// loop, taking care of the actual state of the chat, restarting the loop
	// when needed, and extracting the top chat context at each loop step :
	//
	// while (there is something to run)
	//   get chat context from top of stack
	//   if (a user interaction is waited)
	//     check user interaction
	//   if (a user interaction is no more waited) /* do not use an 'else' here ! */
	//     execute dialog FSM step
	//   if (current dialog is ended)
	//     pop the chat context stack
	//
	// TODO: document the relation between the C part and the Lua part of the
	// dialog engine.

	while ((current_chat_context = chat_get_current_context())) {

		/*
		 * 1- Chat screen rendering
		 */

		*droid_portrait = get_droid_portrait_image(current_chat_context->partner->type);
		clear_screen();
		chat_ui->update_tree(chat_ui);

		chat_ui->display(chat_ui);
		blit_mouse_cursor();
		our_SDL_flip_wrapper();

		/*
		 * 2- User's interaction
		 */

		if (waiting_user_interaction(current_chat_context)) {

			SDL_WaitEvent(&event);

			// Specific events handling
			if (event.type == SDL_QUIT) {
				Terminate(EXIT_SUCCESS);
			}
			if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) {
				if (GameConfig.enable_cheatkeys)
					goto end_current_dialog;
			}
			if (current_chat_context->wait_user_click && event.type == SDL_KEYDOWN) {
				switch (event.key.keysym.sym) {
				case SDLK_SPACE:
				case SDLK_RETURN:
					// Force to end waiting
					stop_wait_user_click(NULL);
					break;
				default:
					break;
				}
			}

			// Push the event to the chat screen.
			// Note: we know that the 'chat_menu' widget_group fills the
			// screen, so we do not need to check if the cursor is inside the widget.
			chat_ui->handle_event(chat_ui, &event);
682 683
		}

684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769
		/*
		 * 3- Execute current state of the chat FSM
		 * (Note: do not replace the test with an 'else')
		 */
		if (!waiting_user_interaction(current_chat_context)) {

			switch (current_chat_context->state) {
				case LOAD_INIT_SCRIPT:
				{
					widget_text_init(chat_log, _("[n]--- Start of Dialog ---\n"));
					current_chat_context->script_coroutine = prepare_lua_coroutine(LUA_DIALOG, "FDdialog", "run_init", NULL);
					current_chat_context->end_dialog = 0;
					// next step
					current_chat_context->state = RUN_INIT_SCRIPT;
					break;
				}
				case LOAD_STARTUP_SCRIPT:
				{
					if (current_chat_context->npc->chat_character_initialized)
						widget_text_init(chat_log, _("[n]--- Start of Dialog ---\n"));
					current_chat_context->script_coroutine = prepare_lua_coroutine(LUA_DIALOG, "FDdialog", "run_startup", NULL);
					current_chat_context->end_dialog = 0;
					// next step
					current_chat_context->state = RUN_STARTUP_SCRIPT;
					break;
				}
				case SELECT_NEXT_NODE:
				{
					// Output the 'partner' message, and load the node script into
					// a lua coroutine.
					// Also, reset chat control variables, so the user will have to
					// select a new option, unless the lua code ends the dialog
					// or auto-activates an other option.
					current_chat_context->script_coroutine = prepare_lua_coroutine(LUA_DIALOG, "FDdialog", "run_node", "d", current_chat_context->current_option);
					current_chat_context->end_dialog = 0;
					current_chat_context->current_option = -1;
					// next step
					current_chat_context->state = RUN_NODE_SCRIPT;
					break;
				}
				case RUN_INIT_SCRIPT:
				case RUN_STARTUP_SCRIPT:
				case RUN_NODE_SCRIPT:
				{
					if (current_chat_context->script_coroutine) {
						if (resume_lua_coroutine(current_chat_context->script_coroutine)) {
							// the lua script reached its end, remove its reference
							lua_State *L = get_lua_state(LUA_DIALOG);
							lua_pop(L, 1);
							free(current_chat_context->script_coroutine);
							current_chat_context->script_coroutine = NULL;
							// end dialog, if asked by the lua script
							if (current_chat_context->end_dialog)
								goto end_current_dialog;
						}
					}

					// next step to run once the script has ended
					// (note: do not replace the test with an 'else')
					if (!current_chat_context->script_coroutine) {
						if (current_chat_context->state == RUN_INIT_SCRIPT)
							current_chat_context->state = LOAD_STARTUP_SCRIPT;
						else
							current_chat_context->state = SELECT_NEXT_NODE;
					}

					// The script can have changed the list of active dialog options,
					// so we have to refresh the chat_selector content.
					// But, a new dialog could have been started by the script.
					// In that case, the chat_selector has to be emptied.
					// Note: the chat_selector could be filled at the beginning
					// of the while-loop, just before to display the chat UI.
					// However, calling Lua to get the list of selected options
					// is a bit costly, so we want to avoid to do it at each frame.
					struct chat_context *top_chat_context = chat_get_current_context();
					if (top_chat_context == current_chat_context) {
						fill_chat_selector();
					} else {
						widget_text_list_init(chat_selector, empty_entries, NULL);
					}
					break;
				}
				default:
					break;
			}

770 771
		}

772 773 774 775 776 777 778 779 780 781 782 783 784 785 786
		// restart the loop, to handle user interaction or stepping the FSM.
		continue;

end_current_dialog:
		// Pump all remaining SDL events
		WaitNoEvent();

		// The dialog has ended. Mark the npc as already initialized, to avoid
		// its initialization script to be run again the next time the dialog
		// is open, and pop the dialog from the chat context stack.
		current_chat_context->npc->chat_character_initialized = TRUE;
		chat_pop_context();
		// Tux's option entries have been freed by chat_pop_context()
		// so we have to clean-up the widget using it
		widget_text_list_init(chat_selector, empty_entries, NULL);
787 788
	}

789 790
	// Close chat screen
	chat_ui->enabled = FALSE;
791 792 793
}

/**
794
 * This is more or less the 'main' function of the chat with friendly
795 796 797 798
 * droids and characters.  It is invoked directly from the user interface
 * function as soon as the player requests communication or there is a
 * friendly bot who rushes Tux and opens talk.
 */
799
int chat_with_droid(struct enemy *partner)
800 801
{
	struct npc *npc;
802 803
	struct chat_context *chat_context;
	char *dialog_name;
804

805 806 807 808
	// Create a chat context.
	// Use the partner's dialog name attribute to get the related npc
	// and the dialog to load.
	npc = npc_get(partner->dialog_section_name);
809
	if (!npc)
810 811
		return FALSE;
	dialog_name = partner->dialog_section_name;
812

813
	chat_context = chat_create_context(partner, npc, dialog_name);
814

815 816 817 818 819
	// Push the chat context on the stack.
	// The dialog will be run on the next loop of the chat engine.
	if (!chat_push_context(chat_context)) {
		chat_delete_context(chat_context);
		return FALSE;
820 821
	}

822 823
	// Open the chat screen and run the chat engine.
	chat_run();
824

825
	return TRUE;
826 827 828
}

/**
829 830
 * Validate dialogs syntax and Lua code
 * This validator loads all known dialogs, checking them for syntax.
831 832
 * It tries to compile each Lua code snippet in the dialogs, checking them for syntax as well.
 * It then proceeds to executing each Lua code snippet, which makes it possible to check for some errors
833
 * such as typos in function calls (calls to non existing functions). However, this runtime check does not cover 100% of
834 835
 * the Lua code because of branches (when it encounters a if, it will not test both the "then" and "else" branches).
 *
836
 * As a result, the fact that the validator finds no error does not imply there are no errors in dialogs.
837 838 839 840
 * Syntax is checked fully, but runtime validation cannot check all of the code.
 */
int validate_dialogs()
{
841
	char fpath[PATH_MAX];
842 843 844 845 846 847 848 849 850 851
	enemy *dummy_partner;
	struct npc *n;
	int error_caught = FALSE;

	skip_initial_menus = 1;

	/* Disable sound to speed up validation. */
	int old_sound_on = sound_on;
	sound_on = FALSE;

852 853 854 855
	find_file("levels.dat", MAP_DIR, fpath, PLEASE_INFORM | IS_FATAL);
	LoadShip(fpath, 0);
	PrepareStartOfNewCharacter("NewTuxStartGameSquare");

856 857 858 859 860
	/* Temporarily disable screen fadings to speed up validation. */
	GameConfig.do_fadings = FALSE;

	/* _says functions are not run by the validator, as they display
	   text on screen and wait for clicks */
861
	run_lua(LUA_DIALOG, "function chat_says(a)\nend\n");
862 863 864
	run_lua(LUA_DIALOG, "function cli_says(a)\nend\n");

	/* Subdialogs currently call run_chat and we cannot do that when validating dialogs */
865
	run_lua(LUA_DIALOG, "function start_chat(a)\nend\n");
866 867 868 869 870 871 872 873 874 875 876 877

	/* Shops must not be run (display + wait for clicks) */
	run_lua(LUA_DIALOG, "function trade_with(a)\nend\n");

	run_lua(LUA_DIALOG, "function user_input_string(a)\nreturn \"dummy\";\nend\n");

	run_lua(LUA_DIALOG, "function upgrade_items(a)\nend\n");
	run_lua(LUA_DIALOG, "function craft_addons(a)\nend\n");

	/* takeover requires user input - hardcode it to win */
	run_lua(LUA_DIALOG, "function takeover(a)\nreturn true\nend\n");

878 879 880 881 882
	/* set_mouse_move_target() breaks validator */
	run_lua(LUA_DIALOG, "function set_mouse_move_target(a)\nend\n");

	/* win_game() causes silly animations and delays the process. */
	run_lua(LUA_DIALOG, "function win_game(a)\nend\n");
883

884
	/* This dummy is needed for the Lua functions that communicates with a npc */
885 886 887 888 889
	BROWSE_ALIVE_BOTS(dummy_partner) {
		break;
	}

	list_for_each_entry(n, &npc_head, node) {
890
		printf("Testing dialog \"%s\"...\n", n->dialog_basename);
891

892
		struct chat_context *dummy_context = chat_create_context(dummy_partner, n, n->dialog_basename);
893

894 895 896 897 898
		// We want the dialog validator to catch all errors. It thus has to call
		// push_dialog() itself. As a consequence, so we can not use chat_push_context().
		check_chat_context_stack_size();
		list_add(&dummy_context->stack_node, &chat_context_stack);
		chat_context_stack_size++;
899

900 901 902 903
		int rtn;
		if (call_lua_func(LUA_DIALOG, "FDdialog", "validate_dialog", "s", "d", n->dialog_basename, &rtn)) {
			if (!rtn) {
				error_caught = TRUE;
904
			}
905 906 907 908 909
			if (term_has_color_cap)
				printf("Result: %s\n", rtn ? "\033[32msuccess\033[0m" : "\033[31mfailed\033[0m");
			else
				printf("Result: %s\n", rtn ? "success" : "failed");
		} else {
910 911 912
			error_caught = TRUE;
		}

913 914 915 916 917 918
		// Remove the dialog from the chat context stack
		list_del(chat_context_stack.next);
		chat_delete_context(dummy_context);
		chat_context_stack_size--;

		printf("\n");
919 920 921 922 923 924 925 926 927 928 929
	}

	/* Re-enable sound as needed. */
	sound_on = old_sound_on;

	/* Re-enable screen fadings. */
	GameConfig.do_fadings = TRUE;

	return error_caught;
}

930 931 932 933 934 935 936 937 938
void free_chat_widgets()
{
	if (chat_menu) {
		struct widget *w = WIDGET(chat_menu);
		w->free(w);
		free(chat_menu);
	}
}

939
#undef _chat_c