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, ¶ms);
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, ¶ms);
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, ¶ms);
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