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/p4.h"
29 #include "engines/icb/debug.h"
30 #include "engines/icb/p4_generic.h"
31 #include "engines/icb/res_man.h"
32 #include "engines/icb/common/px_common.h"
33 #include "engines/icb/common/px_game_object.h"
34 #include "engines/icb/common/px_scriptengine.h"
35 #include "engines/icb/common/px_prop_anims.h"
36 #include "engines/icb/common/px_walkarea_integer.h"
37 #include "engines/icb/object_structs.h"
38 #include "engines/icb/session.h"
39 #include "engines/icb/mission.h"
40 #include "engines/icb/common/px_linkeddatafile.h"
41 #include "engines/icb/floors.h"
42 #include "engines/icb/barriers.h"
43 #include "engines/icb/global_objects.h"
44 #include "engines/icb/global_switches.h"
45 #include "engines/icb/remora.h"
46 #include "engines/icb/sound_logic.h"
47 #include "engines/icb/loadscrn.h"
48 #include "engines/icb/sound.h"
49 #include "engines/icb/sound_lowlevel.h"
50
51 namespace ICB {
52
53
54 // Translation tweaks
55
56 _linked_data_file *LoadTranslatedFile(const char *session, const char *mission);
57
58
59 // prototypes
60 int32 Fetch_token_value(uint8 *file, uint32 length, uint8 *token);
61
62 void ClearTextures();
63
___init(const char * mission,const char * new_session_name)64 void _game_session::___init(const char *mission, const char *new_session_name) {
65 // session object constructor
66 // set up a game_session object from a session name
67
68 uint32 buf_hash;
69
70 // begin with no set object
71 // a camera will be choosen after the first logic cycle based upon the player objects position
72 set.Reset();
73
74 // no special footsteps set
75
76 numFloorFootSfx = 0;
77 specialFootSfx = 0;
78 ladderFootSfx = 0;
79 defaultFootSfx = 0;
80
81 // setup speech text block pointer
82 text_bloc = g_text_bloc1;
83 text_speech_bloc = g_text_bloc2; // pc has second bloc
84
85 // If you die when you have an unread email and restart the mission, the email icon continues
86 // to flash. The Remora should reset this when it initialises but since that happens after all
87 // the objects have been loaded, I decided it would be safest to reset the flag here. This is
88 // very much an 11th-hour hack.
89 g_oRemora->MarkEmailRead();
90
91 // first clear out private_session_resman
92 private_session_resman->Reset();
93 // tell it that the resources cannot be moved about
94 private_session_resman->Set_to_no_defrag();
95
96 ClearTextures();
97
98 if (camera_hack == TRUE8) {
99 total_objects = 0;
100 return;
101 }
102
103 // Make the filename equivalent of the hash'ed version of session name
104 HashFile(new_session_name, session_h_name);
105
106 // Put the hash mission and hash session filenames together
107 char h_mission_name[8];
108 HashFile(mission, h_mission_name);
109
110 sprintf(speech_font_one, FONT_PATH, "font.pcfont");
111 sprintf(remora_font, FONT_PATH, "futura.pcfont");
112
113 if (sprintf(session_name, "%s\\%s\\", mission, new_session_name) > ENGINE_STRING_LEN)
114 Fatal_error("_game_session::_game_session [%s] string overflow", session_name);
115
116 if (sprintf(h_session_name, "%s\\%s", h_mission_name, session_h_name) > ENGINE_STRING_LEN)
117 Fatal_error("_game_session::_game_session [%s] string overflow", h_session_name);
118
119 if (sprintf(session_cluster, SESSION_CLUSTER_PATH, h_mission_name, session_h_name) > ENGINE_STRING_LEN)
120 Fatal_error("_game_session::_game_session [%s] string overflow", session_cluster);
121
122 session_cluster_hash = HashString(session_cluster);
123 speech_font_one_hash = HashString(speech_font_one);
124 remora_font_hash = HashString(remora_font);
125 Zdebug("_game_session %s", (const char *)session_name);
126
127 // now setup the session
128 // load all the fixed name files
129
130 // mission<
131 // session<
132 // objects.linked
133 // scripts.linked
134 // walkgrid.grid
135 // session.RVanims
136 // camera-name<
137
138 // Jake : so PSX can have nice session loading screen and details (for timing and to stop player getting bored)
139 StartLoading(new_session_name);
140
141 LoadMsg("Session Cluster");
142
143 // right, on the psx for the between session sound we need to make sure the
144 // resman has the mission sound stuff in memory BEFORE we do the
145 // StartLoading...
146
147 LoadMsg("Session Sound");
148
149 // setup sound data cluster on psx...
150
151 LoadSessionSounds(session_cluster);
152
153
154 // initialise the session game objects
155 // we can assume all of these in here will be of the game object class!
156
157 // When clustered the session files have the base stripped
158 strcpy(temp_buf, "objects");
159
160 // so PSX can have nice session loading screen and details (for timing and to stop player getting bored)
161 LoadMsg("Session Objects");
162
163 // Make Res_open compute the hash value
164 buf_hash = NULL_HASH;
165 objects = (_linked_data_file *)private_session_resman->Res_open(temp_buf, buf_hash, session_cluster, session_cluster_hash);
166
167 // set this for convenience
168 total_objects = objects->Fetch_number_of_items();
169 Zdebug("total objects %d", total_objects);
170
171 if (total_objects >= MAX_session_objects)
172 Fatal_error("too many objects! max available %d", MAX_session_objects);
173
174 // create the prop table
175 // each object has a prop state - though only objects that are linked to background props will use them
176 // an objects number maps to the state table
177 // prop_state_table = new uint32[total_objects+10];
178 uint32 j;
179 for (j = 0; j < total_objects; j++)
180 prop_state_table[j] = 0;
181
182 // inititialise the session scripts
183
184 // When clustered the session files have the base stripped
185 strcpy(temp_buf, "scripts");
186
187 // so PSX can have nice session loading screen and details (for timing and to stop player getting bored)
188 LoadMsg("Session Scripts");
189 buf_hash = NULL_HASH;
190 scripts = (_linked_data_file *)private_session_resman->Res_open(temp_buf, buf_hash, session_cluster, session_cluster_hash);
191
192 // display script version info
193 // also available on console
194 Script_version_check();
195
196 // initialise prop animation file
197
198 // When clustered the session files have the base stripped
199 strcpy(temp_buf, PX_FILENAME_PROPANIMS);
200
201 // so PSX can have nice session loading screen and details (for timing and to stop player getting bored)
202 LoadMsg("Session PropAnims");
203 buf_hash = NULL_HASH;
204 prop_anims = (_linked_data_file *)private_session_resman->Res_open(temp_buf, buf_hash, session_cluster, session_cluster_hash);
205
206 // Check file version is correct.
207 if (prop_anims->GetHeaderVersion() != VERSION_PXWGPROPANIMS)
208 Fatal_error("%s version check failed (file has %d, engine has %d)", temp_buf, prop_anims->GetHeaderVersion(), VERSION_PXWGPROPANIMS);
209
210 // init features file
211 // we stick this in the private cache so it hangs around and later in-game references wont cause a main pool reload
212
213 // When clustered the session files have the base stripped
214 strcpy(temp_buf, "pxwgfeatures");
215
216 // so PSX can have nice session loading screen and details (for timing and to stop player getting bored)
217 LoadMsg("Session Features");
218 buf_hash = NULL_HASH;
219 features = (_linked_data_file *)private_session_resman->Res_open(temp_buf, buf_hash, session_cluster, session_cluster_hash);
220
221 // engine knows no set/camera chosen
222 Reset_camera_director();
223 camera_lock = FALSE8; // move to camera director
224
225 // reset the route manager service
226 Reset_route_manager();
227
228 text_bloc->please_render = FALSE8;
229 text_speech_bloc->please_render = FALSE8;
230 conv_focus = 0; // no conversation is in focus
231 total_convs = 0; // no conversations
232
233 Tdebug("text_lines.txt", "\n\n---Text Lines---\n");
234
235 // text
236 text = NULL; // game can exist with this file
237
238 char textFileName[100];
239
240 // When clustered the session files have the base stripped
241 strcpy(temp_buf, "text");
242
243 buf_hash = HashString(temp_buf);
244 if (private_session_resman->Test_file(temp_buf, buf_hash, session_cluster, session_cluster_hash)) {
245 // Jake : so PSX can have nice session loading screen and details (for timing and to stop player getting bored)
246 LoadMsg("Session Text");
247
248 // Special text loading code so the translators can test their stuff
249
250 if (tt) {
251 // Ok, translators mode has been activated
252 text = LoadTranslatedFile(mission, session_name);
253 } else
254 text = (_linked_data_file *)private_session_resman->Res_open(temp_buf, buf_hash, session_cluster, session_cluster_hash);
255 } else
256 Fatal_error("Missing Text File \"%s\"", temp_buf);
257
258 Tdebug("session.txt", "text lines END");
259
260 // A copy of the code above to open the global text file. Feel free to edit this if I've ballsed up
261 // anywhere.
262 global_text = NULL;
263
264 char global_cluster[ENGINE_STRING_LEN];
265
266 strcpy(global_cluster, GLOBAL_CLUSTER_PATH);
267
268 uint32 global_cluster_hash = HashString(global_cluster);
269
270 sprintf(textFileName, GLOBAL_TEXT_FILE);
271
272 buf_hash = HashString(textFileName);
273
274 if (private_session_resman->Test_file(textFileName, buf_hash, global_cluster, global_cluster_hash)) {
275 LoadMsg(temp_buf);
276
277 if (tt) {
278 // Ok, translators mode has been activated
279 global_text = LoadTranslatedFile("global", "global\\global\\");
280 } else
281 global_text = (_linked_data_file *)private_session_resman->Res_open(textFileName, buf_hash, global_cluster, global_cluster_hash);
282
283 } else {
284 Fatal_error("Failed to find global text file [%s][%s]", textFileName, global_cluster);
285 }
286
287 // The surface manager needs to know what colour to use for transparency. This comes from a fixed
288 // reference file which is opened here so the global reference can be set.
289 g_oIconMenu->SetTransparencyColourKey();
290
291 // Initialise the remora
292 g_oRemora->InitialiseRemora();
293
294 // Set the default colour for voice over text.
295 voice_over_red = VOICE_OVER_DEFAULT_RED;
296 voice_over_green = VOICE_OVER_DEFAULT_GREEN;
297 voice_over_blue = VOICE_OVER_DEFAULT_BLUE;
298
299 Tdebug("session.txt", "CHI START");
300 // chi
301 // players movement history
302 cur_history = 0;
303 chi_think_mode = __NOTHING;
304
305 is_there_a_chi = FALSE8;
306
307 Tdebug("session.txt", "walkareas START");
308
309 // setup walkareas
310 total_was = 0;
311
312 // When clustered the session files have the base stripped
313 strcpy(temp_buf, "walkarea");
314
315 buf_hash = HashString(temp_buf);
316
317 // Jake : so PSX can have nice session loading screen and details (for timing and to stop player getting bored)
318 LoadMsg("Session Walkareas");
319 uint32 len = private_session_resman->Check_file_size(temp_buf, buf_hash, session_cluster, session_cluster_hash);
320
321 if (len) {
322 walk_areas = (_linked_data_file *)private_session_resman->Res_open(temp_buf, buf_hash, session_cluster, session_cluster_hash);
323 Tdebug("walkareas.txt", "%d top level walkareas\n", walk_areas->Fetch_number_of_items());
324
325 int32 nMissing = 0;
326 for (uint32 k = 0; k < walk_areas->Fetch_number_of_items(); k++) {
327 INTEGER_WalkAreaFile *inner_wa;
328 inner_wa = (INTEGER_WalkAreaFile *)walk_areas->Fetch_item_by_number(k);
329
330 Tdebug("walkareas.txt", "\nclump %d has %d inner items", k, inner_wa->GetNoAreas());
331
332 for (j = 0; j < inner_wa->GetNoAreas(); j++) {
333 const __aWalkArea *wa;
334
335 wa = inner_wa->GetWalkArea(j);
336
337 wa_list[total_was++] = wa; // write entry to individual item list
338
339 if (total_was == MAX_was)
340 Fatal_error("total number of walk-areas exceeded - %d", MAX_was);
341 }
342 }
343 if (nMissing > 0)
344 Fatal_error("%d missing cameras : Game must terminate", nMissing);
345
346 Tdebug("walkareas.txt", "\n%d individual walk areas found", total_was);
347
348 } else
349 Tdebug("walkareas.txt", "no walkarea file");
350
351 number_of_missing_objects = 0; // start with no missing objects
352
353 // structure assignment counters - see fn_create_mega
354 num_megas = 0;
355 num_vox_images = 0;
356
357 // init conveyors
358 for (j = 0; j < MAX_conveyors; j++)
359 conveyors[j].moving = FALSE8;
360
361 // init auto interact
362 for (j = 0; j < MAX_auto_interact; j++)
363 auto_interact_list[j] = 0; // no entry
364
365 // stairs
366 num_stairs = 0;
367
368 // lifts
369 num_lifts = 0;
370
371 // turn off health bar
372 health_time = 0;
373
374 // setup generic asyncer
375 player_stat_was = __TOTAL_PLAYER_MODES; // for 'prev' check
376 player_stat_use = __TOTAL_PLAYER_MODES; // for 'prev' check
377 async_counter = 0; // counts up each frame
378 async_off = 0; // on by default
379
380 // first cycle indicator
381 first_session_cycle = TRUE8;
382
383 Tdebug("session.txt", "session constructor END");
384 }
385
Script_version_check()386 void _game_session::Script_version_check() {
387 if (FN_ROUTINES_DATA_VERSION != scripts->GetHeaderVersion())
388 Fatal_error("WARNING! SCRIPTS AND ENGINE ARE NOT SAME VERSION");
389 }
390
___destruct()391 void _game_session::___destruct() {
392 // session object deconstructor
393 // kills the current session and removes its resources
394
395 Zdebug("*session destructing*");
396
397 // turn off all sounds
398 StopAllSoundsNow();
399
400 Zdebug("sounds stopped");
401
402 // camview mode
403 if (camera_hack == TRUE8) {
404 SetReset();
405 return;
406 }
407
408 // trash resources
409 private_session_resman->Reset();
410
411 // remove diag bars that have been newed
412 for (uint32 j = 0; j < total_objects; j++)
413 if (logic_structs[j]->mega)
414 logic_structs[j]->mega->m_main_route.___init(); // delete diag bars
415
416 // delete current set view
417 SetReset();
418
419 // delete player object
420 // delete player;
421
422 // speech
423 // if (text_bloc)
424 // delete text_bloc; //in-case quit during speech
425 }
426
Initialise_set(const char * name,const char * cluster_name)427 void _game_session::Initialise_set(const char *name, const char *cluster_name) {
428 // initialise a set object for the current session
429 // stick us into TEMP_NETHACK mode if the set does not physically exist
430
431 // place a split point/boundary into the resource loading
432 rs_bg->Advance_time_stamp();
433
434 // init the set
435 set.Init(name, cluster_name);
436
437 // decide which props to sleep and which to wake
438 Setup_prop_sleep_states();
439 }
440
Setup_prop_sleep_states()441 void _game_session::Setup_prop_sleep_states() {
442 // initialise the new set object
443
444 for (uint32 j = 0; j < total_objects; j++)
445 if (!logic_structs[j]->mega) { // props
446 logic_structs[j]->prop_on_this_screen = set.DoesPropExist((const char *)logic_structs[j]->GetName());
447
448 if (logic_structs[j]->hold_mode == prop_camera_hold) {
449 if (!logic_structs[j]->prop_on_this_screen) {
450 logic_structs[j]->camera_held = TRUE8; // not on screen
451 logic_structs[j]->cycle_time = 0; // accurate for displays
452 } else {
453 logic_structs[j]->camera_held = FALSE8; // on screen
454 }
455 } else { // not an auto sleep item, but...
456 // check for sleeping items that are now on screen
457 if ((logic_structs[j]->camera_held) && (logic_structs[j]->prop_on_this_screen)) { // held AND on screen (a door)
458 logic_structs[j]->camera_held = FALSE8; // on screen and not a camera_hold_mode item so wake it up - i.e. doors
459 }
460 }
461 }
462 }
463
Awaken_doors()464 void _game_session::Awaken_doors() {
465 // called to release doors when entering remora mode
466
467 for (uint32 j = 0; j < total_objects; j++)
468 if ((logic_structs[j]->big_mode == __CUSTOM_BUTTON_OPERATED_DOOR) || (logic_structs[j]->big_mode == __CUSTOM_AUTO_DOOR)) {
469 logic_structs[j]->camera_held = FALSE8; // awake!
470 logic_structs[j]->prop_on_this_screen = TRUE8;
471 }
472 }
473
474 __mega_set_names player_startup_anims[] = {__STAND, __STAND_TO_WALK, __WALK, __WALK_TO_STAND, __TURN_ON_THE_SPOT_CLOCKWISE};
475 #define NUMBER_player_startup_anims 5
476
Init_objects()477 void _game_session::Init_objects() {
478 char buf[ENGINE_STRING_LEN];
479 uint32 j, id;
480
481 if (!g_mission->inited_globals) {
482 // init local globals
483 // only do this at start of mission - never again afterward - i.e. not when returning to first session from another
484 uint32 script_hash;
485
486 id = objects->Fetch_item_number_by_name("player"); // returns -1 if object not in existence
487 if (id == 0xffffffff)
488 Fatal_error("Init_objects cant find 'player'");
489 script_hash = HashString("player::globals");
490 const char *pc = (const char *)scripts->Try_fetch_item_by_hash(script_hash);
491 if (pc) {
492 object = (c_game_object *)objects->Fetch_item_by_number(id);
493 Tdebug("objects_init.txt", " initialising globals", (const char *)buf);
494 RunScript(pc, object);
495 }
496
497 g_mission->inited_globals = TRUE8;
498 }
499
500 Zdebug("\nInitialise_objects");
501
502 // so PSX can have nice session loading screen and details (for timing and to stop player getting bored)
503 // this stuff moved here when ammo, bullets etc moved into player struct
504 InitMsg("Player");
505 // create a player object
506 player.___init();
507
508 // Now run the InitScript for each object. This has to be done after the line-of-sight end
509 // event manager have been initialised in case calls get made to these services in any of the
510 // objects' InitScripts.
511 for (j = 0; ((j < total_objects)); j++) {
512 object = (c_game_object *)objects->Fetch_item_by_number(j);
513 Tdebug("objects_init.txt", "\n\n---------------------------------------------------\n%d initialising object '%s'", j, object->GetName());
514 Zdebug("\n\n---------------------------------------------------\n%d initialising object '%s'", j, object->GetName());
515
516 Zdebug("[%d]", num_megas);
517
518 // fast reference for engine functions
519 // needed incase structs are referenced by FN_ functions
520 cur_id = j;
521
522 // set L for FN_ functions that may be called
523 L = logic_structs[j];
524 I = L->voxel_info;
525 M = L->mega;
526
527 // possibly run the init OR logic context script to kick in a base script ready for running in the logic loop
528 // the init script is always script 0 for the object
529 // the init script may or may not be overiden
530 // get the address of the script we want to run
531 const char *pc = (const char *)scripts->Try_fetch_item_by_hash(object->GetScriptNameFullHash(OB_INIT_SCRIPT)); // run init script
532
533 if (pc) {
534 RunScript(pc, object);
535
536 strcpy(buf, object->GetName());
537 strcat(buf, "::local_init");
538
539 uint32 script_hash;
540
541 script_hash = HashString(buf);
542
543 // Jso PSX can have nice session loading screen and details (for timing and to stop player getting bored)
544 InitMsg(object->GetName());
545
546 Tdebug("objects_init.txt", "search for [%s]", (const char *)buf);
547
548 pc = (const char *)scripts->Try_fetch_item_by_hash(script_hash);
549
550 if (pc) {
551 // set M and I for FN_ functions that may be called
552
553 I = L->voxel_info;
554 M = L->mega;
555
556 Tdebug("objects_init.txt", " running optional = [%s]", (const char *)buf);
557 RunScript(pc, object);
558 } else
559 Tdebug("objects_init.txt", " no [%s] found", (const char *)buf);
560
561 // setup logic context
562 // set to start on level 0
563 logic_structs[j]->logic_level = 0;
564
565 // set base logic to logic context script
566 logic_structs[j]->logic[0] = (char *)scripts->Try_fetch_item_by_hash(object->GetScriptNameFullHash(OB_LOGIC_CONTEXT));
567 // **note, we dont need to set up the script reference (logic_ref) for level 0
568 } else
569 Shut_down_object("by initialise - no init script");
570
571 L = logic_structs[j];
572 I = L->voxel_info;
573 if (L->image_type == VOXEL) {
574 for (uint32 i = 0; i < __NON_GENERIC; i++) {
575 if (I->IsAnimTable(i)) {
576 #ifdef PRELOAD
577 rs_anims->Res_open(I->info_name[i], I->info_name_hash[i], I->base_path, I->base_path_hash);
578 #endif
579 }
580 }
581 }
582
583 }
584
585 Tdebug("objects_init.txt", "\n\n\ncreating mega list");
586 // create voxel id list
587 number_of_voxel_ids = 0;
588 for (j = 0; j < total_objects; j++) { // object 0 is used
589 // object must be alive and interactable and a mega
590 if ((logic_structs[j]->image_type == VOXEL) && (logic_structs[j]->ob_status != OB_STATUS_HELD)) { // not if the object has been manually switched out
591 Tdebug("objects_init.txt", "%s", (const char *)logic_structs[j]->GetName());
592 voxel_id_list[number_of_voxel_ids++] = (uint8)j;
593 }
594 }
595
596 if (number_of_voxel_ids >= MAX_voxel_list)
597 Fatal_error("Initialise_objects, the voxel id list is too small");
598
599 Tdebug("objects_init.txt", "\n\nfound %d voxel characters", number_of_voxel_ids);
600
601 // init the player object number
602 // get id
603 id = objects->Fetch_item_number_by_name("player"); // returns -1 if object not in existence
604
605 if (id != 0xffffffff) {
606 L = logic_structs[id]; // fetch logic struct for player object
607 I = L->voxel_info;
608 M = L->mega;
609
610 object = (c_game_object *)objects->Fetch_item_by_number(id);
611
612 // not if this object has been shut-down - for not having a map marker for example
613 if (L->ob_status != OB_STATUS_HELD)
614 player.Set_player_id(id);
615
616 // Preload the player animation to make PSX jerking better
617 for (uint32 i = 0; i < NUMBER_player_startup_anims; i++)
618 rs_anims->Res_open(I->get_anim_name(player_startup_anims[i]), I->anim_name_hash[player_startup_anims[i]], I->base_path, I->base_path_hash);
619 }
620
621 // done
622 Zdebug("Init session finished\n");
623
624 // so PSX can have nice session loading screen and details (for timing and to stop player getting bored)
625 EndLoading();
626 }
627
Pre_initialise_objects()628 void _game_session::Pre_initialise_objects() {
629 // prepare gameworld and objects but dont run init scripts yet
630
631 // so PSX can have nice session loading screen and details (for timing and to stop player getting bored)
632 StartInit(total_objects + 6); // +6 because also floors, barriers, markers, camera_table, plan_view, player
633
634 Zdebug("\nPre_Initialise_objects");
635
636 Zdebug("[%d]", num_megas);
637
638 // so PSX can have nice session loading screen and details (for timing and to stop player getting bored)
639 InitMsg("Floors");
640
641 // initialise the floor area definition file
642 // uses the private resman so mission->session-> need to be initialised so this cant be on session contructor
643 floor_def = g_icb_session_floors;
644 g_icb_session_floors->___init();
645
646 // so PSX can have nice session loading screen and details (for timing and to stop player getting bored)
647 InitMsg("Barriers");
648
649 // initialise the route barriers
650 session_barriers = &g_icb_session_barriers;
651 g_icb_session_barriers.___init();
652
653 Zdebug("A[%d]", num_megas);
654
655 // so PSX can have nice session loading screen and details (for timing and to stop player getting bored)
656 InitMsg("Markers");
657
658 // init engine markers
659 markers.___init();
660
661 // so PSX can have nice session loading screen and details (for timing and to stop player getting bored)
662 InitMsg("Cameras");
663
664 // setup the camera system
665 Build_camera_table();
666
667 // First set up the logic structures for the objects. Each begins with a NULL pointer for its vox image.
668 uint32 j;
669 for (j = 0; ((j < total_objects)); ++j) {
670
671 Zdebug("%d -[%d]", j, num_megas);
672
673 object = (c_game_object *)objects->Fetch_item_by_number(j);
674
675 logic_structs[j] = g_logics[j];
676 logic_structs[j]->___init((const char *)object->GetName());
677 }
678
679 // Set up the event manager for this session. This has to be done after the barrier handler
680 // has been set up and after the objects have been set up because it relies on information
681 // from both.
682 PXTRY
683
684 g_oEventManager->Initialise();
685
686 PXCATCH
687
688 Fatal_error("Exception in _event_manager::Initialise()");
689
690 PXENDCATCH
691
692 // And set a duty cycle for the line-of-sight manager. Just set default for now.
693
694 PXTRY
695
696 Zdebug("duty");
697 g_oLineOfSight->SetDutyCycle(1);
698 Zdebug("~duty");
699
700 PXCATCH
701
702 Fatal_error("Exception in g_oLineOfSight->SetDutyCycle()");
703
704 PXENDCATCH
705
706 // Initialise the sound logic engine.
707 g_oSoundLogicEngine->Initialise();
708
709 player.has_weapon = TRUE8; // called before players init script
710 }
711
712 extern int32 john_number_traces;
713 extern int32 john_total_traces;
714
715 extern int32 fnTimer;
716 int32 logicTimer;
717 uint32 script_cycleTimer;
718
One_logic_cycle()719 void _game_session::One_logic_cycle() {
720 // process all the game objects
721
722 uint32 j;
723
724 // Wind the line-of-sight engine on one position. Note that there is a variable in the line-of-sight engine
725 // which can be used to make this call return without doing anything a certain percentage of the time.
726
727 john_number_traces = 0;
728 john_total_traces = 0;
729
730 uint32 time = GetMicroTimer();
731 PXTRY
732 g_oLineOfSight->DutyCycle();
733 PXCATCH
734 Fatal_error("Exception in g_oLineOfSight->DutyCycle()");
735 PXENDCATCH
736 time = GetMicroTimer() - time;
737 g_mission->los_time = time;
738
739 time = GetMicroTimer();
740 PXTRY
741 g_oSoundLogicEngine->Cycle();
742 PXCATCH
743 Fatal_error("Exception in g_oSoundLogicEngine->Cycle()");
744 PXENDCATCH
745 time = GetMicroTimer() - time;
746 g_mission->sound_time = time;
747
748 time = GetMicroTimer();
749
750 // service the speech driver
751 Service_speech();
752
753 // reset route manager
754 Start_new_router_game_cycle();
755
756 // Tell the event manager to process its event timers if it is currently maintaining any.
757 g_oEventManager->CycleEventManager();
758
759 // If the icon menu currently has a holding icon, service its logic.
760 if (g_oIconListManager->IsHolding())
761 g_oIconListManager->CycleHoldingLogic();
762
763 // If the icon menu is currently flashing added medipacks or clips run the logic for it.
764 if (g_oIconMenu->IsAdding())
765 g_oIconMenu->CycleAddingLogic();
766
767 time = GetMicroTimer() - time;
768 g_mission->event_time = time;
769
770 // run through all the objects calling their logic
771 for (j = 0; j < total_objects; j++) { // object 0 is used
772
773 // fetch the engine created logic structure for this object
774 L = logic_structs[j];
775
776 if ((L->ob_status != OB_STATUS_HELD) && (!L->camera_held)) { // not if the object has been manually switched out
777 I = L->voxel_info;
778 M = L->mega;
779 cur_id = j; // fast reference for engine functions
780 // fetch the object that is our current object
781 // 'object' needed as logic code may ask it for the objects name, etc.
782 object = (c_game_object *)objects->Fetch_item_by_number(j);
783
784 // run appropriate logic
785 switch (L->big_mode) {
786 case __SCRIPT: // just running full scripts
787 if (g_px->mega_timer)
788 script_cycleTimer = GetMicroTimer();
789 Pre_logic_event_check();
790 Script_cycle();
791 if (g_px->mega_timer) {
792 script_cycleTimer = GetMicroTimer() - script_cycleTimer;
793 L->cycle_time = script_cycleTimer;
794 }
795 break;
796
797 case __NO_LOGIC: // do nothing
798 break;
799
800 case __CUSTOM_SIMPLE_ANIMATE: // special simple animator logic
801 if (g_px->mega_timer)
802 script_cycleTimer = GetMicroTimer();
803 Custom_simple_animator();
804 if (g_px->mega_timer) {
805 script_cycleTimer = GetMicroTimer() - script_cycleTimer;
806 L->cycle_time = script_cycleTimer;
807 }
808
809 break;
810
811 case __CUSTOM_BUTTON_OPERATED_DOOR: // special button operated door
812 if (g_px->mega_timer)
813 script_cycleTimer = GetMicroTimer();
814 Custom_button_operated_door();
815 if (g_px->mega_timer) {
816 script_cycleTimer = GetMicroTimer() - script_cycleTimer;
817 L->cycle_time = script_cycleTimer;
818 }
819
820 break;
821
822 case __CUSTOM_AUTO_DOOR:
823 if (g_px->mega_timer)
824 script_cycleTimer = GetMicroTimer();
825 Custom_auto_door();
826 if (g_px->mega_timer) {
827 script_cycleTimer = GetMicroTimer() - script_cycleTimer;
828 L->cycle_time = script_cycleTimer;
829 }
830 break;
831
832 case __MEGA_SLICE_HELD:
833 if (M->on_players_floor) {
834 L->big_mode = __SCRIPT; // in view - alive
835 g_oEventManager->ClearAllEventsForObject(cur_id);
836 g_oSoundLogicEngine->ClearHeardFlag(cur_id);
837 Script_cycle();
838 }
839
840 if (PXfabs(M->actor_xyz.y - logic_structs[player.Fetch_player_id()]->mega->actor_xyz.y) < (int32)(M->slice_hold_tolerance)) {
841 L->big_mode = __SCRIPT;
842 g_oEventManager->ClearAllEventsForObject(cur_id);
843 g_oSoundLogicEngine->ClearHeardFlag(cur_id);
844 Script_cycle();
845 }
846 break;
847 case __MEGA_PLAYER_FLOOR_HELD:
848 case __MEGA_INITIAL_FLOOR_HELD:
849 // waiting for player - release when on our floor
850 if (M->on_players_floor) {
851 L->big_mode = __SCRIPT;
852 g_oEventManager->ClearAllEventsForObject(cur_id);
853 g_oSoundLogicEngine->ClearHeardFlag(cur_id);
854 Script_cycle();
855 } else if (first_session_cycle)
856 Script_cycle(); // no camera yet
857 break;
858 }
859
860 // if the character is voxel based then add it to the list of voxel characters that is used by stage draw
861 if ((L->image_type == VOXEL) && ((L->ob_status != OB_STATUS_HELD))) {
862 time = GetMicroTimer();
863
864 // update some mega related stuff if mega is not sleeping
865 if (L->big_mode == __SCRIPT) {
866 // process outstanding auto pan remaining
867 if (L->auto_panning)
868 Advance_auto_pan();
869
870 Idle_manager(); // heh heh, check for mega just stood around
871
872 UpdateFootstep();
873 UpdateMegaFX();
874 }
875
876 // set floor rect value - used by stage draw to find indexed camera name
877 floor_def->Set_floor_rect_flag(L);
878
879 // check player floor status
880 if (!first_session_cycle)
881 Process_player_floor_status(); // sends events when object gains same floor as player
882
883 // process hold mode options
884 // has on-camera-only mode and is not on screen - then sleep again
885 if ((L->hold_mode == mega_player_floor_hold) && (!M->on_players_floor))
886 L->big_mode = __MEGA_PLAYER_FLOOR_HELD;
887
888 // has slice hold mode and is now off camera - check for shut off tolerance
889 if ((L->hold_mode == mega_slice_hold) && (!M->on_players_floor)) {
890 if (PXfabs(M->actor_xyz.y - logic_structs[player.Fetch_player_id()]->mega->actor_xyz.y) > (int32)(M->slice_hold_tolerance))
891 L->big_mode = __MEGA_SLICE_HELD;
892 }
893
894 // if on screen and stood then lets maybe do a scratch anim
895
896 time = GetMicroTimer() - time;
897 g_mission->xtra_mega_time += time;
898 }
899 }
900 }
901
902 // set not first cycle
903 first_session_cycle = FALSE8;
904 }
905
Pre_logic_event_check()906 void _game_session::Pre_logic_event_check() {
907 // Check if there any events pending for the object.
908 // if so we re-run the logic context which may or may not decide to change the current script
909
910 if (((L->do_not_disturb == 1)) || (L->do_not_disturb == 2))
911 return; // 1 or 2 == fn-do-not-disturb + speech
912
913 if (L->do_not_disturb == 3) { // socket_force_new_logic
914 // clear events
915 g_oEventManager->ClearAllEventsForObject(cur_id);
916
917 L->do_not_disturb = 0; // reset for next cycle
918 return;
919 }
920
921 // Added call to the sound logic engine to see if any sound events are pending.
922 if (L->context_request || g_oEventManager->HasEventPending(cur_id) || g_oSoundLogicEngine->SoundEventPendingForID(cur_id)) {
923 // Yes, the object has an event pending, so rerun its logic context.
924 if (L->context_request)
925 Zdebug("[%s] internal request to rerun logic context", object->GetName());
926
927 else
928 Zdebug("[%s] event means rerun logic context", object->GetName());
929
930 if ((L->image_type == VOXEL) && (M->interacting)) { // check for megas who are interacting
931 // interacting, so ignoring LOS event
932 Zdebug("interacting, so ignoring LOS event");
933 } else {
934 L->logic[0] = (char *)scripts->Try_fetch_item_by_hash((object->GetScriptNameFullHash(OB_LOGIC_CONTEXT)));
935
936 // run script - context chooser MAY pick a new L 1 logic
937 // we call this now so the new script will be setup and ready to run
938 RunScript(const_cast<const char *&>(L->logic[0]), object);
939
940 // reset the crude switch
941 L->context_request = FALSE8;
942 }
943 }
944 }
945
Script_cycle()946 void _game_session::Script_cycle() {
947 int32 ret;
948 c_game_object *script_owner;
949 uint32 inner_cycles;
950
951 inner_cycles = 0; // to catch infnite_loops
952
953 // inner logic loop
954 do {
955 // sort out which object we should run the script with
956 // this can change within a cycle
957 if ((L->image_type == VOXEL) && (M->interacting)) { // check for megas who are interacting
958 // object is running someone elses interaction script
959 // so get their object and pass to interpretter so that local vars can be accessed correctly
960 script_owner = (c_game_object *)objects->Fetch_item_by_number(M->target_id);
961 } else {
962 script_owner = object; // object running its own script
963 }
964
965 ret = RunScript(const_cast<const char *&>(L->logic[L->logic_level]), script_owner);
966
967 // ret is:
968 // 0 done enough this cycle
969 // 1 current script has finished and hit closing brace
970 // 2 FN_ returned an IR_TERMINATE to interpretter so we just go around - new script or gosub
971
972 if (ret == IR_RET_SCRIPT_FINISHED) { // script has finished so drop down a level
973 if (L->logic_level) { // not on base, so we can just drop down to the script below
974 L->logic_level--;
975
976 // in-case we were running an interaction script then cancel interaction
977 if (L->image_type == VOXEL) {
978 M->target_id = 0;
979 M->interacting = FALSE8;
980 L->looping = L->old_looping; // safe for return
981 }
982 }
983
984 if (!L->logic_level) { // restart the object
985 L->logic_ref[1] = 0; // completely reset L 1 so that context choose will select ok
986 // it is acceptable to choose the logic that had previously been running
987
988 // temp reset PC the hard way
989 L->logic[0] = (char *)scripts->Try_fetch_item_by_hash(object->GetScriptNameFullHash(OB_LOGIC_CONTEXT));
990
991 // run script - context chooser will pick a new L 1 logic
992 RunScript(const_cast<const char *&>(L->logic[0]), object);
993
994 // if still on base then nothing chosen - shut down the object and log a warning somewhere
995 if (!L->logic_level) {
996 Shut_down_object("by One_logic_cycle - logic context failed to choose");
997 ret = 0;
998 }
999 }
1000 }
1001
1002 // catch infinite loops
1003 inner_cycles++;
1004
1005 // I upped this from 5 to 1000 because the Remora relies on fn_new_script to jump around
1006 // it's menu structure and this was tripping the limit. The player will have to spend ages
1007 // in the Remora's menu's now for it to trip this limit, and genuine infinite loops will
1008 // still be caught.
1009 if (inner_cycles == 1000)
1010 Fatal_error("object [%s] is in an infinite script loop!", object->GetName());
1011
1012 } while (ret); // ret==0 means quit for this object
1013 }
1014
Fetch_prop_state(char * prop_name)1015 uint32 _game_session::Fetch_prop_state(char *prop_name) {
1016 // return a props state
1017 // if the prop object doesnt exist we create a dummy - the system continues regardless - which is nice
1018
1019 uint32 prop_number;
1020 uint32 j;
1021
1022 if (camera_hack == FALSE8) {
1023 prop_number = objects->Fetch_item_number_by_name(prop_name);
1024
1025 if (prop_number != 0xffffffff)
1026 return (prop_state_table[prop_number]); // get prop state (pc)
1027 }
1028
1029 // prop does not have a owner object - so we create a dummy for it if we havent already
1030 // so, have we already created a dummy?
1031 // search for dummy
1032
1033 // is our object already here?
1034 j = 0;
1035 while ((j < number_of_missing_objects) && (strcmp(missing_obs[j], prop_name)))
1036 ++j;
1037
1038 // didnt find the object
1039 if (j == number_of_missing_objects) {
1040 // create entry for the object
1041 if (strcmp(prop_name, "not a prop") && (camera_hack == FALSE8)) // dont report dummy lights
1042 Message_box("object missing for prop [%s]", prop_name);
1043
1044 Set_string(prop_name, missing_obs[number_of_missing_objects], MAX_missing_object_name_length);
1045
1046 Tdebug("missing_objects.txt", "%d [%s]", number_of_missing_objects, missing_obs[number_of_missing_objects]);
1047 missing_ob_prop_states[number_of_missing_objects++] = 0;
1048
1049 return (0);
1050 }
1051
1052 // dummy did exist so return the current dummy prop value
1053 return (missing_ob_prop_states[j]);
1054 }
1055
Set_prop_state(char * prop_name,uint32 value)1056 void _game_session::Set_prop_state(char *prop_name, uint32 value) {
1057 // set a prop state
1058 // if the prop doesnt exist we skip it - and assume it will soon be built
1059 // there is no scope checking
1060
1061 uint32 prop_number;
1062 uint32 j;
1063
1064 if (camera_hack == FALSE8) {
1065 prop_number = objects->Fetch_item_number_by_name(prop_name);
1066
1067 if (prop_number != 0xffffffff)
1068 prop_state_table[prop_number] = value; // set prop state (pc)
1069 }
1070
1071 // have we already created a dummy?
1072 // search for dummy
1073
1074 // is our object already here?
1075 j = 0;
1076 while ((j < number_of_missing_objects) && (strcmp(missing_obs[j], prop_name)))
1077 ++j;
1078
1079 // didnt find the object
1080 if (j == number_of_missing_objects)
1081 return;
1082
1083 // found the dummy so set its value
1084 missing_ob_prop_states[j] = (uint8)value;
1085 }
1086
Fetch_named_objects_id(const char * name) const1087 uint32 _game_session::Fetch_named_objects_id(const char *name) const {
1088 uint32 i;
1089
1090 for (i = 0; i < total_objects; ++i) {
1091 if (strcmp(name, logic_structs[i]->GetName()) == 0)
1092 return (i);
1093 }
1094
1095 // The object wasn't found.
1096 Fatal_error("Object %s not found in _game_session::Fetch_named_objects_id()", name);
1097
1098 // This stops a compiler error.
1099 return (0xffffffff);
1100 }
1101
Process_player_floor_status()1102 void _game_session::Process_player_floor_status() {
1103 // work out if this object
1104 bool8 result = FALSE8;
1105 uint32 num_extra, j, cam, player_floor;
1106
1107 player_floor = logic_structs[player.Fetch_player_id()]->owner_floor_rect;
1108
1109 // dont need to tell the player he's on the players floor
1110 if (player.Fetch_player_id() == cur_id)
1111 return;
1112
1113 if (floor_to_camera_index[L->owner_floor_rect] == cur_camera_number)
1114 result = TRUE8; //
1115 else { // no exact same
1116 // ok, but is our floor linked to theirs?
1117 cam = floor_to_camera_index[player_floor];
1118 num_extra = cam_floor_list[cam].num_extra_floors;
1119
1120 for (j = 0; j < num_extra; j++)
1121 if (cam_floor_list[cam].extra_floors[j] == L->owner_floor_rect) { // our floor one of players extras?
1122 result = TRUE8; // yes - the floors are linked
1123 break;
1124 }
1125 }
1126
1127 if ((!M->on_players_floor) && (result)) {
1128 g_oEventManager->PostNamedEventToObject("on_floor", cur_id, player.Fetch_player_id()); // send as if from player
1129 }
1130
1131 M->on_players_floor = result; // set to current state
1132 }
1133
Idle_manager()1134 void _game_session::Idle_manager() {
1135 // megas only
1136 // is the character idling?
1137 // if so and for a int32 time gosub a script that will do something
1138
1139 uint32 k;
1140 char *ad;
1141 uint32 script_hash;
1142
1143 if ((L->pause) && (L->cur_anim_type == __STAND) && (L->conversation_uid == NO_SPEECH_REQUEST) && (!M->Is_crouched()) && (Object_visible_to_camera(cur_id))) {
1144
1145 M->idle_count++;
1146
1147 if ((M->idle_count > 24) && (L->logic_level == 1)) {
1148 M->idle_count = 0;
1149
1150 script_hash = HashString("idle");
1151
1152 // try and find a script with the passed extention i.e. ???::looping
1153 for (k = 0; k < object->GetNoScripts(); k++) {
1154 if (script_hash == object->GetScriptNamePartHash(k)) {
1155 // script k is the one to run
1156 // get the address of the script we want to run
1157 ad = (char *)scripts->Try_fetch_item_by_hash(object->GetScriptNameFullHash(k));
1158
1159 // write actual offset
1160 L->logic[2] = ad;
1161
1162 L->logic_level = 2; //
1163
1164 L->old_looping = L->looping; // safe for return
1165
1166 L->looping = 0; // reset to 0 for new logics
1167
1168 M->custom = FALSE8; // reset
1169
1170 return; // done it
1171 }
1172 }
1173 }
1174 }
1175 }
1176
Set_init_voxel_floors()1177 void _game_session::Set_init_voxel_floors() {
1178 // set all mega characters floors - called after game restore because logics may begin by checking the floor number but it wont be set until end of first cycle
1179 uint32 j;
1180
1181 for (j = 0; j < number_of_voxel_ids; j++)
1182 floor_def->Set_floor_rect_flag(logic_structs[voxel_id_list[j]]);
1183
1184 // setup the players route barriers
1185
1186 MS->M = MS->logic_structs[MS->player.Fetch_player_id()]->mega;
1187 MS->L = MS->logic_structs[MS->player.Fetch_player_id()];
1188 Prepare_megas_route_barriers(TRUE8); // update barriers
1189 }
1190
LoadTranslatedFile(const char * mission,const char * session)1191 _linked_data_file *LoadTranslatedFile(const char *mission, const char *session) {
1192 // Get the actual session name
1193 const char *sessionstart = session + strlen(mission) + 1;
1194 pxString actsession;
1195 actsession.SetString(sessionstart, strlen(sessionstart) - 1);
1196
1197 // Make up the name for the file to be loaded up
1198 pxString fname = pxVString("%s\\data\\%s%s.ttrans", tt_text, mission, (const char *)actsession);
1199
1200 if (!checkFileExists(fname))
1201 Fatal_error("Unable to load file %s", (const char *)fname);
1202
1203 // Load in this file
1204 Common::SeekableReadStream *stream = openDiskFileForBinaryStreamRead(fname.c_str());
1205
1206 if (stream == NULL) // if it could not be opened
1207 Fatal_error("Unable to load file %s", (const char *)fname);
1208
1209 uint32 len = stream->size();
1210
1211 // make space for file
1212 char *memPtr = new char[len + 1];
1213
1214 // read it in
1215 stream->read(memPtr, len);
1216 delete stream; // close the file
1217
1218 // 0 terminate the string
1219 memPtr[len] = 0;
1220
1221 return ((_linked_data_file *)memPtr);
1222 }
1223
1224 } // End of namespace ICB
1225