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/common/px_floor_map.h"
30 #include "engines/icb/common/px_linkeddatafile.h"
31 #include "engines/icb/common/px_scriptengine.h"
32 #include "engines/icb/common/ptr_util.h"
33 #include "engines/icb/floors.h"
34 #include "engines/icb/debug.h"
35 #include "engines/icb/route_manager.h"
36 #include "engines/icb/global_objects.h"
37 #include "engines/icb/session.h"
38 #include "engines/icb/global_switches.h"
39 #include "engines/icb/res_man.h"
40 #include "engines/icb/mission.h"
41 
42 namespace ICB {
43 
44 #define ROUTE_CLOSE 10 * REAL_ONE
45 
fn_route_to_marker(int32 & result,int32 * params)46 mcodeFunctionReturnCodes fn_route_to_marker(int32 &result, int32 *params) {
47 	return (MS->fn_route_to_marker(result, params));
48 }
49 
fn_route_to_nico(int32 & result,int32 * params)50 mcodeFunctionReturnCodes fn_route_to_nico(int32 &result, int32 *params) {
51 	return (MS->fn_route_to_nico(result, params));
52 }
53 
fn_tiny_route(int32 & result,int32 * params)54 mcodeFunctionReturnCodes fn_tiny_route(int32 &result, int32 *params) {
55 	return (MS->fn_tiny_route(result, params));
56 }
57 
fn_sharp_route(int32 & result,int32 * params)58 mcodeFunctionReturnCodes fn_sharp_route(int32 &result, int32 *params) {
59 	return (MS->fn_sharp_route(result, params));
60 }
61 
fn_route_to_near_mega(int32 & result,int32 * params)62 mcodeFunctionReturnCodes fn_route_to_near_mega(int32 &result, int32 *params) {
63 	return (MS->fn_route_to_near_mega(result, params));
64 }
65 
fn_interact_near_mega(int32 & result,int32 * params)66 mcodeFunctionReturnCodes fn_interact_near_mega(int32 &result, int32 *params) {
67 	return (MS->fn_interact_near_mega(result, params));
68 }
69 
fn_sharp_route_to_near_mega(int32 & result,int32 * params)70 mcodeFunctionReturnCodes fn_sharp_route_to_near_mega(int32 &result, int32 *params) {
71 	return (MS->fn_sharp_route_to_near_mega(result, params));
72 }
73 
fn_spectre_route_to_mega(int32 & result,int32 * params)74 mcodeFunctionReturnCodes fn_spectre_route_to_mega(int32 &result, int32 *params) {
75 	return (MS->fn_spectre_route_to_mega(result, params));
76 }
77 
fn_laser_route(int32 & result,int32 * params)78 mcodeFunctionReturnCodes fn_laser_route(int32 &result, int32 *params) {
79 	return (MS->fn_laser_route(result, params));
80 }
81 
fn_room_route(int32 & result,int32 * params)82 mcodeFunctionReturnCodes fn_room_route(int32 &result, int32 *params) {
83 	return (MS->fn_room_route(result, params));
84 }
85 
Reset_route_manager()86 void _game_session::Reset_route_manager() {
87 	// reset the router object - called by session constructor
88 
89 	// remove used routes - just keeps the memory limit down
90 
91 	Zdebug("--new sesssion: reseting route manager service--");
92 }
93 
Is_route_required(PXreal startx,PXreal startz,PXreal destx,PXreal destz)94 bool8 _game_session::Is_route_required(PXreal startx, PXreal startz, PXreal destx, PXreal destz) {
95 	// check if route is really close or exact
96 	// if close then adjust
97 
98 	if ((PXfabs(startx - destx) < ROUTE_CLOSE) && (PXfabs(startz - destz) < ROUTE_CLOSE)) {
99 		M->actor_xyz.x = destx;
100 		M->actor_xyz.z = destz;
101 
102 		//		no route is required
103 		return (FALSE8);
104 	}
105 
106 	// a route is required
107 	return (TRUE8);
108 }
109 
Create_initial_route(__rtype type)110 void _game_session::Create_initial_route(__rtype type) {
111 	// Changed last parameter to int32 from bool to stop a performance warning.  Other bools are passed as int anyway,
112 	// so this is more consistent and faster.
113 
114 	// create an initial route for character 'id'
115 	// this route will replace any route that maybe partway complete at this moment (chr$ can change their plans)
116 	// the route is of a fixed type specified by the _route_type structure
117 	// after building a route we return allowing fn_ functions to make any decisions/selections they wish before pressing on with animation
118 
119 	_route_stat res;
120 	uint32 time = 0;
121 
122 	if ((g_px->logic_timing) && (g_px->mega_timer))
123 		time = GetMicroTimer();
124 
125 	Zdebug("create_initial_route");
126 
127 	if (!Is_route_required(M->m_main_route.request_form.initial_x, M->m_main_route.request_form.initial_z, M->m_main_route.request_form.dest_x,
128 	                       M->m_main_route.request_form.dest_z)) {
129 		M->m_main_route.request_form.error = __RR_NO_ROUTE_REQUIRED;
130 		Zdebug("no route required");
131 		return;
132 	}
133 
134 	//	set extrapolation size
135 	troute.extrap_size = M->extrap_size;
136 
137 	M->m_phase = RM_MAIN;
138 
139 	// reset the routers barrier list
140 	troute.Reset_barrier_list();
141 
142 	switch (type) {
143 	case __FULL:
144 		MS->session_barriers->Form_route_barrier_list(M->m_main_route.request_form.initial_x, M->m_main_route.request_form.character_y,
145 		                                              M->m_main_route.request_form.initial_z, M->m_main_route.request_form.dest_x, M->m_main_route.request_form.dest_z);
146 		break;
147 
148 	case __ENDB:
149 		MS->session_barriers->Form_parent_barrier_list(M->m_main_route.request_form.dest_x, M->m_main_route.request_form.character_y, M->m_main_route.request_form.dest_z);
150 		break;
151 
152 	default:
153 		break;
154 	}
155 
156 	// now make the route
157 	res = troute.Calc_route(M->m_main_route.request_form.initial_x, M->m_main_route.request_form.initial_z, M->m_main_route.request_form.dest_x,
158 	                        M->m_main_route.request_form.dest_z);
159 
160 	if (res == __PRIM_ROUTE_OK) {
161 		Zdebug("route ok");
162 
163 		// get the route - pass the address of a pointer to a _point where the route can be new'ed and copied
164 		troute.Give_route(&M->m_main_route);
165 
166 		// get barriers for nethack diagnostics
167 		if (type != __LASER)
168 			troute.Give_barrier_list(&M->m_main_route);
169 
170 		if (L->pan >= HALF_TURN)
171 			L->pan -= FULL_TURN;
172 		else if (L->pan <= -HALF_TURN)
173 			L->pan += FULL_TURN;
174 
175 		Calc_dist_and_pan(M->actor_xyz.x, M->actor_xyz.z, &M->m_main_route);
176 
177 		M->m_main_route.request_form.error = __ROUTE_REQUEST_OK;
178 	} else {
179 		Zdebug("route failed");
180 		M->m_main_route.request_form.error = __ROUTE_REQUEST_PRIM_FAILED;
181 	}
182 
183 	if ((g_px->logic_timing) && (g_px->mega_timer)) {
184 		time = GetMicroTimer() - time;
185 		L->slowest_cycle_time = time;
186 	}
187 }
188 
Process_route()189 bool8 _game_session::Process_route() {
190 	// processes the route that has been built by Create_initial_route
191 	// animate along this characters route
192 
193 	// returns   0 still going
194 	//				1 finished
195 
196 	_route_description *route;
197 	bool8 res;
198 
199 	route = &M->m_main_route;
200 
201 	// gosub turning
202 	if ((M->target_pan) && (!L->auto_panning)) { // animated pan - as opposed to engine auto panning
203 		Animate_turn_to_pan(__TURN_ON_THE_SPOT_CLOCKWISE, 1);
204 
205 		return (0);
206 		//      note, when this anim finishes we must snap the x,z back to what they were before the turn began
207 	} else if (route->arrived) {
208 		// must have finished route and set up a slow out anim - now run that anim
209 		if (route->request_form.finish_on_stand) {
210 			res = Play_anim();
211 		}
212 
213 		else
214 			res = Play_anim_with_no_movement();
215 
216 		return (res);
217 
218 		// remove the phase so headup diagnostics disappear now the route is effectively done
219 		M->m_phase = RM_NONE;
220 
221 		return (1); // no slow-out so we're done
222 	}
223 
224 	// ok, process that point-to-point route!
225 	if (Animate_points(route)) {
226 		// route has finished
227 		// has a slow-out been requested?
228 
229 		if (route->request_form.finish_on_stand) {
230 			// set up follow on animation as requested
231 
232 			// set up walk-to stand + stand
233 
234 			Soft_start_with_double_link(__WALK_TO_STAND, __WALK_TO_OTHER_STAND_LEFT_LEG, __STAND);
235 
236 			res = Play_anim();
237 
238 			// if more to do then return
239 			return (res);
240 
241 			// else drop down and return fnished after removing the phase
242 		} else if (route->request_form.finish_on_null_stand) { // play link anim WITHOUT coordinate movement; for interaction accuracy
243 			// set up follow on animation as requested
244 			// set up walk-to stand + stand
245 
246 			Soft_start_with_double_link(__WALK_TO_STAND, __WALK_TO_OTHER_STAND_LEFT_LEG, __STAND);
247 
248 			res = Play_anim_with_no_movement();
249 
250 			// if more to do then return
251 			return (res);
252 
253 			// else drop down and return fnished after removing the phase
254 		}
255 
256 		// remove the phase so headup diagnostics disappear now the route is effectively done
257 		M->m_phase = RM_NONE;
258 
259 		return (1); // no slow-out so we're done
260 	}
261 	// still auto-routing
262 	return (0);
263 }
264 
Fetch_route_desc(uint32 id)265 _route_description *_game_session::Fetch_route_desc(uint32 id) {
266 	// return a pointer to a built route
267 	// this is used to draw into the plan viewer
268 	// returns a 0 if there is no route
269 
270 	// no route at all
271 	if (!logic_structs[id]->mega->m_phase)
272 		return (0);
273 
274 	return (&logic_structs[id]->mega->m_main_route);
275 }
276 
___init()277 void _route_description::___init() {
278 	// nethack diagnostics
279 	if (diag_bars)
280 		delete[] diag_bars;
281 	diag_bars = 0;
282 
283 	number_of_diag_bars = 0;
284 
285 	arrived = FALSE8; // when route has been animated this is set to YES ready for optional slow-out animation
286 
287 	total_points = 0; // in-case fails for viewers
288 }
289 
fn_tiny_route(int32 & result,int32 * params)290 mcodeFunctionReturnCodes _game_session::fn_tiny_route(int32 &result, int32 *params) {
291 	// auto-route a mega character characters x,z to specified x1,z1 from the same or adjoining floor rects
292 	// player or mega
293 
294 	// params    0   x   INT32's to be cast to floats :| hmmm...
295 	//			1  z
296 	//			2  0='walk', else 'run'
297 	//			3  0=no turn-on-spot   1=yes
298 
299 	// return    IR_CONT or
300 	//			IR_REPEAT
301 
302 	int32 sub1, sub2, len;
303 	bool8 route_res;
304 
305 	if (L->looping < 2) {
306 
307 		Tdebug("route_async.txt", "%s tiny", object->GetName());
308 
309 		// check for free router
310 		if (Is_router_busy())
311 			return IR_REPEAT;
312 
313 		sub1 = params[0] - (int32)M->actor_xyz.x;
314 		sub2 = params[1] - (int32)M->actor_xyz.z;
315 
316 		if (sub1 < 0)
317 			sub1 = (0 - sub1);
318 		if (sub2 < 0)
319 			sub2 = (0 - sub2);
320 
321 		len = sub1 + sub2;
322 
323 		session_barriers->Set_route_barrier_mask((int32)M->actor_xyz.x - len, (int32)M->actor_xyz.x + len, (int32)M->actor_xyz.z - len, (int32)M->actor_xyz.z + len);
324 		route_res = Setup_route(result, params[0], params[1], params[2], __FULL, TRUE8);
325 		session_barriers->Clear_route_barrier_mask();
326 		if (!route_res) {
327 			if (!result) { // failed
328 				Setup_route(result, params[0], params[1], params[2], __LASER, TRUE8); // must succeed
329 			} else {
330 				//      not looping any int32er
331 				L->looping = 0;
332 				result = TRUE8;
333 				return IR_CONT; // finished so send script on
334 			}
335 		}
336 	}
337 
338 	// animate the route
339 	if (Process_route()) {
340 		//		not looping any int32er
341 		L->looping = 0;
342 		result = TRUE8;
343 		return (IR_CONT); // finished so send script on
344 	}
345 
346 	// not finished, so see you next cycle
347 	return (IR_REPEAT);
348 }
349 
fn_room_route(int32 & result,int32 * params)350 mcodeFunctionReturnCodes _game_session::fn_room_route(int32 &result, int32 *params) {
351 	// auto-route a mega character characters x,z to specified x1,z1 in an adjacent room
352 	// player or mega
353 	// doesnt end on stand
354 
355 	// params    0   x
356 	//			1  z
357 	//			2  0='walk', else 'run'
358 	//			3  end on stand (i.e. tiny or sharp)
359 
360 	// return    IR_CONT or
361 	//			IR_REPEAT
362 
363 	if (L->looping < 2) {
364 		// check for free router
365 		if (Is_router_busy()) {
366 			return IR_REPEAT;
367 		}
368 
369 		if (!Setup_route(result, params[0], params[1], params[2], __ENDB, params[3])) {
370 			L->looping = 0;
371 			return (IR_CONT);
372 		}
373 	}
374 
375 	// animate the route
376 	if (Process_route()) {
377 		// not looping any int32er
378 		L->looping = 0;
379 		result = TRUE8;
380 		return (IR_CONT); // finished so send script on
381 	}
382 
383 	// not finished, so see you next cycle
384 	return (IR_REPEAT);
385 }
386 
fn_sharp_route(int32 & result,int32 * params)387 mcodeFunctionReturnCodes _game_session::fn_sharp_route(int32 &result, int32 *params) {
388 	// auto-route a mega character characters x,z to specified x1,z1 from the same or adjoining floor rects
389 	// player or mega
390 	// doesnt end on stand
391 
392 	// params    0   x   INT32's to be cast to floats :| hmmm...
393 	//			1  z
394 	//			2  0='walk', else 'run'
395 	//			3  0=no turn-on-spot   1=yes
396 
397 	// return    IR_CONT or
398 	//			IR_REPEAT
399 
400 	int32 sub1, sub2, len;
401 	bool8 route_res;
402 
403 	if (L->looping < 2) {
404 		// check for free router
405 		if (Is_router_busy()) {
406 			return IR_REPEAT;
407 		}
408 
409 		sub1 = params[0] - (int32)M->actor_xyz.x;
410 		sub2 = params[1] - (int32)M->actor_xyz.z;
411 
412 		if (sub1 < 0)
413 			sub1 = (0 - sub1);
414 		if (sub2 < 0)
415 			sub2 = (0 - sub2);
416 
417 		len = sub1 + sub2;
418 
419 		session_barriers->Set_route_barrier_mask((int32)M->actor_xyz.x - len, (int32)M->actor_xyz.x + len, (int32)M->actor_xyz.z - len, (int32)M->actor_xyz.z + len);
420 		route_res = Setup_route(result, params[0], params[1], params[2], __FULL, FALSE8);
421 		session_barriers->Clear_route_barrier_mask();
422 		if (!route_res) {
423 			if (!result) { // failed
424 				Setup_route(result, params[0], params[1], params[2], __LASER, FALSE8); // must succeed
425 			} else {
426 				//      not looping any int32er
427 				L->looping = 0;
428 				result = TRUE8;
429 				return IR_CONT; // finished so send script on
430 			}
431 		}
432 	}
433 
434 	// animate the route
435 	if (Process_route()) {
436 		// not looping any int32er
437 		L->looping = 0;
438 		result = TRUE8;
439 		return (IR_CONT); // finished so send script on
440 	}
441 
442 	// not finished, so see you next cycle
443 	return (IR_REPEAT);
444 }
445 
fn_laser_route(int32 & result,int32 * params)446 mcodeFunctionReturnCodes _game_session::fn_laser_route(int32 &result, int32 *params) {
447 	// auto-route a mega character characters x,z to specified x1,z1 from the same or adjoining floor rects
448 	// player or mega
449 	// doesnt end on stand
450 
451 	// params    0   x   INT32's to be cast to floats :| hmmm...
452 	//			1  z
453 	//			2  0='walk', else 'run'
454 	//			3  end on stand (i.e. tiny or sharp)
455 
456 	// return    IR_CONT or
457 	//			IR_REPEAT
458 
459 	if (L->looping < 2) {
460 		// check for free router
461 		if (Is_router_busy()) {
462 			return IR_REPEAT;
463 		}
464 
465 		if (!Setup_route(result, params[0], params[1], params[2], __LASER, params[3])) {
466 			L->looping = 0;
467 			return (IR_CONT);
468 		}
469 	}
470 
471 	// animate the route
472 	if (Process_route()) {
473 		// not looping any int32er
474 		L->looping = 0;
475 		result = TRUE8;
476 		return (IR_CONT); // finished so send script on
477 	}
478 
479 	// not finished, so see you next cycle
480 	return (IR_REPEAT);
481 }
482 
fn_route_to_nico(int32 & result,int32 * params)483 mcodeFunctionReturnCodes _game_session::fn_route_to_nico(int32 &result, int32 *params) {
484 	// auto-route a mega character characters x,z to nico x1,z1 from the same or adjoining floor rects
485 	// walk to a nico marker
486 	// player or mega
487 
488 	// params    0   nico
489 	//			1  0='walk', else 'run'
490 	//			2  0=no turn-on-spot   1=yes
491 
492 	// return    IR_CONT or
493 	//			IR_REPEAT
494 
495 	_feature_info *monica;
496 
497 	const char *nico_name = NULL;
498 	if (params && params[0]) {
499 		nico_name = (const char *)MemoryUtil::resolvePtr(params[0]);
500 	}
501 
502 	if (L->looping < 2) {
503 		// check for free router
504 		if (Is_router_busy()) {
505 			return IR_REPEAT;
506 		}
507 
508 		monica = (_feature_info *)features->Try_fetch_item_by_name(nico_name);
509 
510 		if (!monica)
511 			Fatal_error("fn_route_to_nico - object [%s] cant find nico [%s]", object->GetName(), nico_name);
512 
513 		// build route
514 		if (!Setup_route(result, (int32)monica->x, (int32)monica->z, params[1], __FULL, TRUE8)) {
515 			if (result == 0)
516 				Message_box("fn_route_to_nico nico found but route failed");
517 
518 			L->looping = 0;
519 			return (IR_CONT);
520 		}
521 	}
522 
523 	// animate the route
524 	if (Process_route()) {
525 		// not looping any int32er
526 		L->looping = 0;
527 		result = TRUE8;
528 		return (IR_CONT); // finished so send script on
529 	}
530 
531 	// not finished, so see you next cycle
532 	return (IR_REPEAT);
533 }
534 
fn_interact_near_mega(int32 & result,int32 * params)535 mcodeFunctionReturnCodes _game_session::fn_interact_near_mega(int32 &result, int32 *params) {
536 	// auto-route a mega character characters x,z to nico x1,z1 from the same or adjoining floor rects
537 	// walk to a nico marker
538 	// player or mega
539 
540 	// params
541 	//			0  0='walk', else 'run'
542 	//			1  0=no turn-on-spot   1=yes
543 	//			2  distance within which we stop
544 
545 	// return    IR_CONT or
546 	//			IR_REPEAT
547 
548 	PXreal sub1, sub2, len;
549 
550 	// check we are within the distance and stop us if so
551 	sub1 = logic_structs[M->target_id]->mega->actor_xyz.x - M->actor_xyz.x;
552 	sub2 = logic_structs[M->target_id]->mega->actor_xyz.z - M->actor_xyz.z;
553 
554 	//	dist
555 	len = (PXreal)((sub1 * sub1) + (sub2 * sub2));
556 
557 	if (L->looping < 2) {
558 		// check for free router
559 		if (Is_router_busy()) {
560 			return IR_REPEAT;
561 		}
562 
563 		// dont even build route if too close already
564 		if (len < (PXreal)(params[2] * params[2])) {
565 			L->looping = 0;
566 			result = TRUE8;
567 			return (IR_CONT); // finished so send script on
568 		}
569 
570 		// build route
571 		if (!Setup_route(result, (int32)logic_structs[M->target_id]->mega->actor_xyz.x, (int32)logic_structs[M->target_id]->mega->actor_xyz.z, params[0], __FULL, TRUE8)) {
572 			if (result == 0)
573 				Message_box("fn_route_to_nico nico found but route failed");
574 
575 			L->looping = 0;
576 			return (IR_CONT);
577 		}
578 	}
579 
580 	// animate the route
581 	if (Process_route()) {
582 		// not looping any int32er
583 		L->looping = 0;
584 		result = TRUE8;
585 		return (IR_CONT); // finished so send script on
586 	}
587 
588 	if (len < (PXreal)(params[2] * params[2])) {
589 		M->m_main_route.dist_left = FLOAT_ZERO;
590 		M->m_main_route.current_position = (M->m_main_route.total_points - 1);
591 	}
592 
593 	// not finished, so see you next cycle
594 	return (IR_REPEAT);
595 }
596 
fn_spectre_route_to_mega(int32 & result,int32 * params)597 mcodeFunctionReturnCodes _game_session::fn_spectre_route_to_mega(int32 &result, int32 *params) {
598 	// auto-route a mega character to named mega to within specified distance - rebuild the route if the target moves away from original position
599 
600 	// params    0   mega object name
601 	//			1  0='walk', else 'run'
602 	//			2  0=no turn-on-spot   1=yes
603 	//			3  distance within which we stop
604 	//			4  re-route trigger distance
605 
606 	PXreal sub1, sub2, len, len2 = REAL_ONE;
607 	uint32 id;
608 #define SPECTRE_INITIAL_ROUTE_TWEEK 4242
609 #define SPECTRE_ROUTE_TWEEK 4269
610 	int32 tx;
611 	int32 tz;
612 	bool8 route_res;
613 
614 	const char *mega_name = (const char *)MemoryUtil::resolvePtr(params[0]);
615 
616 	// get object to check
617 	id = objects->Fetch_item_number_by_name(mega_name);
618 	if (id == 0xffffffff)
619 		Fatal_error("fn_spectre_route_to_mega - illegal object [%s]", mega_name);
620 
621 	// check for starting off on a different floor - route straight to player
622 	if ((!L->looping) && (L->owner_floor_rect != logic_structs[id]->owner_floor_rect)) {
623 		L->list[1] = SPECTRE_INITIAL_ROUTE_TWEEK;
624 		// build route
625 		if (!Setup_route(result, (int32)logic_structs[id]->mega->actor_xyz.x, (int32)logic_structs[id]->mega->actor_xyz.z, params[1], __ENDB, 0)) { // sharp
626 			if (result == 0)
627 				Message_box("fn_spectre_route_to_mega route failed");
628 			return IR_CONT;
629 		}
630 	}
631 
632 	// check we are within the distance and stop us if so
633 	sub1 = logic_structs[id]->mega->actor_xyz.x - M->actor_xyz.x;
634 	sub2 = logic_structs[id]->mega->actor_xyz.z - M->actor_xyz.z;
635 	len = (PXreal)((sub1 * sub1) + (sub2 * sub2));
636 
637 	// are we close enough to finish here?
638 	if (len < (PXreal)(params[3] * params[3])) {
639 		L->looping = 0;
640 		return IR_CONT; // yeah
641 	}
642 
643 	if (L->owner_floor_rect == logic_structs[id]->owner_floor_rect) {
644 		L->list[0] = cur_history; // cancel tweeky catch up mode
645 		L->list[1] = 0; // cancel follow modes
646 	}
647 
648 	// see if the target has moved
649 	// get distance from targets original position
650 	if (L->looping) {
651 		sub1 = logic_structs[id]->mega->actor_xyz.x - M->pushed_actor_xyz.x;
652 		sub2 = logic_structs[id]->mega->actor_xyz.z - M->pushed_actor_xyz.z;
653 		len2 = (PXreal)((sub1 * sub1) + (sub2 * sub2));
654 	}
655 
656 	// normal same floor following
657 	if ((L->owner_floor_rect == logic_structs[id]->owner_floor_rect) && ((!L->looping) || (len2 > (PXreal)(params[4] * params[4])))) {
658 		M->pushed_actor_xyz.x = logic_structs[id]->mega->actor_xyz.x;
659 		M->pushed_actor_xyz.z = logic_structs[id]->mega->actor_xyz.z;
660 		M->reverse_route = FALSE8;
661 
662 		//		build route
663 		//		get target coords
664 		tx = (int32)logic_structs[id]->mega->actor_xyz.x;
665 		tz = (int32)logic_structs[id]->mega->actor_xyz.z;
666 
667 		//		route with a mask - the mask will be the retrigger dist; so should be fine but will occlude some in huge rooms
668 		session_barriers->Set_route_barrier_mask((int32)tx - params[4], (int32)tx + params[4], (int32)tz - params[4], (int32)tz + params[4]);
669 		route_res = Setup_route(result, tx, tz, params[1], __FULL, 0);
670 		session_barriers->Clear_route_barrier_mask();
671 
672 		if (!route_res) { // sharp
673 			if (result == 0) {
674 				if (!Setup_route(result, (int32)logic_structs[id]->mega->actor_xyz.x, (int32)logic_structs[id]->mega->actor_xyz.z, params[1], __LASER, 0)) {
675 					if (result == 0) {
676 						L->looping = 0;
677 						return IR_CONT;
678 					}
679 				}
680 			}
681 		}
682 		return IR_REPEAT;
683 	}
684 
685 	// check for different floors - route to players exit point - nice straight line
686 	if ((!L->list[1]) && (L->owner_floor_rect != logic_structs[id]->owner_floor_rect)) {
687 		// was on same floor but now gone
688 		// go to coordinate where he left this room
689 
690 		L->list[1] = SPECTRE_ROUTE_TWEEK;
691 		if (!Setup_route(result, (int32)spectre_hist[L->owner_floor_rect].x, (int32)spectre_hist[L->owner_floor_rect].z, params[1], __LASER, 0)) { // sharp
692 			if (result == 0)
693 				Message_box("fn_spectre_route_to_mega route failed");
694 
695 			L->looping = 0;
696 			return IR_CONT;
697 		}
698 	}
699 
700 	// animate the route
701 	if (Process_route()) {
702 
703 		// just finished special to-door route - now do a super route through door
704 		if (L->list[1] == SPECTRE_ROUTE_TWEEK) {
705 			// player went and we're now at the door where he left
706 
707 			L->list[0] = L->list[0] + 1;
708 			if (L->list[0] == MAX_player_history)
709 				L->list[0] = 0; // wrap
710 
711 			// do a super route to cross floor
712 			// use a mask - we can, because the coordinate is at the door
713 			tx = (int32)history[L->list[0]].first_x;
714 			tz = (int32)history[L->list[0]].first_z;
715 			session_barriers->Set_route_barrier_mask((int32)tx - 200, (int32)tx + 200, (int32)tz - 200, (int32)tz + 200);
716 			route_res = Setup_route(result, tx, tz, params[1], __ENDB, 0);
717 			session_barriers->Clear_route_barrier_mask();
718 
719 			if (!route_res) { // sharp
720 				if (result == 0) {
721 					if (!Setup_route(result, (int32)history[L->list[0]].first_x, (int32)history[L->list[0]].first_z, params[1], __LASER, 0)) { // sharp
722 						if (result == 0) {
723 							L->looping = 0;
724 							return IR_CONT;
725 						}
726 					}
727 				}
728 			}
729 			return IR_REPEAT;
730 		}
731 
732 		//		not looping any int32er
733 		L->list[1] = 0; // clear initail-route-tweek
734 		L->looping = 0;
735 		result = TRUE8;
736 		return IR_CONT; // finished so send script on
737 	}
738 
739 	// not finished, so see you next cycle
740 	return IR_REPEAT;
741 }
742 
fn_sharp_route_to_near_mega(int32 & result,int32 * params)743 mcodeFunctionReturnCodes _game_session::fn_sharp_route_to_near_mega(int32 &result, int32 *params) {
744 	// auto-route a mega character characters x,z to nico x1,z1 from the same or adjoining floor rects
745 	// walk to a nico marker
746 	// player or mega
747 
748 	// params    0   mega object name
749 	//			1  0='walk', else 'run'
750 	//			2  0=no turn-on-spot   1=yes
751 	//			3  distance within which we stop
752 
753 	// return    IR_CONT or
754 	//			IR_REPEAT
755 
756 	const char *mega_name = (const char *)MemoryUtil::resolvePtr(params[0]);
757 
758 	return Route_to_near_mega_core(mega_name, params[1], params[2], params[3], FALSE8, result);
759 }
760 
fn_route_to_near_mega(int32 & result,int32 * params)761 mcodeFunctionReturnCodes _game_session::fn_route_to_near_mega(int32 &result, int32 *params) {
762 	// auto-route a mega character characters x,z to nico x1,z1 from the same or adjoining floor rects
763 	// walk to a nico marker
764 	// player or mega
765 
766 	// params    0   mega object name
767 	//			1  0='walk', else 'run'
768 	//			2  0=no turn-on-spot   1=yes
769 	//			3  distance within which we stop
770 
771 	// return    IR_CONT or
772 	//			IR_REPEAT
773 
774 	const char *mega_name = (const char *)MemoryUtil::resolvePtr(params[0]);
775 
776 	return Route_to_near_mega_core(mega_name, params[1], params[2], params[3], TRUE8, result);
777 }
778 
Route_to_near_mega_core(const char * name,int32 run,int32 initial_turn,uint32 dist,int32 end_on_stand,int32 & result)779 mcodeFunctionReturnCodes _game_session::Route_to_near_mega_core(const char *name, int32 run, int32 initial_turn, uint32 dist, int32 end_on_stand, int32 &result) {
780 	PXreal sub1, sub2, x, z;
781 	uint32 id;
782 	_feature_info *monica;
783 	bool8 route_res;
784 	int32 tx;
785 	int32 tz;
786 	int32 len, mask_len;
787 
788 	// get object to check
789 
790 	monica = (_feature_info *)features->Try_fetch_item_by_name(name);
791 	if (monica) {
792 		x = monica->x;
793 		z = monica->z;
794 
795 	} else {
796 		id = objects->Fetch_item_number_by_name(name);
797 		if (id == 0xffffffff)
798 			Fatal_error("[%s] calling Route_to_near_mega_core - finds neither object or nico named [%s]", object->GetName(), name);
799 		//			found mega with name!
800 		//			check we are within the distance and stop us if so
801 
802 		x = logic_structs[id]->mega->actor_xyz.x;
803 		z = logic_structs[id]->mega->actor_xyz.z;
804 	}
805 
806 	sub1 = x - M->actor_xyz.x;
807 	sub2 = z - M->actor_xyz.z;
808 
809 	// dist
810 	len = (int32)((sub1 * sub1) + (sub2 * sub2));
811 
812 	if (L->looping < 2) {
813 		// check for free router
814 		if (Is_router_busy())
815 			return IR_REPEAT;
816 
817 		// dont even build route if too close already
818 		if (len < (int32)(dist * dist)) {
819 			result = TRUE8;
820 			L->looping = 0;
821 			return IR_CONT; // finished so send script on
822 		}
823 
824 		// route mask length - accuracy not required
825 
826 		if (sub1 < REAL_ZERO)
827 			sub1 = (REAL_ZERO - sub1);
828 		if (sub2 < REAL_ZERO)
829 			sub2 = (REAL_ZERO - sub2);
830 
831 		mask_len = (int32)(sub1 + sub2);
832 
833 		// build route
834 		// apply a route mask
835 		tx = (int32)x;
836 		tz = (int32)z;
837 		session_barriers->Set_route_barrier_mask((int32)tx - mask_len, (int32)tx + mask_len, (int32)tz - mask_len, (int32)tz + mask_len);
838 		route_res = Setup_route(result, (int32)x, (int32)z, run, __FULL, end_on_stand);
839 		session_barriers->Clear_route_barrier_mask();
840 
841 		if (!route_res) { // no route built
842 			if (!result) { // failed
843 				Setup_route(result, (int32)x, (int32)z, run, __LASER, end_on_stand); // must succeed
844 			} else {
845 				//      not looping any int32er
846 				L->looping = 0;
847 				result = TRUE8;
848 				return IR_CONT; // finished so send script on
849 			}
850 		}
851 	}
852 
853 	// animate the route
854 	if (Process_route()) {
855 		// not looping any int32er
856 		L->looping = 0;
857 		result = TRUE8;
858 		return IR_CONT; // finished so send script on
859 	}
860 
861 	if (len < (PXreal)(dist * dist)) {
862 		M->m_main_route.dist_left = FLOAT_ZERO;
863 		M->m_main_route.current_position = (M->m_main_route.total_points - 1);
864 
865 		if (!end_on_stand) {
866 			L->looping = 0;
867 			return IR_CONT;
868 		}
869 	}
870 
871 	// not finished, so see you next cycle
872 	return IR_REPEAT;
873 }
874 
fn_route_to_marker(int32 & result,int32 * params)875 mcodeFunctionReturnCodes _game_session::fn_route_to_marker(int32 &result, int32 *params) {
876 	// walk to a nico marker
877 	// player or mega
878 
879 	// params    0   name of marker
880 	//			1  0='walk', else 'run'
881 	//			2  0=no turn-on-spot   1=yes
882 
883 	// return    IR_CONT or
884 	//			IR_REPEAT
885 
886 	_map_marker *marker;
887 
888 	const char *marker_name = (const char *)MemoryUtil::resolvePtr(params[0]);
889 
890 	if (L->looping < 2) {
891 		// check for free router
892 		if (Is_router_busy()) {
893 			return IR_REPEAT;
894 		}
895 
896 		marker = (_map_marker *)markers.Fetch_marker_by_object_name(marker_name);
897 
898 		if (!marker)
899 			Fatal_error("fn_route_to_marker - object [%s] cant find marker [%s]", object->GetName(), marker_name);
900 
901 		// build route
902 		if (!Setup_route(result, (int32)marker->x, (int32)marker->z, params[1], __FULL, TRUE8)) {
903 			L->looping = 0;
904 			return (IR_CONT);
905 		}
906 	}
907 
908 	// animate the route
909 	if (Process_route()) {
910 		// not looping any int32er
911 		L->looping = 0;
912 		result = TRUE8;
913 		return (IR_CONT); // finished so send script on
914 	}
915 
916 	// not finished, so see you next cycle
917 	return (IR_REPEAT);
918 }
919 
920 // Changed last two parameters to int32 from bool tostop a performance warning.  Other bools are passed as int anyway,
921 // so this is more consistent and faster.
Setup_route(int32 & result,int32 corex,int32 corez,int32 runs,__rtype type,int32 end_on_stand)922 bool8 _game_session::Setup_route(int32 &result, int32 corex, int32 corez, int32 runs, __rtype type, int32 end_on_stand) {
923 	// setup and create the route
924 
925 	// returns   false if we should return to script - because route failed or is not required
926 	//				true if we need to now go on to animate along the route
927 
928 	//				result=
929 	//				TRUE - no route required
930 	//				FALSE- route failed
931 
932 	// only one of these per cycle
933 	Set_router_busy();
934 
935 	// stop people routing when armed
936 	if ((cur_id == player.Fetch_player_id()) && (M->Fetch_armed_status())) {
937 		// tell it no route is required
938 		result = TRUE8;
939 		return (FALSE8);
940 	}
941 
942 	// set up walk or run
943 	M->m_main_route.___init();
944 
945 	// set walk or run
946 	if (!runs) {
947 		M->m_main_route.request_form.anim_type = __WALK;
948 		Set_motion(__MOTION_WALK); // for safety
949 	} else {
950 		M->m_main_route.request_form.anim_type = __RUN;
951 		Set_motion(__MOTION_RUN); // for safety
952 	}
953 
954 	// quick CAPS check on the anim
955 	if ((!L->voxel_info->IsAnimTable(L->cur_anim_type)))
956 		Fatal_error("mega [%s] has anim [%s] missing", object->GetName(), master_anim_name_table[L->cur_anim_type].name);
957 
958 	// new route do prepare a route request form!
959 	// initial x,z
960 	M->m_main_route.request_form.initial_x = M->actor_xyz.x;
961 	M->m_main_route.request_form.initial_z = M->actor_xyz.z;
962 
963 	// target x,z
964 	M->m_main_route.request_form.dest_x = (PXreal)corex;
965 	M->m_main_route.request_form.dest_z = (PXreal)corez;
966 
967 	// need characters y coordinate also
968 	M->m_main_route.request_form.character_y = M->actor_xyz.y;
969 
970 	// this function attempts to finish on stand
971 	M->m_main_route.request_form.finish_on_stand = (end_on_stand == 0) ? FALSE8 : TRUE8;
972 
973 	M->m_main_route.request_form.finish_on_null_stand = FALSE8; // special mode for interactions
974 
975 	// set type
976 	M->m_main_route.request_form.rtype = ROUTE_points_only;
977 
978 	// now log and create the initial route
979 	Create_initial_route(type);
980 
981 	// if the route could not be built
982 	if (M->m_main_route.request_form.error == __ROUTE_REQUEST_PRIM_FAILED) {
983 		result = FALSE8;
984 		return (FALSE8);
985 	}
986 
987 	if (M->m_main_route.request_form.error == __RR_NO_ROUTE_REQUIRED) {
988 		result = TRUE8;
989 		return (FALSE8);
990 	}
991 
992 	// we are now looping, having done the init
993 	L->looping = 2;
994 
995 	return (TRUE8);
996 }
997 
Animate_points(_route_description * route)998 uint32 _game_session::Animate_points(_route_description *route) {
999 	// animate to each point in turn
1000 
1001 	// look at the characters available anims to judge the frames to use - this has a degree of flexibility
1002 
1003 	// compute the pan and distance of the current line
1004 
1005 	//	return   0 still going
1006 	//				1 we're finished
1007 
1008 	PXreal sub1, sub2;
1009 	PXreal xnext, znext;
1010 	PXreal x, z;
1011 	PXreal dist;
1012 
1013 	// have we any further to go on the current segment?
1014 	if (route->dist_left == FLOAT_ZERO) {
1015 		Zdebug(" end of seg");
1016 		// check for end of route
1017 		if (route->current_position == (route->total_points - 1)) {
1018 			Zdebug("end of route!");
1019 
1020 			route->arrived = TRUE8;
1021 
1022 			// its the end
1023 			return (1);
1024 		}
1025 
1026 		// advance to the next line segment
1027 		route->current_position++;
1028 
1029 		// calc distance to travel and set the pan value (force the pan for now)
1030 
1031 		Calc_dist_and_pan(M->actor_xyz.x, M->actor_xyz.z, route);
1032 
1033 		// if  new pan is acute we may wish to GOSUB to a turn anim?
1034 	}
1035 
1036 	//if   not current __WALK then choose nearest leg position from __WALK frame set
1037 
1038 	if (L->cur_anim_type != route->request_form.anim_type) {
1039 		// starting anew so softly into walk anim
1040 
1041 		Soften_up_anim_file(route->request_form.anim_type, 1000000);
1042 		L->cur_anim_type = route->request_form.anim_type;
1043 	}
1044 
1045 	// advance frame and motion - clipping distance if over the end
1046 
1047 	// work out how far we wish to move
1048 	// open the motion anim file
1049 	ANIM_CHECK(L->cur_anim_type);
1050 
1051 	PXanim *pAnim = (PXanim *)rs_anims->Res_open(L->voxel_info->get_info_name(L->cur_anim_type), L->voxel_info->info_name_hash[L->cur_anim_type], L->voxel_info->base_path,
1052 	                                             L->voxel_info->base_path_hash); //
1053 
1054 	if (L->anim_pc + 1 >= pAnim->frame_qty)
1055 		Fatal_error("Animate_points finds [%s] has illegal frame in anim [%s] %d %d", (const char *)L->GetName(),
1056 		            (const char *)L->voxel_info->get_info_name(L->cur_anim_type), L->anim_pc, pAnim->frame_qty);
1057 
1058 	// get motion displacement from currently displayed frame to next one
1059 	// note that we always read frame+1 for motion of next frame even though the voxel frame itself will be looped back to 0
1060 	PXreal x1, z1, x2, z2, unused;
1061 	PXFrameEnOfAnim(L->anim_pc + 1, pAnim)->markers[ORG_POS].GetXYZ(&x1, &unused, &z1);
1062 	PXFrameEnOfAnim(L->anim_pc, pAnim)->markers[ORG_POS].GetXYZ(&x2, &unused, &z2);
1063 
1064 	xnext = x1 - x2;
1065 	znext = z1 - z2;
1066 
1067 	// calculate the new x and z coordinate from this frames motion offset
1068 	// do the z and x together
1069 	PXfloat ang = L->pan * TWO_PI;
1070 	PXfloat cang = (PXfloat)PXcos(ang);
1071 	PXfloat sang = (PXfloat)PXsin(ang);
1072 
1073 	x = M->actor_xyz.x + PXfloat2PXreal(xnext * cang + znext * sang);
1074 	z = M->actor_xyz.z + PXfloat2PXreal(znext * cang - xnext * sang);
1075 
1076 	// work out how far we are from the end
1077 
1078 	// if over the end then clip the distance
1079 	// we do this by
1080 	// working out how far on we go this time
1081 	// and if that is further than the distance left then we clip
1082 
1083 	// work out how far the next frame moves us
1084 
1085 	sub1 = x - M->actor_xyz.x;
1086 	sub2 = z - M->actor_xyz.z;
1087 	dist = (PXreal)PXsqrt((sub1 * sub1) + (sub2 * sub2));
1088 
1089 	if (dist > route->dist_left) {
1090 		// this would take us too far so clip at the end point
1091 		M->actor_xyz.x = route->prim_route[route->current_position].x;
1092 		M->actor_xyz.z = route->prim_route[route->current_position].z;
1093 
1094 		// set to finished
1095 		route->dist_left = FLOAT_ZERO;
1096 
1097 		// in theory, we must now do extra correctional work on the actor.pos.x,z thing?
1098 	} else {
1099 		// ok, use the desired motion
1100 		M->actor_xyz.x = x;
1101 		M->actor_xyz.z = z;
1102 
1103 		// we've moved this much further
1104 		route->dist_left -= dist;
1105 	}
1106 
1107 	// advance the frame
1108 	L->anim_pc = (L->anim_pc + 1) % (pAnim->frame_qty - 1);
1109 
1110 	return (0);
1111 }
1112 
Advance_auto_pan()1113 void _game_session::Advance_auto_pan() {
1114 	// move the pan closer to target
1115 
1116 	PXfloat turn;
1117 
1118 	if (!M->turn_dir)
1119 		turn = -(FULL_TURN / 10); // 0.1f;
1120 	else
1121 		turn = (FULL_TURN / 10); // 0.1f;
1122 
1123 	if (PXfabs(turn) >= M->target_pan) {
1124 		//		too far so snap
1125 		L->auto_panning = FALSE8;
1126 		M->target_pan = ZERO_TURN; // we're done
1127 		L->pan = M->auto_target_pan; // used for things like interact - where we just auto turn to target
1128 	} else {
1129 		L->auto_display_pan += turn;
1130 		M->target_pan = M->target_pan - (PXfloat)PXfabs(turn); // distance left to travel
1131 	}
1132 
1133 	if (L->auto_display_pan >= HALF_TURN)
1134 		L->auto_display_pan -= FULL_TURN;
1135 	else if (L->auto_display_pan <= -HALF_TURN)
1136 		L->auto_display_pan += FULL_TURN; // stop the wrap
1137 }
1138 
Calc_dist_and_pan(PXreal x,PXreal z,_route_description * route)1139 void _game_session::Calc_dist_and_pan(PXreal x, PXreal z, _route_description *route) {
1140 	// setup the next segment to route down
1141 	// calc 'dist_left'
1142 
1143 	PXreal sub1, sub2;
1144 
1145 	// get length
1146 	sub1 = x - route->prim_route[route->current_position].x;
1147 	sub2 = z - route->prim_route[route->current_position].z;
1148 
1149 	route->dist_left = (PXreal)PXsqrt((sub1 * sub1) + (sub2 * sub2));
1150 
1151 	// calculate the facing pan down this track
1152 
1153 	L->auto_panning = FALSE8; // none
1154 	M->target_pan = ZERO_TURN; // none
1155 
1156 	PXfloat new_pan, diff;
1157 
1158 	if (!M->reverse_route)
1159 		new_pan = PXAngleOfVector(route->prim_route[route->current_position].z - z, route->prim_route[route->current_position].x - x);
1160 	else
1161 		new_pan = PXAngleOfVector(z - route->prim_route[route->current_position].z, x - route->prim_route[route->current_position].x);
1162 
1163 	// get difference between the two
1164 	diff = new_pan - L->pan;
1165 
1166 	if (PXfabs(diff) > (FULL_TURN / 10)) { // 0.1f
1167 		                               // work out which way to turn
1168 		if (diff > HALF_TURN)
1169 			diff -= FULL_TURN;
1170 		else if (diff < -HALF_TURN)
1171 			diff += FULL_TURN;
1172 
1173 		//		diff is now the distance to turn by and its sign denotes direction
1174 
1175 		if (diff < FLOAT_ZERO)
1176 			M->turn_dir = 0; // right
1177 		else
1178 			M->turn_dir = 1; // left
1179 
1180 		M->target_pan = (PXfloat)PXfabs(diff); // save positive pan distance
1181 		L->auto_display_pan = L->pan; // start where we currently are
1182 		L->auto_panning = TRUE8;
1183 		M->auto_target_pan = new_pan; // need this here as its snapped to at end of auto-pan cycle
1184 	}
1185 
1186 	// normal pan set regardless for movement
1187 	L->pan = new_pan;
1188 }
1189 
Calc_dist_and_target_pan(PXreal x,PXreal z,_route_description * route)1190 void _game_session::Calc_dist_and_target_pan(PXreal x, PXreal z, _route_description *route) {
1191 	// usually called to setup the first segment to route down
1192 	// will set target turn-to pan if current pan and pan of route are too far apart for a pan snap
1193 	// calc 'dist_left'
1194 	// checks for anim CAPS should have already been done before we are called
1195 
1196 	PXreal sub1, sub2;
1197 
1198 	// cancel any previous
1199 	L->auto_panning = FALSE8;
1200 
1201 	// get length
1202 	sub1 = x - route->prim_route[route->current_position].x;
1203 	sub2 = z - route->prim_route[route->current_position].z;
1204 
1205 	route->dist_left = (PXreal)PXsqrt((sub1 * sub1) + (sub2 * sub2));
1206 
1207 	Calc_target_pan_no_bones(route->prim_route[route->current_position].x, route->prim_route[route->current_position].z, x, z);
1208 }
1209 
Start_new_router_game_cycle()1210 void _game_session::Start_new_router_game_cycle() {
1211 	// called at start of game cycle - this allows one route to be built during the cycle
1212 	router_busy = FALSE8;
1213 }
1214 
Set_router_busy()1215 void _game_session::Set_router_busy() {
1216 	// a route has been built - so dont allow anymore
1217 
1218 	router_busy = TRUE8;
1219 }
1220 
Is_router_busy()1221 bool8 _game_session::Is_router_busy() {
1222 	// has a route been built?
1223 
1224 	if (first_session_cycle)
1225 		return FALSE8;
1226 
1227 	return router_busy;
1228 }
1229 
1230 } // End of namespace ICB
1231