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/common/px_common.h"
29 #include "engines/icb/common/ptr_util.h"
30 #include "engines/icb/icb.h"
31 #include "engines/icb/session.h"
32 #include "engines/icb/mission.h"
33 #include "engines/icb/p4.h"
34 #include "engines/icb/object_structs.h"
35 #include "engines/icb/global_objects.h"
36 #include "engines/icb/sound.h"
37 #include "engines/icb/res_man.h"
38 
39 namespace ICB {
40 
fn_simple_chi(int32 & result,int32 * params)41 mcodeFunctionReturnCodes fn_simple_chi(int32 &result, int32 *params) { return (MS->fn_simple_chi(result, params)); }
42 
fn_chi(int32 & result,int32 * params)43 mcodeFunctionReturnCodes fn_chi(int32 &result, int32 *params) { return (MS->fn_chi(result, params)); }
44 
fn_start_chi_following(int32 & result,int32 * params)45 mcodeFunctionReturnCodes fn_start_chi_following(int32 &result, int32 *params) { return (MS->fn_start_chi_following(result, params)); }
46 
fn_record_player_interaction(int32 & result,int32 * params)47 mcodeFunctionReturnCodes fn_record_player_interaction(int32 &result, int32 *params) { return (MS->fn_record_player_interaction(result, params)); }
48 
fn_fetch_chi_mode(int32 & result,int32 * params)49 mcodeFunctionReturnCodes fn_fetch_chi_mode(int32 &result, int32 *params) { return (MS->fn_fetch_chi_mode(result, params)); }
50 
fn_check_for_chi(int32 & result,int32 * params)51 mcodeFunctionReturnCodes fn_check_for_chi(int32 &result, int32 *params) { return (MS->fn_check_for_chi(result, params)); }
52 
fn_wait_for_chi(int32 & result,int32 * params)53 mcodeFunctionReturnCodes fn_wait_for_chi(int32 &result, int32 *params) { return (MS->fn_wait_for_chi(result, params)); }
54 
fn_send_chi_to_this_object(int32 & result,int32 * params)55 mcodeFunctionReturnCodes fn_send_chi_to_this_object(int32 &result, int32 *params) { return (MS->fn_send_chi_to_this_object(result, params)); }
56 
fn_chi_wait_for_player_to_move(int32 & result,int32 * params)57 mcodeFunctionReturnCodes fn_chi_wait_for_player_to_move(int32 &result, int32 *params) { return (MS->fn_chi_wait_for_player_to_move(result, params)); }
58 
fn_stop_chi_following(int32 & result,int32 * params)59 mcodeFunctionReturnCodes fn_stop_chi_following(int32 &result, int32 *params) { return (MS->fn_stop_chi_following(result, params)); }
60 
fn_register_chi(int32 & result,int32 * params)61 mcodeFunctionReturnCodes fn_register_chi(int32 &result, int32 *params) { return (MS->fn_register_chi(result, params)); }
62 
fn_send_chi_to_named_object(int32 & result,int32 * params)63 mcodeFunctionReturnCodes fn_send_chi_to_named_object(int32 &result, int32 *params) { return (MS->fn_send_chi_to_named_object(result, params)); }
64 
fn_chi_heard_gunshot(int32 & result,int32 * params)65 mcodeFunctionReturnCodes fn_chi_heard_gunshot(int32 &result, int32 *params) { return (MS->fn_chi_heard_gunshot(result, params)); }
66 
fn_calibrate_chi(int32 & result,int32 * params)67 mcodeFunctionReturnCodes fn_calibrate_chi(int32 &result, int32 *params) { return (MS->fn_calibrate_chi(result, params)); }
68 
fn_simple_chi(int32 &,int32 *)69 mcodeFunctionReturnCodes _game_session::fn_simple_chi(int32 &, int32 *) {
70 	// simple chi that teleports around
71 
72 	Fatal_error("fn_simple_chi not supported");
73 
74 	return (IR_CONT);
75 }
76 
fn_start_chi_following(int32 &,int32 *)77 mcodeFunctionReturnCodes _game_session::fn_start_chi_following(int32 &, int32 *) {
78 	// the real chi
79 
80 	g_mission->chi_following = TRUE8;
81 
82 	chi_history = cur_history; // chi gets same point as cord
83 
84 	chi_think_mode = __FOLLOWING; // set mode
85 
86 	chi_do_mode = __THINKING; // thinking about following :)
87 
88 	chi_next_move = 100; // 100 cycles
89 
90 	Tdebug("chi.txt", "-+fn_start_chi_following - history=%d +-", chi_history);
91 
92 	permission_to_fire = FALSE8; // reset
93 
94 	return (IR_CONT);
95 }
96 
fn_stop_chi_following(int32 &,int32 *)97 mcodeFunctionReturnCodes _game_session::fn_stop_chi_following(int32 &, int32 *) {
98 	// stop her
99 	// do this before changing her to custom logics
100 
101 	g_mission->chi_following = FALSE8;
102 
103 	chi_think_mode = __NOTHING; // nowt
104 
105 	Tdebug("chi.txt", "-+fn_stop_chi_following");
106 
107 	return (IR_CONT);
108 }
109 
fn_record_player_interaction(int32 &,int32 *)110 mcodeFunctionReturnCodes _game_session::fn_record_player_interaction(int32 &, int32 *) {
111 	// write the id of interact object to player history list
112 	// advance history
113 	cur_history++;
114 	if (cur_history == MAX_player_history)
115 		cur_history = 0; // wrap
116 
117 	// record it
118 	history[cur_history].interaction = TRUE8;
119 	history[cur_history].id = M->target_id;
120 
121 	Tdebug("history.txt", "-> [%s] %d", objects->Fetch_items_name_by_number(M->target_id), M->target_id);
122 
123 	return IR_CONT;
124 }
125 
fn_send_chi_to_named_object(int32 &,int32 * params)126 mcodeFunctionReturnCodes _game_session::fn_send_chi_to_named_object(int32 &, int32 *params) {
127 	const char *object_name = (const char *)MemoryUtil::resolvePtr(params[0]);
128 	uint32 id;
129 
130 	id = objects->Fetch_item_number_by_name(object_name);
131 	if (id == 0xffffffff)
132 		Fatal_error("fn_send_chi_to_named_object - illegal object [%s]", object_name);
133 
134 	// advance history
135 	cur_history++;
136 	if (cur_history == MAX_player_history)
137 		cur_history = 0; // wrap
138 
139 	// record it
140 	history[cur_history].interaction = TRUE8;
141 	history[cur_history].id = id;
142 
143 	Tdebug("history.txt", ">> [%s] %d", object->GetName(), cur_id);
144 
145 	return IR_CONT;
146 }
147 
fn_send_chi_to_this_object(int32 &,int32 *)148 mcodeFunctionReturnCodes _game_session::fn_send_chi_to_this_object(int32 &, int32 *) {
149 	// write the id of calling object to player history list
150 
151 	// advance history
152 	cur_history++;
153 	if (cur_history == MAX_player_history)
154 		cur_history = 0; // wrap
155 
156 	// record it
157 	history[cur_history].interaction = TRUE8;
158 	history[cur_history].id = cur_id;
159 
160 	return IR_CONT;
161 }
162 
fn_fetch_chi_mode(int32 & result,int32 *)163 mcodeFunctionReturnCodes _game_session::fn_fetch_chi_mode(int32 &result, int32 *) {
164 	// returns the chi mode (__chi_think_mode) to script
165 
166 	result = (int32)chi_think_mode;
167 
168 	return IR_CONT;
169 }
170 
fn_fetch_chi_mode()171 __chi_think_mode _game_session::fn_fetch_chi_mode() {
172 	// returns the chi mode (__chi_think_mode) to engine
173 	return chi_think_mode;
174 }
175 
fn_chi_heard_gunshot(int32 &,int32 *)176 mcodeFunctionReturnCodes _game_session::fn_chi_heard_gunshot(int32 &, int32 *) {
177 	// called when chi hears a gunshot
178 
179 	// if we can see the player and same history - or, we're already in armed mode
180 	if (((chi_history == cur_history) && (g_oLineOfSight->LineOfSight(cur_id, player.Fetch_player_id()))) || (chi_do_mode == __FIGHT_HELP)) {
181 		permission_to_fire = 1; // chi can start fighting
182 	}
183 
184 	return IR_CONT;
185 }
186 
fn_calibrate_chi(int32 &,int32 * params)187 mcodeFunctionReturnCodes _game_session::fn_calibrate_chi(int32 &, int32 *params) {
188 	// set chi's near and far distances
189 
190 	// params        0       catch up distance
191 	//				1     lost player distance
192 
193 	chi_catch_up_dist = (PXreal)(params[0] * params[0]);
194 
195 	chi_lost_dist = (PXreal)(params[1] * params[1]);
196 
197 	return IR_CONT;
198 }
199 
Set_chi_permission()200 void _game_session::Set_chi_permission() {
201 	//	called by players gunshot code
202 
203 	permission_to_fire = 1; // chi can start fighting
204 }
205 
Find_a_chi_target()206 bool8 _game_session::Find_a_chi_target() {
207 	// find an ememy for chi
208 	uint32 j;
209 
210 	for (j = 0; j < number_of_voxel_ids; j++) {
211 		if ((!logic_structs[voxel_id_list[j]]->mega->dead) &&                 // alive
212 		    (logic_structs[voxel_id_list[j]]->ob_status != OB_STATUS_HELD) && // not held
213 		    (logic_structs[voxel_id_list[j]]->mega->is_evil) &&               // is evil
214 		    (Object_visible_to_camera(voxel_id_list[j])) &&                   // on screen
215 		    (g_oLineOfSight->ObjectToObject(cur_id, voxel_id_list[j], LIGHT, 0, _line_of_sight::USE_OBJECT_VALUE,
216 		                                    TRUE8))) { // can see
217 			chi_target_id = voxel_id_list[j];
218 			chi_has_target = TRUE8;
219 			Tdebug("chi_targets.txt", "chi selects [%s] as target", logic_structs[chi_target_id]->GetName());
220 			return TRUE8;
221 		}
222 	}
223 
224 	chi_has_target = FALSE8;
225 
226 	// no
227 	return FALSE8;
228 }
229 
Chi_leaves_fight_mode()230 void _game_session::Chi_leaves_fight_mode() {
231 	// set animation
232 	L->cur_anim_type = __PULL_OUT_WEAPON;
233 	I->IsAnimTable(L->cur_anim_type);
234 
235 	// get animation
236 	PXanim *anim = (PXanim *)rs_anims->Res_open(I->get_info_name(L->cur_anim_type), I->info_name_hash[L->cur_anim_type], I->base_path, I->base_path_hash); //
237 	// set to last frame
238 
239 	L->anim_pc = anim->frame_qty - 2; // if 10 frames then 10+1 (looper) == 11 meaning 9 is last displayable frame number
240 	M->next_anim_type = __NO_ANIM;
241 	chi_do_mode = __DISARM_TO_THINK; // back to thinking next cycle
242 }
243 
fn_chi(int32 &,int32 *)244 mcodeFunctionReturnCodes _game_session::fn_chi(int32 &, int32 *) {
245 	// the real chi
246 
247 	while (!Process_chi()) {
248 	};
249 
250 	return IR_REPEAT;
251 }
252 
Process_chi()253 bool8 _game_session::Process_chi() {
254 	uint32 next_room;
255 	PXreal x, z;
256 	int32 result;
257 	bool8 res;
258 	PXreal sub1, sub2, cord_dist;
259 	bool8 route_res;
260 
261 	switch (chi_do_mode) {
262 	case __ANIMATE_TO_THINK:
263 		if (Play_anim()) {
264 			chi_do_mode = __THINKING;
265 		}
266 		return TRUE8;
267 		break;
268 
269 	case __DISARM_TO_THINK:
270 		if (Play_reverse_anim()) {
271 
272 			chi_do_mode = __THINKING;
273 			Set_pose(__NOT_ARMED);
274 			Change_pose_in_current_anim_set();
275 
276 			L->cur_anim_type = __STAND;
277 			I->IsAnimTable(L->cur_anim_type);
278 		}
279 		return TRUE8;
280 		break;
281 
282 	case __GET_WEAPON_OUT:
283 		if (fight_pause)
284 			fight_pause--;
285 		else {
286 			Set_pose(__GUN);
287 			Change_pose_in_current_anim_set();
288 
289 			L->anim_pc = 0; // start here
290 			L->cur_anim_type = __PULL_OUT_WEAPON;
291 			M->next_anim_type = __STAND;
292 
293 			I->IsAnimTable(L->cur_anim_type);
294 
295 			Find_a_chi_target(); // select a target if there is one available
296 
297 			chi_do_mode = __ANIMATE_TO_FIGHT_HELP;
298 		}
299 
300 		return TRUE8;
301 		break;
302 
303 	case __ANIMATE_TO_FIGHT_HELP:
304 		if (Play_anim()) {
305 			chi_do_mode = __FIGHT_HELP;
306 		}
307 		return TRUE8;
308 		break;
309 
310 	case __TURN_RND:
311 		if (fast_face_rnd(2))
312 			chi_do_mode = __FIGHT_HELP;
313 		return TRUE8;
314 		break;
315 
316 	case __TURN_TO_FACE_OBJECT:
317 		if (fast_face_object(chi_target_id, 3)) {
318 			chi_do_mode = __FIGHT_HELP;
319 		}
320 
321 		return TRUE8;
322 		break;
323 
324 	case __INTERACT_FOLLOW: // return from interact with ladder, stair, etc., here
325 		// we then move the position on and think again
326 		// arrived
327 		chi_do_mode = __THINKING;
328 
329 		// move on to next position
330 		chi_history += 1;
331 		if (chi_history == MAX_player_history)
332 			chi_history = 0; // wrap
333 		chi_history += 1;
334 		if (chi_history == MAX_player_history)
335 			chi_history = 0; // wrap
336 
337 		chi_next_move = 12;
338 
339 		return TRUE8;
340 		break;
341 
342 	case __ROUTING:
343 		// first do a check to see if players wandered back onto our floor
344 		// if we're on same floor as we started AND we meet player then stop
345 		if ((history[chi_history].id == L->owner_floor_rect) && (L->owner_floor_rect == logic_structs[player.Fetch_player_id()]->owner_floor_rect)) {
346 			chi_do_mode = __ANIMATE_TO_THINK;
347 			chi_history = cur_history; // chi gets same point as cord
348 
349 			L->anim_pc = 0; // start here
350 			L->cur_anim_type = __WALK_TO_STAND;
351 			M->next_anim_type = __STAND;
352 
353 			return TRUE8;
354 		}
355 
356 		// animate the route
357 		if (Process_route()) {
358 			// arrived
359 			chi_do_mode = __THINKING;
360 
361 			// in-case last move pick a time to next move
362 			chi_next_move = g_icb->getRandomSource()->getRandomNumber(200 - 1);
363 			chi_next_move += 50;
364 
365 			// move on to next position
366 			chi_history += 1;
367 			if (chi_history == MAX_player_history)
368 				chi_history = 0; // wrap
369 
370 			// cancel looping flag from the routing
371 			L->looping = 0;
372 
373 			return TRUE8;
374 		}
375 		break;
376 
377 	case __CHASING:
378 		// running after cord but on same floor
379 		cord_dist = Cord_dist();
380 
381 		// animate the route
382 		if ((Process_route()) || (chi_history != cur_history) ||
383 		    (cord_dist < (PXreal)(chi_catch_up_dist))) { // check for route done or player moved on in which case we quit the route and get following
384 			// cancel looping flag from the routing
385 			L->looping = 0;
386 
387 			if (chi_history == cur_history) {
388 				// still on same floor - route has just finished naturally
389 				// if we are within the zone then we come to a stand
390 				if (cord_dist < (PXreal)(chi_lost_dist)) {
391 					// within distance - so come to a stand
392 					chi_do_mode = __ANIMATE_TO_THINK;
393 					L->anim_pc = 0; // start here
394 					L->cur_anim_type = __WALK_TO_STAND;
395 					M->next_anim_type = __STAND;
396 					chi_next_move = g_icb->getRandomSource()->getRandomNumber(200 - 1);
397 					chi_next_move += 50;
398 					return TRUE8;
399 				}
400 			}
401 			// players gone or we're going to keep running
402 			chi_do_mode = __THINKING;
403 			chi_next_move = 1;
404 
405 			return FALSE8;
406 		}
407 		break;
408 
409 	case __BUMBLING:
410 		// just ambling about the current room
411 
412 		cord_dist = Cord_dist();
413 
414 		// animate the route
415 		if ((Process_route()) || ((chi_history != cur_history)) ||
416 		    (cord_dist < (PXreal)(chi_catch_up_dist / 2))) { // check for route done or player moved on in which case we quit the route and get following
417 			// arrived
418 			chi_do_mode = __THINKING;
419 
420 			// in-case last move pick a time to next move
421 			chi_next_move = g_icb->getRandomSource()->getRandomNumber(200 - 1);
422 			chi_next_move += 50;
423 
424 			// cancel looping flag from the routing
425 			L->looping = 0;
426 
427 			if (chi_history == cur_history) {
428 				// still on same floor - route has just finished naturally
429 				// if we are within the zone then we come to a stand
430 				if (cord_dist < (PXreal)(chi_catch_up_dist)) {
431 					// within distance - so come to a stand
432 					chi_do_mode = __ANIMATE_TO_THINK;
433 					L->anim_pc = 0; // start here
434 					L->cur_anim_type = __WALK_TO_STAND;
435 					M->next_anim_type = __STAND;
436 					return TRUE8;
437 				}
438 			}
439 			return TRUE8;
440 		}
441 		break;
442 
443 	case __GO_CORD_GO:
444 		// wait for player to moveout of a lift
445 		sub1 = logic_structs[player.Fetch_player_id()]->mega->actor_xyz.x - M->actor_xyz.x;
446 		sub2 = logic_structs[player.Fetch_player_id()]->mega->actor_xyz.z - M->actor_xyz.z;
447 
448 		// dist
449 		if (((chi_history != cur_history)) || (((sub1 * sub1) + (sub2 * sub2)) > (PXreal)(100 * 100))) {
450 			// players moved
451 			Message_box("bye then");
452 			chi_next_move = 1;
453 			chi_do_mode = __THINKING;
454 		}
455 
456 		return TRUE8;
457 		break;
458 
459 	case __PAUSING:
460 		break;
461 
462 	case __FIGHT_HELP: // help the player in a fight
463 		// first see if players gone
464 		if (chi_history != cur_history) {
465 			Chi_leaves_fight_mode();
466 			return TRUE8;
467 		} else {
468 			if (!fight_pause) {
469 				// not got permission, can see player and player not armed - exit armed mode
470 				if ((!permission_to_fire) && (!MS->logic_structs[player.Fetch_player_id()]->mega->Fetch_armed_status()) &&
471 				    (g_oLineOfSight->ObjectToObject(cur_id, player.Fetch_player_id(), LIGHT, 0, _line_of_sight::USE_OBJECT_VALUE, TRUE8))) {
472 					// exit this mode
473 					Chi_leaves_fight_mode();
474 					return TRUE8;
475 				}
476 
477 				// not on camera -  then quit fight mode and catch up with player
478 				if (!Object_visible_to_camera(chi_id)) {
479 					// exit this mode
480 					Chi_leaves_fight_mode();
481 					return TRUE8;
482 				}
483 
484 				// if cant see the player but would expect to (he's moved) then forget this and catch him up
485 				if (!g_oLineOfSight->ObjectToObject(cur_id, player.Fetch_player_id(), LIGHT, 0, _line_of_sight::USE_OBJECT_VALUE, TRUE8)) {
486 					// exit this mode
487 					PXreal x2 = logic_structs[player.Fetch_player_id()]->mega->actor_xyz.x;
488 					PXreal z2 = logic_structs[player.Fetch_player_id()]->mega->actor_xyz.z;
489 					PXreal vect = PXAngleOfVector(z2 - M->actor_xyz.z, x2 - M->actor_xyz.x); // work out vector
490 
491 					if (PXfabs(L->pan - vect) < (FULL_TURN / 5)) {
492 						Chi_leaves_fight_mode();
493 						return TRUE8;
494 					}
495 				}
496 
497 				// got target but target is dead or not seeable - set no target
498 				if (chi_has_target) {
499 					if (logic_structs[chi_target_id]->mega->dead)
500 						chi_has_target = 0; // dead so no target
501 
502 					if (!Object_visible_to_camera(chi_target_id))
503 						chi_has_target = 0; // target is now off camera so forget it
504 				}
505 
506 				// do something!
507 				if ((chi_has_target) && (permission_to_fire)) {
508 					// if right angle, shoot
509 					// else turn to face
510 
511 					if (Need_to_turn_to_face_object(chi_target_id))
512 						chi_do_mode = __TURN_TO_FACE_OBJECT;
513 					else {
514 						// shoot!
515 
516 						// play gun sound
517 						if (MS->logic_structs[cur_id]->sfxVars[GUNSHOT_SFX_VAR] != 0)
518 							RegisterSound(cur_id, NULL, MS->logic_structs[cur_id]->sfxVars[GUNSHOT_SFX_VAR], gunDesc,
519 							              (int8)127); // have to use full version so we can give hash instead of string
520 						else
521 							RegisterSound(cur_id, defaultGunSfx, gunDesc); // use small version as we have string not hash
522 
523 						// dynamic light
524 						M->SetDynamicLight(1, 255, 255, 255, 0, 150, 100, 200);
525 
526 						// Hey we are shooting
527 						M->is_shooting = TRUE8;
528 
529 						L->anim_pc = 0; // start here
530 						L->cur_anim_type = __STAND_AND_SHOOT;
531 						M->next_anim_type = __STAND;
532 						I->IsAnimTable(L->cur_anim_type);
533 						chi_do_mode = __ANIMATE_TO_FIGHT_HELP;
534 						fight_pause = (uint8)(1 + g_icb->getRandomSource()->getRandomNumber(10 - 1));
535 						Call_socket(chi_target_id, "gun_shot", &result);
536 
537 						return TRUE8;
538 					}
539 				} else if (chi_has_target) {
540 					// cant shoot
541 					chi_do_mode = __TURN_TO_FACE_OBJECT;
542 				} else {
543 					// no target
544 					// try to find new target
545 					// yes, then set to turn
546 					// no, turn to player
547 
548 					if (!Find_a_chi_target()) {
549 
550 						if (!fast_face_rnd(2))
551 							chi_do_mode = __TURN_RND;
552 
553 						// cancel permission
554 						permission_to_fire = 0;
555 					} else {                                     // found a new target
556 						chi_do_mode = __TURN_TO_FACE_OBJECT; // turns to face new  target
557 					}
558 				}
559 
560 				fight_pause = (uint8)(6 + g_icb->getRandomSource()->getRandomNumber(10 - 1));
561 			} else {
562 				fight_pause--;
563 			}
564 		}
565 		break;
566 
567 	case __THINKING:
568 		switch (chi_think_mode) {
569 		case __FOLLOWING:
570 			if (M->reverse_route)
571 				Message_box("chi in reverse");
572 
573 			if (chi_history != cur_history) {
574 				// player must have moved on one or more rooms but we can be certain the rooms are properly adjacent
575 
576 				// get next room
577 				next_room = chi_history + 1;
578 				if (next_room == MAX_player_history)
579 					next_room = 0; // wrap
580 
581 				// is next entry a new floor or an object to interact with
582 				if (history[next_room].interaction == TRUE8) {
583 					// players interacted with something - a floor, lift, stairway, etc.
584 
585 					if (L->looping) {
586 						L->looping = 0;
587 					}
588 
589 					Set_motion(__MOTION_RUN);
590 
591 					res = chi_interacts(history[next_room].id, "chi");
592 					if (!res)
593 						res = chi_interacts(history[next_room].id, "interact");
594 
595 					if (!res) {                      // no interaction portal for chi - actually not possible now she can use the normal
596 						                         // 'interact' script
597 						chi_think_mode = __LOST; // oh dear, we must be lost. Not legal but lets handle it for now
598 						Tdebug("chi.txt", "chi cant follow player via %d", history[next_room].id);
599 						return TRUE8;
600 					} else {
601 						// ok, back to script
602 						// script interpretter shouldnt write a pc back
603 
604 						chi_do_mode = __INTERACT_FOLLOW;
605 
606 						return (IR_GOSUB);
607 					}
608 				} else { // players gone to an adjacent floor
609 					x = history[next_room].first_x;
610 					z = history[next_room].first_z;
611 
612 					// if player is running then chi should run
613 					// if chi is more than one behind chi should run
614 					bool8 run = TRUE8;
615 					if (Cord_dist() < (400 * 400))
616 						run = FALSE8; // walk if near
617 
618 					// 0  result
619 					// 1  x
620 					// 2  z
621 					// 3  0='walk', else 'run'
622 					// 4  0=no turn-on-spot   1=yes
623 					// 5  end on stand
624 					if (!Setup_route(result, (int32)x, (int32)z, run, __ENDB, TRUE8)) {
625 						// route failed or was no route required which in theory cant happen - so we take it as
626 						// route failed to build
627 						Tdebug("chi.txt", "  route failed");
628 						Setup_route(result, (int32)x, (int32)z, 1, __LASER, TRUE8);
629 					}
630 
631 					Tdebug("chi.txt", "  route made");
632 
633 					chi_do_mode = __ROUTING;
634 					return TRUE8;
635 				}
636 			} else {
637 				// same room - unless we started incorrectly, which we'll notice when we move
638 				// decide on something to do then?
639 
640 				if (!chi_next_move) {
641 					// create a coordinate to route to
642 					bool8 ret = Make_floor_coordinate(&x, &z);
643 					if (!ret) {
644 						chi_next_move = 36; // try again in a second
645 						return TRUE8;       // make another next cycle and try again
646 					}
647 
648 					session_barriers->Set_route_barrier_mask((int32)x - 300, (int32)x + 300, (int32)z - 300, (int32)z + 300);
649 					route_res = Setup_route(result, (int32)x, (int32)z, 0, __FULL, 1); // walk
650 					session_barriers->Clear_route_barrier_mask();
651 
652 					if (!route_res) {
653 						// route failed or was no route required which in theory cant happen - so we take it as
654 						// route failed to build
655 						Tdebug("chi.txt", "  bumble route failed");
656 
657 						chi_next_move = 36; // try again in a second
658 						return TRUE8;       // make another next cycle and try again
659 					} else {
660 						if (M->reverse_route)
661 							Message_box("chi is reversing! do it - do it now");
662 						chi_do_mode = __BUMBLING;
663 						return TRUE8;
664 					}
665 				} else {
666 					chi_next_move--; // reduce time to next
667 
668 					// see if we should fight help
669 					// either we just heard player gunshot or we can see him and he's armed
670 					if ((Object_visible_to_camera(chi_id)) && (Cord_dist() < (PXreal)(chi_lost_dist)) &&
671 					    (logic_structs[player.Fetch_player_id()]->mega->actor_xyz.y == M->actor_xyz.y))
672 						if ((permission_to_fire) || ((g_oLineOfSight->LineOfSight(cur_id, player.Fetch_player_id())) &&
673 						                             (MS->logic_structs[player.Fetch_player_id()]->mega->Fetch_armed_status()))) {
674 							// has he got gun out?
675 							chi_do_mode = __GET_WEAPON_OUT;
676 
677 							if (!permission_to_fire)
678 								fight_pause = 3; // shes seen player arming - wait a bit
679 							else
680 								fight_pause = 0; // heard a shot - arm straight away
681 
682 							permission_to_fire = 0; // when armed - must see player shoot someone before she will shoot
683 
684 							return TRUE8;
685 						}
686 
687 					// move if player a distance away
688 					if ((Cord_dist() > (PXreal)(chi_lost_dist)) && (logic_structs[player.Fetch_player_id()]->mega->actor_xyz.y == M->actor_xyz.y) &&
689 					    (prev_save_state)) {
690 						//                          0  result
691 						//                          1   x
692 						//                          2   z
693 						//                          3   0='walk', else 'run'
694 						//                          4   0=no turn-on-spot   1=yes
695 						//                          5  end on stand
696 						x = logic_structs[player.Fetch_player_id()]->mega->actor_xyz.x;
697 						z = logic_structs[player.Fetch_player_id()]->mega->actor_xyz.z;
698 
699 						bool8 run = TRUE8;
700 						bool8 eos = FALSE8;
701 						if (Cord_dist() < (400 * 400)) {
702 							run = FALSE8; // walk if near
703 							eos = TRUE8;  // end on stand
704 						}
705 
706 						// set a barrier mask :(
707 						session_barriers->Set_route_barrier_mask((int32)x - 450, (int32)x + 450, (int32)z - 450, (int32)z + 450);
708 						route_res = Setup_route(result, (int32)x, (int32)z, run, __FULL, eos);
709 						session_barriers->Clear_route_barrier_mask();
710 
711 						if (!route_res) {
712 							// route failed or was no route required which in theory cant happen - so we take it
713 							// as route failed to build
714 							if (result == FALSE8)
715 								Setup_route(result, (int32)x, (int32)z, run, __LASER, eos);
716 							else
717 								return TRUE8; // make another next cycle and try again
718 						} else {
719 							Tdebug("chi.txt", "  route made");
720 							if (M->reverse_route)
721 								Message_box("chi is reversing! do it - do it now");
722 							chi_do_mode = __CHASING;
723 							return FALSE8;
724 						}
725 					}
726 
727 					// cancel permission - so we catch player gunshots immediately
728 					permission_to_fire = 0;
729 				}
730 			}
731 			break;
732 
733 		case __LOST:
734 			// we're lost - but if the player turns up then we can restart
735 			if (logic_structs[player.Fetch_player_id()]->owner_floor_rect == L->owner_floor_rect) {
736 				chi_history = cur_history;    // chi gets same point as cord
737 				chi_think_mode = __FOLLOWING; // set mode
738 				Tdebug("chi.txt", "chi is finds cord again - chi=%d, player=%d", chi_history, cur_history);
739 			}
740 			/* fall through */
741 
742 		case __NOTHING:
743 			// dummy mode for when switched to custom logics
744 
745 			L->cur_anim_type = __STAND;
746 			L->anim_pc = 0;
747 
748 			return (TRUE8);
749 
750 			break;
751 
752 		default:
753 			Fatal_error("ilegal chi mode");
754 			break;
755 		}
756 		break;
757 	}
758 
759 	return (TRUE8);
760 }
761 
Cord_dist()762 PXreal _game_session::Cord_dist() {
763 	// return cords distance from chi (or other mega depending upon where called)
764 	PXreal sub1,
765 	sub2;
766 
767 	sub1 = logic_structs[player.Fetch_player_id()]->mega->actor_xyz.x - M->actor_xyz.x;
768 	sub2 = logic_structs[player.Fetch_player_id()]->mega->actor_xyz.z - M->actor_xyz.z;
769 
770 	// dist
771 	return ((sub1 * sub1) + (sub2 * sub2));
772 }
773 
fn_check_for_chi(int32 & result,int32 *)774 mcodeFunctionReturnCodes _game_session::fn_check_for_chi(int32 &result, int32 *) {
775 	// player wants to go down a lift so we must check to see if we need to tell chi to come over
776 
777 	// returns   0 no need to wait for chi
778 	//				1 need to wait for chi
779 
780 	// default to no
781 	result = 0;
782 
783 	// check there is a following chi
784 	if (!is_there_a_chi)
785 		return IR_CONT;
786 
787 	// not if held
788 	if (logic_structs[chi_id]->ob_status == OB_STATUS_HELD)
789 		return IR_CONT;
790 
791 	if (g_mission->chi_following) {
792 		if (logic_structs[chi_id]->mega->dead) {
793 			// chi is dead
794 			result = 0;
795 			return IR_CONT;
796 		}
797 
798 		result = 1;
799 		return IR_CONT;
800 		// chi's got to come over and onto the platform
801 	}
802 
803 	// not in follow mode
804 	return IR_CONT;
805 }
806 
fn_wait_for_chi(int32 &,int32 *)807 mcodeFunctionReturnCodes _game_session::fn_wait_for_chi(int32 &, int32 *) {
808 	// waits for chi to register as having arrived
809 	// this happens when she resets her following variables
810 
811 	if (chi_history == cur_history) {
812 		// she's arrived
813 		return IR_CONT;
814 	}
815 
816 	return IR_REPEAT;
817 }
818 
fn_chi_wait_for_player_to_move(int32 &,int32 *)819 mcodeFunctionReturnCodes _game_session::fn_chi_wait_for_player_to_move(int32 &, int32 *) {
820 	// set to go-cord-go wait mode
821 	// used after lift movement
822 
823 	chi_history = cur_history; // chi gets same point as cord
824 
825 	return IR_CONT;
826 }
827 
fn_register_chi(int32 &,int32 *)828 mcodeFunctionReturnCodes _game_session::fn_register_chi(int32 &, int32 *) {
829 	// tell engine there is a chi object
830 
831 	if (is_there_a_chi)
832 		Fatal_error("double call to fn_register_chi");
833 
834 	Tdebug("chi.txt", "%s registers as chi", object->GetName());
835 
836 	is_there_a_chi = TRUE8;
837 	chi_id = cur_id;
838 
839 	return IR_CONT;
840 }
841 
Make_floor_coordinate(PXreal * x,PXreal * z)842 bool8 _game_session::Make_floor_coordinate(PXreal *x, PXreal *z) {
843 	// make a coordinate for the floor
844 
845 	if (!local_history_count)
846 		return FALSE8;
847 
848 	int32 choice = g_icb->getRandomSource()->getRandomNumber(local_history_count - 1);
849 
850 	*x = local_history[choice].x;
851 	*z = local_history[choice].z;
852 
853 	return TRUE8;
854 }
855 
856 } // End of namespace ICB
857