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