1 /* ResidualVM - A 3D game interpreter
2  *
3  * ResidualVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the AUTHORS
5  * file distributed with this source distribution.
6  *
7  * Additional copyright for this file:
8  * Copyright (C) 1999-2000 Revolution Software Ltd.
9  * This code is based on source code created by Revolution Software,
10  * used with permission.
11  *
12  * This program is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU General Public License
14  * as published by the Free Software Foundation; either version 2
15  * of the License, or (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25  *
26  */
27 
28 #include "engines/icb/floors.h"
29 #include "engines/icb/speech.h"
30 #include "engines/icb/fn_routines.h"
31 #include "engines/icb/session.h"
32 #include "engines/icb/global_objects.h"
33 #include "engines/icb/global_switches.h"
34 #include "engines/icb/icon_list_manager.h"
35 #include "engines/icb/sound.h"
36 #include "engines/icb/mission.h"
37 #include "engines/icb/sound_lowlevel.h"
38 #include "engines/icb/res_man.h"
39 #include "engines/icb/common/ptr_util.h"
40 #include "engines/icb/sound/music_manager.h"
41 
42 namespace ICB {
43 
44 #define TEXT_MAX_WIDTH 300
45 #define IS_SPEECH_STARTED 1
46 #define SPEECH_ERROR 0
47 #define IS_SPEECH_ALREADY_PLAYING 0
48 
GetCountReduction()49 uint32 GetCountReduction() { return 1; }
50 
51 // This colour is used to display voice over text (normally player's speech colour).
52 uint8 voice_over_red = VOICE_OVER_DEFAULT_RED;
53 uint8 voice_over_green = VOICE_OVER_DEFAULT_GREEN;
54 uint8 voice_over_blue = VOICE_OVER_DEFAULT_BLUE;
55 
fn_request_speech(int32 & result,int32 * params)56 mcodeFunctionReturnCodes fn_request_speech(int32 &result, int32 *params) { return (MS->fn_request_speech(result, params)); }
fn_add_talker(int32 & result,int32 * params)57 mcodeFunctionReturnCodes fn_add_talker(int32 &result, int32 *params) { return (MS->fn_add_talker(result, params)); }
fn_issue_speech_request(int32 & result,int32 * params)58 mcodeFunctionReturnCodes fn_issue_speech_request(int32 &result, int32 *params) { return (MS->fn_issue_speech_request(result, params)); }
fn_anon_speech_invite(int32 & result,int32 * params)59 mcodeFunctionReturnCodes fn_anon_speech_invite(int32 &result, int32 *params) { return (MS->fn_anon_speech_invite(result, params)); }
fn_speak(int32 & result,int32 * params)60 mcodeFunctionReturnCodes fn_speak(int32 &result, int32 *params) { return (MS->fn_speak(result, params)); }
fn_confirm_requests(int32 & result,int32 * params)61 mcodeFunctionReturnCodes fn_confirm_requests(int32 &result, int32 *params) { return (MS->fn_confirm_requests(result, params)); }
fn_converse(int32 & result,int32 * params)62 mcodeFunctionReturnCodes fn_converse(int32 &result, int32 *params) { return (MS->fn_converse(result, params)); }
speak_object_face_object(int32 & result,int32 * params)63 mcodeFunctionReturnCodes speak_object_face_object(int32 &result, int32 *params) { return (MS->speak_object_face_object(result, params)); }
speak_play_generic_anim(int32 & result,int32 * params)64 mcodeFunctionReturnCodes speak_play_generic_anim(int32 &result, int32 *params) { return (MS->speak_play_generic_anim(result, params)); }
speak_wait_for_everyone(int32 & result,int32 * params)65 mcodeFunctionReturnCodes speak_wait_for_everyone(int32 &result, int32 *params) { return (MS->speak_wait_for_everyone(result, params)); }
speak_add_chooser_icon(int32 & result,int32 * params)66 mcodeFunctionReturnCodes speak_add_chooser_icon(int32 &result, int32 *params) { return (MS->speak_add_chooser_icon(result, params)); }
speak_user_chooser(int32 & result,int32 * params)67 mcodeFunctionReturnCodes speak_user_chooser(int32 &result, int32 *params) { return (MS->speak_user_chooser(result, params)); }
speak_chosen(int32 & result,int32 * params)68 mcodeFunctionReturnCodes speak_chosen(int32 &result, int32 *params) { return (MS->speak_chosen(result, params)); }
speak_new_menu(int32 & result,int32 * params)69 mcodeFunctionReturnCodes speak_new_menu(int32 &result, int32 *params) { return (MS->speak_new_menu(result, params)); }
speak_close_menu(int32 & result,int32 * params)70 mcodeFunctionReturnCodes speak_close_menu(int32 &result, int32 *params) { return (MS->speak_close_menu(result, params)); }
speak_menu_still_active(int32 & result,int32 * params)71 mcodeFunctionReturnCodes speak_menu_still_active(int32 &result, int32 *params) { return (MS->speak_menu_still_active(result, params)); }
speak_menu_choices_remain(int32 & result,int32 * params)72 mcodeFunctionReturnCodes speak_menu_choices_remain(int32 &result, int32 *params) { return (MS->speak_menu_choices_remain(result, params)); }
speak_end_conversation(int32 & result,int32 * params)73 mcodeFunctionReturnCodes speak_end_conversation(int32 &result, int32 *params) { return (MS->speak_end_conversation(result, params)); }
speak_end_menu(int32 & result,int32 * params)74 mcodeFunctionReturnCodes speak_end_menu(int32 &result, int32 *params) { return (MS->speak_end_menu(result, params)); }
speak_add_special_chooser_icon(int32 & result,int32 * params)75 mcodeFunctionReturnCodes speak_add_special_chooser_icon(int32 &result, int32 *params) { return (MS->speak_add_special_chooser_icon(result, params)); }
speak_set_custom(int32 & result,int32 * params)76 mcodeFunctionReturnCodes speak_set_custom(int32 &result, int32 *params) { return (MS->speak_set_custom(result, params)); }
speak_play_custom_anim(int32 & result,int32 * params)77 mcodeFunctionReturnCodes speak_play_custom_anim(int32 &result, int32 *params) { return (MS->speak_play_custom_anim(result, params)); }
fn_get_speech_status(int32 & result,int32 * params)78 mcodeFunctionReturnCodes fn_get_speech_status(int32 &result, int32 *params) { return (MS->fn_get_speech_status(result, params)); }
fn_kill_conversations(int32 & result,int32 * params)79 mcodeFunctionReturnCodes fn_kill_conversations(int32 &result, int32 *params) { return (MS->fn_kill_conversations(result, params)); }
fn_speech_colour(int32 & result,int32 * params)80 mcodeFunctionReturnCodes fn_speech_colour(int32 &result, int32 *params) { return (MS->fn_speech_colour(result, params)); }
speak_reverse_custom_anim(int32 & result,int32 * params)81 mcodeFunctionReturnCodes speak_reverse_custom_anim(int32 &result, int32 *params) { return (MS->speak_reverse_custom_anim(result, params)); }
speak_preload_custom_anim(int32 & result,int32 * params)82 mcodeFunctionReturnCodes speak_preload_custom_anim(int32 &result, int32 *params) { return (MS->speak_preload_custom_anim(result, params)); }
fn_set_voice_over_colour(int32 & result,int32 * params)83 mcodeFunctionReturnCodes fn_set_voice_over_colour(int32 &result, int32 *params) { return (MS->fn_set_voice_over_colour(result, params)); }
fn_default_voice_over_colour(int32 & result,int32 * params)84 mcodeFunctionReturnCodes fn_default_voice_over_colour(int32 &result, int32 *params) { return (MS->fn_default_voice_over_colour(result, params)); }
85 
fn_get_speech_status(int32 & result,int32 *)86 mcodeFunctionReturnCodes _game_session::fn_get_speech_status(int32 &result, int32 *) {
87 	// tells us if a converation is already running or not
88 
89 	result = total_convs;
90 
91 	if ((cur_id == player.Fetch_player_id()) && (player.player_status == REMORA))
92 		Fatal_error("fn_get_speech_status - player cant start conversation inside remora!");
93 
94 	if ((cur_id == player.Fetch_player_id()) && (g_oIconMenu->IsActive()))
95 		g_oIconMenu->CloseDownIconMenu();
96 
97 	if ((g_oIconMenu->IsActive()) || (player.player_status == REMORA))
98 		result = 1;
99 
100 	if ((result) && (cur_id == player.Fetch_player_id()))
101 		Tdebug("speech_check.txt", "get status");
102 
103 	return IR_CONT;
104 }
105 
fn_request_speech(int32 & result,int32 * params)106 mcodeFunctionReturnCodes _game_session::fn_request_speech(int32 &result, int32 *params) {
107 	// try to start up a conversation
108 	// there may be one or several participants who must agree to join the conversation
109 
110 	// params    0 name of scene script
111 
112 	const char *scene_script_name = (const char *)MemoryUtil::resolvePtr(params[0]);
113 
114 	Zdebug("[%s] fn_request_speech [%s]", object->GetName(), scene_script_name);
115 
116 	// there cannot be any other conversations happening - change to initial spec as it is not used and memory is required
117 	if (total_convs) {
118 		if (cur_id == player.Fetch_player_id())
119 			Tdebug("speech_check.txt", "request");
120 
121 		return IR_REPEAT; // just wait until other is done
122 	}
123 
124 	if (player.player_status == REMORA)
125 		return IR_REPEAT;
126 
127 	if ((cur_id == player.Fetch_player_id()) && (g_oIconMenu->IsActive()))
128 		g_oIconMenu->CloseDownIconMenu();
129 
130 	if ((g_oIconMenu->IsActive()) || (player.player_status == REMORA))
131 		return IR_REPEAT;
132 
133 	// not started yet
134 	S.state = __PENDING;
135 
136 	// get the system now - in case another object tries to start a conversation
137 	total_convs++; // to 1 ;)
138 
139 	// find the speech script
140 	// form name of speech script
141 	sprintf(temp_buf, "scenes::%s", scene_script_name);
142 
143 	S.script_pc = (char *)scripts->Try_fetch_item_by_name(temp_buf); // run init script
144 
145 	//	conversation script doesnt exist
146 	if (!S.script_pc)
147 		Fatal_error("object [%d] tried to start conversation script [%s] which doesnt exist", cur_id, (const char *)temp_buf);
148 
149 	// reset number of subs
150 	S.total_subscribers = 0; // everyone but us initially
151 
152 	S.current_subscribers = 0; // reset
153 
154 	for (uint32 j = 0; j < MAX_coms; j++)
155 		S.coms[j].active = FALSE8;
156 
157 	menu_number = 0; // start at menu 0
158 
159 	result = 0; // means ok
160 
161 	Set_string(scene_script_name, speech_conv_name); // from, to
162 
163 	// set player to stand frame
164 	if (cur_id == player.Fetch_player_id()) {
165 		L->cur_anim_type = __STAND;
166 		L->anim_pc = 0;
167 	}
168 
169 	return IR_CONT;
170 }
171 
fn_add_talker(int32 &,int32 * params)172 mcodeFunctionReturnCodes _game_session::fn_add_talker(int32 &, int32 *params) {
173 	// a name of a mega to join the conversation is passed
174 	// we add its id into the list of subscribers
175 
176 	// params    0   ascii name of object
177 
178 	uint32 talk_id;
179 
180 	const char *object_name = (const char *)MemoryUtil::resolvePtr(params[0]);
181 
182 	if (S.state != __PENDING)
183 		Fatal_error("fn_add_talker called but in wrong order");
184 
185 	// convert the ascii name into an object id
186 	talk_id = objects->Fetch_item_number_by_name(object_name);
187 
188 	// check for illegal object
189 	if (talk_id >= total_objects)
190 		Fatal_error("fn_add_talker finds [%s] is not a real object", object_name);
191 
192 	if (cur_id == talk_id)
193 		Fatal_error("[%s] calls fn_add_talker('%s') which isnt necessary and may cause strange lock up effects!", object_name, object_name);
194 
195 	Zdebug("talk id %d", talk_id);
196 
197 	if (talk_id != 0xffffffff) {
198 
199 		if (S.total_subscribers == MAX_people_talking)
200 			Fatal_error("fn_add_talker(%s) too many people in conversation", object_name);
201 
202 		S.subscribers_requested[S.total_subscribers] = talk_id;
203 
204 		//		increase participant count
205 		S.total_subscribers++;
206 
207 	} else {
208 		//		named object doesnt exist which is pretty serious
209 		Fatal_error("tried to add non existent object [%s] to conversation", object_name);
210 	}
211 
212 	// keep going
213 	return (IR_CONT);
214 }
215 
fn_issue_speech_request(int32 &,int32 *)216 mcodeFunctionReturnCodes _game_session::fn_issue_speech_request(int32 &, int32 *) {
217 	// once everyone required has been added to the subscriber list then we can ask each in turn if they want to join
218 	uint32 j;
219 
220 	Zdebug("issue speech request");
221 	Zdebug(" %d invitees", S.total_subscribers);
222 
223 	if (S.state != __PENDING)
224 		Fatal_error("fn_issue_speech_request called but in wrong order");
225 
226 	//	if   (S.subscribers_requested.GetNoItems())
227 	if (S.total_subscribers) {
228 		//		force each to re-run their context
229 		for (j = 0; j < S.total_subscribers; j++) {
230 			Zdebug("forcing context rerun for %d", S.subscribers_requested[j]);
231 
232 			if (S.subscribers_requested[j] >= total_objects)
233 				Fatal_error("fn_issue_speech_request has illegal object in issue list");
234 
235 			Force_context_check(S.subscribers_requested[j]);
236 			Set_objects_conversation_uid(S.subscribers_requested[j], 0); // participant id, conversation uid
237 		}
238 	}
239 
240 	// skip a cycle to allow the results to come back
241 	return (IR_STOP);
242 }
243 
fn_confirm_requests(int32 & result,int32 * params)244 mcodeFunctionReturnCodes _game_session::fn_confirm_requests(int32 &result, int32 *params) {
245 	// did everyone in this conversation subscribe?
246 	// if yes then record the owner and let the  conversation begin
247 
248 	Zdebug("fn-confirm-requests");
249 
250 	if (S.state != __PENDING)
251 		Fatal_error("fn_confirm_requests called but in wrong order");
252 
253 	// did everyone subscrive and consequently fn-converse?
254 	if (S.total_subscribers == S.current_subscribers) {
255 		// same number accepted as were requested so we're all systems go
256 
257 		Zdebug(" conversation ok");
258 
259 		S.state = __PROCESS; // set to script processing mode
260 
261 		if (S.total_subscribers > MAX_people_talking)
262 			Fatal_error("fn_confirm_requests finds too many people in conversation");
263 
264 		S.subscribers_requested[S.total_subscribers] = cur_id;
265 
266 		// increase participant count
267 		S.total_subscribers++;
268 
269 		// set our conversation uid as we are about to become a lowly subscriber
270 		Set_objects_conversation_uid(cur_id, 0); // participant id, conversation uid
271 
272 		L->do_not_disturb++; // stop events
273 
274 		conv_focus = 0; // us and always
275 
276 		result = TRUE8;
277 
278 		if (fn_stop_sting(result, params) == IR_REPEAT)
279 			return IR_REPEAT;
280 	} else {
281 		// no
282 		Zdebug(" conversation not fully subscribed");
283 
284 		End_conversation(CONV_ID);
285 
286 		result = FALSE8;
287 	}
288 
289 	// and onward
290 	return (IR_CONT);
291 }
292 
fn_anon_speech_invite(int32 & result,int32 *)293 mcodeFunctionReturnCodes _game_session::fn_anon_speech_invite(int32 &result, int32 *) {
294 	// general context script check for being a participant in a conversation
295 	// the idea is that if we are in a conversation then we continue with it regardless
296 
297 	// just having this in the logic context means no conversation can be interupted by another
298 
299 	Zdebug("check speech invite");
300 
301 	if (L->conversation_uid != NO_SPEECH_REQUEST) {
302 		// yes, a request is pending
303 		// if not on a floor - perhaps on a ladder or stair - then mask the request
304 		if (M) {
305 			if (!floor_def->On_a_floor(M)) {
306 				Message_box("%d", player.player_status);
307 				L->conversation_uid = NO_SPEECH_REQUEST;
308 				result = 0; // no request
309 				return IR_CONT;
310 			}
311 		}
312 
313 		L->do_not_disturb++; // stop events
314 		result = 1;
315 		return IR_CONT;
316 	}
317 
318 	// no
319 	result = 0;
320 	return (IR_CONT);
321 }
322 
fn_kill_conversations(int32 &,int32 *)323 mcodeFunctionReturnCodes _game_session::fn_kill_conversations(int32 &, int32 *) {
324 	// current conversations end - allowing a new one to interupt
325 
326 	// if there are conversations on going them kill em (it)
327 	if (total_convs)
328 		End_conversation(CONV_ID);
329 
330 	return IR_STOP;
331 }
332 
fn_converse(int32 &,int32 *)333 mcodeFunctionReturnCodes _game_session::fn_converse(int32 &, int32 *) {
334 	// mega calls this to subscribe to a conversation
335 	// we will be told when to quit though
336 
337 	uint32 j;
338 	int32 result;
339 	int32 params;
340 	mcodeFunctionReturnCodes ret;
341 
342 	Zdebug("fn_converse [%s] - uid %d", object->GetName(), L->conversation_uid);
343 
344 	if (L->conversation_uid == NO_SPEECH_REQUEST) {
345 		//		conversation has ended!
346 
347 		L->do_not_disturb--; // restart events
348 
349 		//		continue and fall off script letting us re-run our logic context
350 		return (IR_CONT);
351 	}
352 
353 	// re-register
354 	speech_info[L->conversation_uid].current_subscribers++;
355 
356 	// if player then update buttons and check for skip speech
357 	if (cur_id == player.Fetch_player_id()) {
358 		//		check keys/pads/etc. to see what the user is trying to do
359 		player.Update_input_state();
360 
361 		if (((++no_click_zone) > 2)) {
362 			if ((player.cur_state.IsButtonSet(__INTERACT)) && (!player.interact_lock) && (speech_info[CONV_ID].state == __SAYING)) {
363 				player.interact_lock = TRUE8;
364 
365 				speech_info[CONV_ID].count = 0;
366 				CancelSpeechPlayback();
367 
368 			} else if (!player.cur_state.IsButtonSet(__INTERACT)) {
369 				player.interact_lock = FALSE8;
370 			}
371 		}
372 	}
373 
374 	//	check for instructions
375 	for (j = 0; j < MAX_coms; j++) // loop to number of commands
376 		if (speech_info[L->conversation_uid].coms[j].active == TRUE8) // this slot?
377 			if (speech_info[L->conversation_uid].coms[j].id == cur_id) { // is the command for us
378 				switch (speech_info[L->conversation_uid].coms[j].command) {
379 				case __FACE_OBJECT:
380 					Zdebug("face object [com %d] - [param %d]", j, speech_info[L->conversation_uid].coms[j].param1);
381 
382 					if (!speech_face_object(speech_info[L->conversation_uid].coms[j].param1)) {
383 						speech_info[L->conversation_uid].coms[j].active = FALSE8;
384 					}
385 					break;
386 
387 				case __PLAY_GENERIC_ANIM:
388 					params = MemoryUtil::encodePtr((uint8 *)speech_info[L->conversation_uid].coms[j].str_param1);
389 					ret = fn_play_generic_anim(result, &params);
390 					if (ret == IR_CONT) {
391 						speech_info[L->conversation_uid].coms[j].active = FALSE8;
392 					}
393 					break;
394 
395 				case __PLAY_CUSTOM_ANIM:
396 					params = MemoryUtil::encodePtr((uint8 *)speech_info[L->conversation_uid].coms[j].str_param1);
397 					ret = fn_easy_play_custom_anim(result, &params);
398 					if (ret == IR_CONT) {
399 						Reset_cur_megas_custom_type();
400 						speech_info[L->conversation_uid].coms[j].active = FALSE8;
401 					}
402 					break;
403 
404 				case __REVERSE_CUSTOM_ANIM:
405 					params = MemoryUtil::encodePtr((uint8 *)speech_info[L->conversation_uid].coms[j].str_param1);
406 					ret = fn_reverse_custom_anim(result, &params);
407 					if (ret == IR_CONT) {
408 						Reset_cur_megas_custom_type();
409 						speech_info[L->conversation_uid].coms[j].active = FALSE8;
410 					}
411 					break;
412 
413 				default:
414 					Fatal_error("illegal speech com instruction");
415 					break;
416 				}
417 
418 				// do the first pending com - not any further ones which may be queued
419 				return (IR_REPEAT); // keep returning until we're released
420 			}
421 
422 	return (IR_REPEAT); // keep returning until we're released
423 }
424 
Service_speech()425 void _game_session::Service_speech() {
426 	// the system runs all speech scripts
427 
428 	c_game_object *speech_object;
429 	uint32 ret;
430 
431 	// anything going on?
432 	if (!total_convs)
433 		return; // none
434 
435 	// not started yet
436 	if (S.state == __PENDING)
437 		return;
438 
439 	if (speech_info[CONV_ID].total_subscribers > speech_info[CONV_ID].current_subscribers) {
440 		// oh no, conversation has to end as someones dropped out - perhaps they were shot, blown up or distracted
441 
442 		// in-case someone is mid sentence
443 		CancelSpeechPlayback();
444 
445 		End_conversation(CONV_ID);
446 		return;
447 	}
448 
449 	// reset sub total for next cycles check
450 	speech_info[CONV_ID].current_subscribers = 0;
451 
452 	// process the conversation
453 	switch (speech_info[CONV_ID].state) {
454 	case __PROCESS: // run the script
455 		// get the dummy speech object
456 		speech_object = (c_game_object *)objects->Fetch_item_by_name("scenes");
457 		cur_id = objects->Fetch_item_number_by_name("scenes");
458 		L = logic_structs[cur_id];
459 		I = 0;
460 		M = 0;
461 
462 		// run the script
463 		ret = RunScript(const_cast<const char *&>(speech_info[CONV_ID].script_pc), speech_object);
464 
465 		if (ret == 1) {
466 			// speech script has finished
467 			End_conversation(CONV_ID);
468 			return;
469 		}
470 
471 		break;
472 
473 	// on psx this waits until the speech is started then sets the timer for stopping it
474 	case __WAITING_TO_SAY:
475 	case __SAYING: // someone is talking
476 
477 		// set state to saying if we're actually playing
478 		if (IS_SPEECH_STARTED)
479 			speech_info[CONV_ID].state = __SAYING;
480 
481 		// count down the timer
482 		// back into process mode when we're done
483 		if (!speech_info[CONV_ID].count) { // done
484 			speech_info[CONV_ID].current_talker = -1; // nobody talking now
485 			speech_info[CONV_ID].state = __PROCESS;
486 
487 			// if  this conversation has the focus then kill the text bloc
488 			if (CONV_ID == conv_focus) {
489 				// delete    text_bloc;
490 				text_speech_bloc->please_render = FALSE8;
491 			}
492 		} else
493 			speech_info[CONV_ID].count -= GetCountReduction();
494 
495 		break;
496 
497 	default:
498 		Fatal_error("illegal instruction found in conversation");
499 		break;
500 	}
501 }
502 
fn_speak(int32 &,int32 * params)503 mcodeFunctionReturnCodes _game_session::fn_speak(int32 &, int32 *params) {
504 	// current conversation
505 
506 	// params    0   ascii name of person
507 	//			1  ascii text line name
508 
509 	_TSrtn ret_code;
510 	uint32 speaker_id;
511 	PXvector pos;
512 	// yesno
513 	bool8 resu = FALSE8;
514 
515 	// screen pos
516 	PXvector filmpos;
517 
518 	char error[] = "text file out of date!";
519 
520 	char *ascii;
521 
522 	const char *person_name = (const char *)MemoryUtil::resolvePtr(params[0]);
523 	const char *text_label = (const char *)MemoryUtil::resolvePtr(params[1]);
524 
525 	// wait until last line is finished
526 	if (IS_SPEECH_ALREADY_PLAYING)
527 		return IR_REPEAT;
528 
529 	// Make the hash from the label
530 	uint32 speechHash = HashString(text_label);
531 
532 	if (!PreloadSpeech(speechHash))
533 		return IR_REPEAT;
534 
535 	if (text_speech_bloc->please_render == TRUE8)
536 		Fatal_error("fn_speak - text block already exists!");
537 
538 	Zdebug("fn_speak [%s] [%s]", person_name, text_label);
539 
540 	// work out position using speakers position
541 	// fetch the speaker
542 	speaker_id = objects->Fetch_item_number_by_name(person_name);
543 	if (speaker_id == PX_LINKED_DATA_FILE_ERROR)
544 		Fatal_error("Unable to find object ID for [%s] in fn_speak()", person_name);
545 
546 	if (text) {
547 		// retrieve text line
548 		ascii = (char *)text->Try_fetch_item_by_name(text_label);
549 
550 		if (!ascii)
551 			ascii = error;
552 
553 		else if (!*(ascii)) // null terminated string
554 			ascii = error;
555 
556 	} else
557 		ascii = error;
558 
559 	// work out length
560 	Zdebug("[%s]", ascii);
561 
562 	{
563 		_TSparams *text_params;
564 		// build text block
565 		text_params = text_speech_bloc->GetParams();
566 		text_speech_bloc->please_render = TRUE8; // exists to draw
567 
568 		text_params->textLine = (uint8 *)ascii; // print id instead of real text for now
569 		// set font
570 		text_params->fontResource = (const char *)speech_font_one; //&test_font[0];
571 
572 		// Jake for clustering
573 		text_params->fontResource_hash = speech_font_one_hash;
574 
575 		text_params->maxWidth = TEXT_MAX_WIDTH;
576 		text_params->lineSpacing = 0;
577 		text_params->charSpacing = 0;
578 		text_params->errorChecking = 1; // enable error checking during AnalyseSentence
579 
580 		// Set the colour for the speech.
581 		if (logic_structs[speaker_id]->mega) {
582 			// Each mega has their own speech colour.
583 			SetTextColour(logic_structs[speaker_id]->mega->speech_red, logic_structs[speaker_id]->mega->speech_green, logic_structs[speaker_id]->mega->speech_blue);
584 		} else {
585 			// Objects all share one value.
586 			SetTextColour(voice_over_red, voice_over_green, voice_over_blue);
587 		}
588 
589 		ret_code = text_speech_bloc->MakeTextSprite();
590 
591 		if (ret_code != TS_OK)
592 			Fatal_error("line [%s] text formating is illegal [%s]", text_label, ascii);
593 
594 		if (g_px->display_mode == THREED) {
595 
596 			// get coords
597 			if (logic_structs[speaker_id]->image_type == PROP) {
598 				// has prop got coords?
599 				if (logic_structs[speaker_id]->prop_coords_set == TRUE8) {
600 					pos.x = logic_structs[speaker_id]->prop_xyz.x;
601 					pos.y = logic_structs[speaker_id]->prop_xyz.y; // talks over head rather than from the feet
602 					pos.z = logic_structs[speaker_id]->prop_xyz.z;
603 
604 					// setup camera
605 					PXcamera &camera = GetCamera();
606 
607 					// compute screen coord
608 
609 					PXWorldToFilm(pos, camera, resu, filmpos);
610 
611 					if (!resu)
612 						Zdebug(" position off film?");
613 
614 					text_speech_bloc->GetRenderCoords((int32)(filmpos.x + (SCREEN_WIDTH / 2)), (int32)((SCREEN_DEPTH / 2) - (filmpos.y)), PIN_AT_CENTRE_OF_BASE,
615 					                                  5); // margin
616 				} else { // prop with no coords prints along bottom of screen
617 					text_speech_bloc->renderX = 20;
618 					text_speech_bloc->renderY = 400;
619 				}
620 			} else {
621 				pos.x = logic_structs[speaker_id]->mega->actor_xyz.x;
622 				pos.y = logic_structs[speaker_id]->mega->actor_xyz.y + 200; // talks over head rather than from the feet
623 				pos.z = logic_structs[speaker_id]->mega->actor_xyz.z;
624 
625 				// setup camera
626 				PXcamera &camera = GetCamera();
627 
628 				// compute screen coord
629 				PXWorldToFilm(pos, camera, resu, filmpos);
630 
631 				if (!resu)
632 					Zdebug(" position off film?");
633 
634 				text_speech_bloc->GetRenderCoords((int32)(filmpos.x + (SCREEN_WIDTH / 2)), (int32)((SCREEN_DEPTH / 2) - (filmpos.y)), PIN_AT_CENTRE_OF_BASE,
635 				                                  5); // margin
636 			}
637 
638 		} else { // fixed position in nethack mode
639 			text_speech_bloc->renderX = 20;
640 			text_speech_bloc->renderY = 400;
641 		}
642 
643 		speech_info[CONV_ID].count = SayLineOfSpeech(speechHash);
644 
645 
646 		if (!speech_info[CONV_ID].count)
647 			Fatal_error("Speech xa file is 0 game cycles see, int32");
648 	}
649 
650 	no_click_zone = 0; // cant click past for specified period
651 
652 	// set conv mode
653 	speech_info[CONV_ID].current_talker = speaker_id;
654 	speech_info[CONV_ID].state = __WAITING_TO_SAY;
655 
656 	return (IR_STOP); // drop out
657 }
658 
fn_speech_colour(int32 &,int32 * params)659 mcodeFunctionReturnCodes _game_session::fn_speech_colour(int32 &, int32 *params) {
660 	Zdebug("fn_speech_colour( %d, %d, %d ) called by object ID %d", (int32)params[0], (int32)params[1], (int32)params[2], cur_id);
661 
662 	// Must be a mega calling this.
663 	if (!logic_structs[cur_id]->mega)
664 		Fatal_error("Non-mega %d called fn_speech_colour()", cur_id);
665 
666 	// Set the values in the mega's structure.
667 	logic_structs[cur_id]->mega->speech_red = (uint8)params[0];
668 	logic_structs[cur_id]->mega->speech_green = (uint8)params[1];
669 	logic_structs[cur_id]->mega->speech_blue = (uint8)params[2];
670 
671 	// Calling script can continue.
672 	return (IR_CONT);
673 }
674 
fn_set_voice_over_colour(int32 &,int32 * params)675 mcodeFunctionReturnCodes _game_session::fn_set_voice_over_colour(int32 &, int32 *params) {
676 	// This colour is used to display voice over text (normally player's speech colour).
677 	voice_over_red = (uint8)params[0];
678 	voice_over_green = (uint8)params[1];
679 	voice_over_blue = (uint8)params[2];
680 
681 	// Calling script can continue.
682 	return (IR_CONT);
683 }
684 
fn_default_voice_over_colour(int32 &,int32 *)685 mcodeFunctionReturnCodes _game_session::fn_default_voice_over_colour(int32 &, int32 *) {
686 	// This colour is used to display voice over text (normally player's speech colour).
687 	voice_over_red = VOICE_OVER_DEFAULT_RED;
688 	voice_over_green = VOICE_OVER_DEFAULT_GREEN;
689 	voice_over_blue = VOICE_OVER_DEFAULT_BLUE;
690 
691 	// Calling script can continue.
692 	return (IR_CONT);
693 }
694 
695 extern int32 globalCharSpacing;
696 
697 // This function computes the formatting of a paragraph of Remora text without going as far as making
698 // the sprite.  This is so the Remora can decide how it is going to format text before it tries to draw
699 // it.
Format_remora_text(const char * pcText,int32 nLineSpacing,int32 nCharSpacing,uint32 nMaxWidth)700 void _game_session::Format_remora_text(const char *pcText, int32 nLineSpacing, int32 nCharSpacing, uint32 nMaxWidth) {
701 	_TSrtn eErrorCode;
702 	_TSparams *psTextParams;
703 
704 	// Get the pointer to the parameter block that needs to be filled in before the functions of class text_sprite
705 	// can do their stuff.
706 	psTextParams = text_bloc->GetParams();
707 
708 	// Set the parameters required by text_sprite::AnalyseSentence().
709 	psTextParams->textLine = (uint8 *)const_cast<char *>(pcText);
710 	psTextParams->fontResource = (const char *)remora_font;
711 	psTextParams->fontResource_hash = remora_font_hash;
712 	psTextParams->maxWidth = nMaxWidth;
713 	psTextParams->lineSpacing = nLineSpacing;
714 	psTextParams->charSpacing = nCharSpacing + globalCharSpacing;
715 	psTextParams->errorChecking = 1;
716 
717 	// Now I can call AnalyseSentence() to work out how the text will be formatted when it is drawn.
718 	eErrorCode = text_bloc->AnalyseSentence();
719 
720 	// Check there was no error.
721 	if (eErrorCode != TS_OK) {
722 		Fatal_error("text_sprite::AnalyseSentence( '%s' ) failed with code %d in Format_remora_text", pcText, (int32)eErrorCode);
723 	}
724 }
725 
Create_remora_text(uint32 x,uint32 y,const char * ascii,int32 margin,_pin_position pin_pos,int32 lineSpacing,int32 charSpacing,uint32 maxWidth,bool8 analysisAlreadyDone,int32 stopAtLine)726 void _game_session::Create_remora_text(uint32 x, uint32 y, const char *ascii,
727 									   int32 margin, _pin_position pin_pos, int32 lineSpacing, int32 charSpacing,
728 									   uint32 maxWidth,
729 									   bool8 analysisAlreadyDone,
730 									   int32 stopAtLine) {
731 	bool8 bRemoraLeftFormatting;
732 
733 	Zdebug("Create_remora_text %d,%d [%s]", x, y, ascii);
734 
735 	// if we are in focus then build a text bloc
736 	// build text block
737 	_TSparams *text_params;
738 	text_params = text_bloc->GetParams();
739 	text_params->textLine = (uint8 *)const_cast<char *>(ascii); // print id instead of real text for now
740 	// set font
741 	text_params->fontResource = (const char *)remora_font;
742 	// Jake for clustering
743 	text_params->fontResource_hash = remora_font_hash;
744 
745 	text_params->maxWidth = maxWidth;
746 	text_params->lineSpacing = lineSpacing;
747 	text_params->charSpacing = charSpacing + globalCharSpacing;
748 	text_params->errorChecking = 1; // enable error checking during AnalyseSentence
749 
750 	// See if we need to trigger the special left formatting that only the Remora uses.
751 	if (pin_pos == PIN_AT_TOP_LEFT)
752 		bRemoraLeftFormatting = TRUE8;
753 	else
754 		bRemoraLeftFormatting = FALSE8;
755 
756 	int32 oldFlag = g_px->speechLineNumbers;
757 
758 	// Turn off line numbers for non-spoken lines of dialogue
759 	if (*ascii == TS_NON_SPOKEN_LINE)
760 		g_px->speechLineNumbers = 0;
761 
762 	text_bloc->MakeTextSprite(analysisAlreadyDone, stopAtLine, bRemoraLeftFormatting);
763 	g_px->speechLineNumbers = (uint8)oldFlag;
764 	text_bloc->GetRenderCoords(x, y, pin_pos, margin);
765 
766 	text_bloc->please_render = TRUE8;
767 }
768 
Kill_remora_text()769 void _game_session::Kill_remora_text() {
770 
771 	if (!text_bloc->please_render)
772 		Fatal_error("Kill_remora_text - no text block exists!");
773 
774 	text_bloc->please_render = FALSE8;
775 }
776 
speak_object_face_object(int32 &,int32 * params)777 mcodeFunctionReturnCodes _game_session::speak_object_face_object(int32 &, int32 *params) {
778 	// first person turns to face the other
779 
780 	// params    0   ascii name of person to turn
781 	//			1  ascii name of person to turn to
782 
783 	uint32 speaker_id;
784 	uint32 tar_id;
785 	uint32 com_no = 0;
786 
787 	const char *speaker_name = (const char *)MemoryUtil::resolvePtr(params[0]);
788 	const char *target_name = (const char *)MemoryUtil::resolvePtr(params[1]);
789 
790 	// fetch the speaker
791 	speaker_id = objects->Fetch_item_number_by_name(speaker_name);
792 
793 	// fetch target
794 	tar_id = objects->Fetch_item_number_by_name(target_name);
795 
796 	// find next com slot
797 	while ((speech_info[0].coms[com_no].active == TRUE8) && (speech_info[0].coms[com_no].id != speaker_id))
798 		com_no++;
799 
800 	speech_info[CONV_ID].coms[com_no].active = TRUE8;
801 
802 	// set the commands owner id
803 	speech_info[CONV_ID].coms[com_no].id = speaker_id;
804 
805 	// set command type
806 	speech_info[CONV_ID].coms[com_no].command = __FACE_OBJECT;
807 
808 	// set the target id parameter
809 	speech_info[CONV_ID].coms[com_no].param1 = tar_id;
810 
811 	return (IR_CONT);
812 }
813 
speak_play_generic_anim(int32 &,int32 * params)814 mcodeFunctionReturnCodes _game_session::speak_play_generic_anim(int32 &, int32 *params) {
815 	// object plays a generic animation
816 	// links to fn_play_generic_anim
817 
818 	// params    0   ascii name of person to animate
819 	//			1  ascii name of generic animation
820 
821 	uint32 speaker_id;
822 	uint32 com_no = 0;
823 
824 	const char *person_name = (const char *)MemoryUtil::resolvePtr(params[0]);
825 	const char *anim_name = (const char *)MemoryUtil::resolvePtr(params[1]);
826 
827 	Zdebug("speak_play_generic_anim [%s] to face [%s]", person_name, anim_name);
828 
829 	// fetch the speaker
830 	speaker_id = objects->Fetch_item_number_by_name(person_name);
831 
832 	while ((speech_info[0].coms[com_no].active == TRUE8) && (speech_info[0].coms[com_no].id != speaker_id))
833 		com_no++;
834 
835 	if (com_no == MAX_coms)
836 		Fatal_error("too many speech commands");
837 
838 	speech_info[CONV_ID].coms[com_no].active = TRUE8;
839 
840 	// set the commands owner id
841 	speech_info[CONV_ID].coms[com_no].id = speaker_id;
842 
843 	// set command type
844 	speech_info[CONV_ID].coms[com_no].command = __PLAY_GENERIC_ANIM;
845 
846 	// set the target id parameter
847 	Set_string(anim_name, speech_info[CONV_ID].coms[com_no].str_param1);
848 
849 	return (IR_CONT);
850 }
851 
speak_set_custom(int32 &,int32 * params)852 mcodeFunctionReturnCodes _game_session::speak_set_custom(int32 &, int32 *params) {
853 	// set the custom set for a character
854 
855 	// params    0   ascii name of person to animate
856 	//			1  ascii name of custom set
857 
858 	int32 speaker_id;
859 
860 	const char *person_name = (const char *)MemoryUtil::resolvePtr(params[0]);
861 	const char *custom_name = (const char *)MemoryUtil::resolvePtr(params[1]);
862 
863 	// fetch the speaker
864 	speaker_id = objects->Fetch_item_number_by_name(person_name);
865 
866 	if (speaker_id == -1)
867 		Fatal_error("speak_set_custom cant find object [%s]", person_name);
868 
869 	Set_string(custom_name, logic_structs[speaker_id]->mega->custom_set, MAX_CUSTOM_NAME_LENGTH);
870 	logic_structs[speaker_id]->mega->custom = TRUE8;
871 	logic_structs[speaker_id]->looping = 0; // reset
872 
873 	return IR_CONT;
874 }
875 
speak_preload_custom_anim(int32 &,int32 * params)876 mcodeFunctionReturnCodes _game_session::speak_preload_custom_anim(int32 &, int32 *params) {
877 	// set a custom anim preloading
878 
879 	// params    0   name of mega
880 	//			1  name of anim
881 
882 	int32 speaker_id;
883 
884 	const char *mega_name = (const char *)MemoryUtil::resolvePtr(params[0]);
885 	/*const char *anim_name = (const char *)*/ MemoryUtil::resolvePtr(params[1]);
886 
887 #define II logic_structs[speaker_id]->voxel_info
888 #define MM logic_structs[speaker_id]->mega
889 
890 	// fetch the speaker
891 	speaker_id = objects->Fetch_item_number_by_name(mega_name);
892 
893 	// build that name
894 	II->Init_custom_animation(mega_name);
895 
896 	// now set prebuilt flag
897 	II->has_custom_path_built = FALSE8;
898 
899 	// start preloading
900 	rs_anims->Res_open(II->get_anim_name(__NON_GENERIC), II->anim_name_hash[__NON_GENERIC], II->base_path, II->base_path_hash); //
901 
902 	MM->custom = FALSE8;
903 
904 	return IR_CONT;
905 }
906 
speak_play_custom_anim(int32 &,int32 * params)907 mcodeFunctionReturnCodes _game_session::speak_play_custom_anim(int32 &, int32 *params) {
908 	// object plays a custom animation
909 	// links to fn_play_custom_anim
910 
911 	// params    0   ascii name of person to animate
912 	//			1  ascii name of custom animation
913 
914 	uint32 speaker_id;
915 	uint32 com_no = 0;
916 
917 	const char *speaker_name = (const char *)MemoryUtil::resolvePtr(params[0]);
918 	const char *anim_name = (const char *)MemoryUtil::resolvePtr(params[1]);
919 
920 	// fetch the speaker
921 	speaker_id = objects->Fetch_item_number_by_name(speaker_name);
922 
923 	while ((speech_info[0].coms[com_no].active == TRUE8) && (speech_info[0].coms[com_no].id != speaker_id))
924 		com_no++;
925 
926 	if (com_no == MAX_coms)
927 		Fatal_error("too many speech commands");
928 
929 	speech_info[CONV_ID].coms[com_no].active = TRUE8;
930 
931 	// set the commands owner id
932 	speech_info[CONV_ID].coms[com_no].id = speaker_id;
933 
934 	// set command type
935 	speech_info[CONV_ID].coms[com_no].command = __PLAY_CUSTOM_ANIM;
936 
937 	// set the target id parameter
938 	Set_string(anim_name, speech_info[CONV_ID].coms[com_no].str_param1);
939 
940 	return (IR_STOP);
941 }
942 
speak_reverse_custom_anim(int32 &,int32 * params)943 mcodeFunctionReturnCodes _game_session::speak_reverse_custom_anim(int32 &, int32 *params) {
944 	// object REVERSE plays a custom animation
945 
946 	// params    0   ascii name of person to animate
947 	//			1  ascii name of custom animation
948 
949 	uint32 speaker_id;
950 	uint32 com_no = 0;
951 
952 	const char *speaker_name = (const char *)MemoryUtil::resolvePtr(params[0]);
953 	const char *anim_name = (const char *)MemoryUtil::resolvePtr(params[1]);
954 
955 	// fetch the speaker
956 	speaker_id = objects->Fetch_item_number_by_name(speaker_name);
957 
958 	while ((speech_info[0].coms[com_no].active == TRUE8) && (speech_info[0].coms[com_no].id != speaker_id))
959 		com_no++;
960 
961 	if (com_no == MAX_coms)
962 		Fatal_error("too many speech commands");
963 
964 	speech_info[CONV_ID].coms[com_no].active = TRUE8;
965 
966 	// set the commands owner id
967 	speech_info[CONV_ID].coms[com_no].id = speaker_id;
968 
969 	// set command type
970 	speech_info[CONV_ID].coms[com_no].command = __REVERSE_CUSTOM_ANIM;
971 
972 	// set the target id parameter
973 	Set_string(anim_name, speech_info[CONV_ID].coms[com_no].str_param1);
974 
975 	return (IR_CONT);
976 }
977 
Exit_speech(uint32 id)978 void _game_session::Exit_speech(uint32 id) {
979 	// if we are talking then stop the sample
980 	// called when this id takes a gun shot
981 	// will drop out of speech and speech wav will stop playing if this id is currently speaking
982 
983 	if (logic_structs[id]->conversation_uid != NO_SPEECH_REQUEST) { // is talking
984 		if (speech_info[CONV_ID].current_talker == (int32)id)
985 			StopSpeechPlayback(); // target is talking
986 
987 		//		will drop out
988 		logic_structs[id]->conversation_uid = NO_SPEECH_REQUEST;
989 	}
990 }
991 
End_conversation(uint32 uid)992 void _game_session::End_conversation(uint32 uid) {
993 	// clean up conversation
994 
995 	uint32 j;
996 
997 	// decrease number of active conversations (from 1 to 0)
998 	total_convs--;
999 
1000 	//	clean up and free the objects
1001 	//	reset everyones conversation_uid if they are still registed as being in this conversation
1002 	if (speech_info[uid].total_subscribers)
1003 		for (j = 0; j < speech_info[uid].total_subscribers; j++) {
1004 			if (speech_info[uid].subscribers_requested[j] == player.Fetch_player_id()) {
1005 				if (logic_structs[player.Fetch_player_id()]->conversation_uid !=
1006 				    NO_SPEECH_REQUEST) { // dont reset player if he never joined - for request failures on ladders, etc.
1007 					player.Pop_player_stat(); // stood or aiming, etc
1008 				}
1009 			}
1010 
1011 			if (speech_info[uid].subscribers_requested[j] >= total_objects)
1012 				Fatal_error("End_conversation find illegal id %d - total = %d", speech_info[uid].subscribers_requested[j],
1013 				            speech_info[uid].total_subscribers);
1014 
1015 			Set_objects_conversation_uid(speech_info[uid].subscribers_requested[j], NO_SPEECH_REQUEST); // participant id, conversation uid   - release the object
1016 		}
1017 
1018 	conv_focus = 0; // no one
1019 
1020 	if (text_speech_bloc->please_render == TRUE8) {
1021 		Zdebug("removing text bloc");
1022 		text_speech_bloc->please_render = FALSE8;
1023 	}
1024 
1025 	// end music
1026 	int32 r, p[1];
1027 
1028 	speak_end_music(r, p);
1029 
1030 	// reset list of subscribers
1031 	speech_info[uid].total_subscribers = 0;
1032 }
1033 
speak_wait_for_everyone(int32 &,int32 *)1034 mcodeFunctionReturnCodes _game_session::speak_wait_for_everyone(int32 &, int32 *) {
1035 	// waits until all pending coms are done
1036 
1037 	// are there any coms pending?
1038 
1039 	uint32 j;
1040 
1041 	for (j = 0; j < MAX_coms; j++)
1042 		if (speech_info[0].coms[j].active == TRUE8)
1043 			return (IR_REPEAT); // yes
1044 
1045 	// no
1046 	return (IR_CONT);
1047 }
1048 
1049 // ICON_LIST_MANAGER maximum must be this number +2 (inventory + remora)
1050 #define MAX_number_menus 6
1051 
1052 char menu_name_list[MAX_number_menus][4];
1053 
1054 bool8 choosing[10];
1055 uint32 item_count[10];
1056 
speak_new_menu(int32 &,int32 *)1057 mcodeFunctionReturnCodes _game_session::speak_new_menu(int32 &, int32 *) {
1058 	// create a new menu
1059 
1060 	menu_number++;
1061 	if (menu_number >= MAX_number_menus) {
1062 		Fatal_error("too many menus MAX %d", MAX_number_menus);
1063 	}
1064 
1065 	g_oIconListManager->ResetList(menu_name_list[menu_number]);
1066 
1067 	sprintf(menu_name_list[menu_number], "m%02d", menu_number); // create a unique name
1068 	choosing[menu_number] = FALSE8;
1069 	item_count[menu_number] = 0;
1070 
1071 	return (IR_CONT);
1072 }
1073 
speak_close_menu(int32 &,int32 *)1074 mcodeFunctionReturnCodes _game_session::speak_close_menu(int32 &, int32 *) {
1075 	menu_number--;
1076 
1077 	return (IR_CONT);
1078 }
1079 
speak_add_chooser_icon(int32 &,int32 * params)1080 mcodeFunctionReturnCodes _game_session::speak_add_chooser_icon(int32 &, int32 *params) {
1081 	// add icon to speech menu
1082 
1083 	// params    0   ascii name of icon
1084 	const char *icon_name = (const char *)MemoryUtil::resolvePtr(params[0]);
1085 
1086 	g_oIconListManager->AddIconToList(menu_name_list[menu_number], icon_name);
1087 
1088 	item_count[menu_number]++;
1089 
1090 	return (IR_CONT);
1091 }
1092 
speak_add_special_chooser_icon(int32 &,int32 * params)1093 mcodeFunctionReturnCodes _game_session::speak_add_special_chooser_icon(int32 &, int32 *params) {
1094 	// add icon to speech menu
1095 	// these icons dont count as items that must be selected to finish with the menu
1096 
1097 	// params    0   ascii name of icon
1098 	const char *icon_name = (const char *)MemoryUtil::resolvePtr(params[0]);
1099 
1100 	Zdebug("speak_add_special_chooser_icon [%s]", icon_name);
1101 
1102 	g_oIconListManager->AddIconToList(menu_name_list[menu_number], icon_name);
1103 
1104 	return (IR_CONT);
1105 }
1106 
speak_user_chooser(int32 &,int32 *)1107 mcodeFunctionReturnCodes _game_session::speak_user_chooser(int32 &, int32 *) {
1108 	// wait for user to choose an option
1109 
1110 	_input *state;
1111 
1112 	Zdebug("user chooser");
1113 
1114 	// check keys/pads/etc. to see what the user is trying to do
1115 	player.Update_input_state();
1116 
1117 	state = player.Fetch_input_state();
1118 
1119 	// first time in bring up the new menu
1120 	if (choosing[menu_number] == FALSE8) {
1121 		Zdebug("activating menu");
1122 
1123 		g_oIconListManager->ActivateIconMenu(menu_name_list[menu_number], FALSE8, FALSE8);
1124 		Zdebug("~activating menu");
1125 		choosing[menu_number] = TRUE8;
1126 
1127 		player.Push_control_mode(ACTOR_RELATIVE);
1128 	}
1129 
1130 	// safe to do it here as menu has been setup
1131 	g_oIconListManager->CycleInventoryLogic(*state);
1132 
1133 	if (g_oIconListManager->ItemHeld()) {
1134 		Zdebug("selected");
1135 		choosing[menu_number] = FALSE8; // reset
1136 		player.interact_lock = TRUE8;
1137 
1138 		player.Pop_control_mode();
1139 
1140 		return (IR_CONT);
1141 	}
1142 
1143 	return (IR_REPEAT);
1144 }
1145 
speak_chosen(int32 & result,int32 * params)1146 mcodeFunctionReturnCodes _game_session::speak_chosen(int32 &result, int32 *params) {
1147 	// is the named icon the one that was chosen
1148 
1149 	const char *icon_name = (const char *)MemoryUtil::resolvePtr(params[0]);
1150 
1151 	if (g_oIconListManager->Holding(icon_name)) {
1152 		item_count[menu_number]--;
1153 		g_oIconListManager->RemoveIconFromList(menu_name_list[menu_number], icon_name);
1154 		g_oIconListManager->Drop();
1155 		result = TRUE8;
1156 	} else {
1157 		//		no
1158 		result = FALSE8;
1159 	}
1160 
1161 	return (IR_CONT);
1162 }
1163 
speak_menu_still_active(int32 & result,int32 *)1164 mcodeFunctionReturnCodes _game_session::speak_menu_still_active(int32 &result, int32 *) {
1165 	// says if menu is still active - i.e user hasnt chosen a quit icon
1166 
1167 	result = TRUE8;
1168 
1169 	return (IR_CONT);
1170 }
1171 
speak_menu_choices_remain(int32 & result,int32 *)1172 mcodeFunctionReturnCodes _game_session::speak_menu_choices_remain(int32 &result, int32 *) {
1173 	// says if menu still has items left to choose
1174 
1175 	result = item_count[menu_number];
1176 
1177 	Zdebug("speak_menu_choices_remain reports %d options remain", result);
1178 
1179 	return (IR_CONT);
1180 }
1181 
speak_end_conversation(int32 &,int32 *)1182 mcodeFunctionReturnCodes _game_session::speak_end_conversation(int32 &, int32 *) {
1183 	// kill current conversation at user request
1184 	// this will be called via Service_speech
1185 
1186 	End_conversation(CONV_ID);
1187 
1188 	return (IR_STOP);
1189 }
1190 
speak_end_menu(int32 &,int32 *)1191 mcodeFunctionReturnCodes _game_session::speak_end_menu(int32 &, int32 *) {
1192 	// end the current menu
1193 
1194 	// only destroy it if there are some options left
1195 	if (item_count[menu_number])
1196 		g_oIconListManager->DestroyList(menu_name_list[menu_number]);
1197 
1198 	// zero the items left count so that speak_menu_choices_remain will cancel the outer while loop
1199 	item_count[menu_number] = 0;
1200 
1201 	return (IR_CONT);
1202 }
1203 
1204 // Works out how int32 some text should be displayed to allow adequate time for it to be read.  We might need to
1205 // tinker with the formula in here 'till we get it right.
Get_reading_time(const char * pcString)1206 uint32 Get_reading_time(const char *pcString) {
1207 	uint32 nPos = 0;
1208 	uint32 nSpaceCount = 0;
1209 
1210 	// Base it on the wordcount.
1211 	do {
1212 		if (pcString[nPos] == ' ')
1213 			++nSpaceCount;
1214 
1215 	} while (pcString[nPos++] != '\0');
1216 
1217 	// Fixed 1/2 second plus 1/12 second for each word.
1218 	return (12 + (nSpaceCount * 2));
1219 
1220 } // end Get_reading_time()
1221 
1222 } // End of namespace ICB
1223