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_common.h"
30 #include "engines/icb/common/px_linkeddatafile.h"
31 #include "engines/icb/common/ptr_util.h"
32 #include "engines/icb/mission.h"
33 #include "engines/icb/session.h"
34 #include "engines/icb/object_structs.h"
35 #include "engines/icb/debug.h"
36 #include "engines/icb/player.h"
37 #include "engines/icb/direct_input.h"
38 #include "engines/icb/barriers.h"
39 #include "engines/icb/common/px_route_barriers.h"
40 #include "engines/icb/global_objects.h"
41 #include "engines/icb/animation_mega_set.h"
42 #include "engines/icb/mission.h"
43 #include "engines/icb/common/px_scriptengine.h"
44 #include "engines/icb/session.h"
45 #include "engines/icb/global_switches.h"
46 #include "engines/icb/res_man.h"
47 #include "engines/icb/floors.h"
48
49 namespace ICB {
50
fn_generic_prop_interact(int32 & result,int32 * params)51 mcodeFunctionReturnCodes fn_generic_prop_interact(int32 &result, int32 *params) { return (MS->fn_generic_prop_interact(result, params)); }
52
fn_custom_prop_interact(int32 & result,int32 * params)53 mcodeFunctionReturnCodes fn_custom_prop_interact(int32 &result, int32 *params) { return (MS->fn_custom_prop_interact(result, params)); }
54
fn_is_there_interact_object(int32 & result,int32 * params)55 mcodeFunctionReturnCodes fn_is_there_interact_object(int32 &result, int32 *params) { return (MS->fn_is_there_interact_object(result, params)); }
56
fn_get_interact_object_id(int32 & result,int32 * params)57 mcodeFunctionReturnCodes fn_get_interact_object_id(int32 &result, int32 *params) { return (MS->fn_get_interact_object_id(result, params)); }
58
fn_is_object_interact_object(int32 & result,int32 * params)59 mcodeFunctionReturnCodes fn_is_object_interact_object(int32 &result, int32 *params) { return (MS->fn_is_object_interact_object(result, params)); }
60
fn_register_for_auto_interaction(int32 & result,int32 * params)61 mcodeFunctionReturnCodes fn_register_for_auto_interaction(int32 &result, int32 *params) { return (MS->fn_register_for_auto_interaction(result, params)); }
62
fn_route_to_custom_prop_interact(int32 & result,int32 * params)63 mcodeFunctionReturnCodes fn_route_to_custom_prop_interact(int32 &result, int32 *params) { return (MS->fn_route_to_custom_prop_interact(result, params)); }
64
fn_route_to_generic_prop_interact(int32 & result,int32 * params)65 mcodeFunctionReturnCodes fn_route_to_generic_prop_interact(int32 &result, int32 *params) { return (MS->fn_route_to_generic_prop_interact(result, params)); }
66
fn_sony_door_interact(int32 & result,int32 * params)67 mcodeFunctionReturnCodes fn_sony_door_interact(int32 &result, int32 *params) { return (MS->fn_sony_door_interact(result, params)); }
68
fn_unregister_for_auto_interaction(int32 & result,int32 * params)69 mcodeFunctionReturnCodes fn_unregister_for_auto_interaction(int32 &result, int32 *params) { return (MS->fn_unregister_for_auto_interaction(result, params)); }
70
fn_wandering_custom_prop_interact(int32 & result,int32 * params)71 mcodeFunctionReturnCodes fn_wandering_custom_prop_interact(int32 &result, int32 *params) { return (MS->fn_wandering_custom_prop_interact(result, params)); }
72
fn_wandering_generic_prop_interact(int32 & result,int32 * params)73 mcodeFunctionReturnCodes fn_wandering_generic_prop_interact(int32 &result, int32 *params) { return (MS->fn_wandering_generic_prop_interact(result, params)); }
74
75 #define SONY_DOOR_STEP_BACK_DIST ((50 * REAL_ONE) * (50 * REAL_ONE))
76 #define SONY_DOOR_PRESS_DIST ((100 * REAL_ONE) * (100 * REAL_ONE))
77
fn_set_interacting(int32 &,int32 * params)78 mcodeFunctionReturnCodes _game_session::fn_set_interacting(int32 &, int32 *params) {
79 // set interting and id of target
80 // so we can run prop interact type animation functions outside of an interaction
81
82 // params 0 name of target
83
84 const char *object_name = (const char *)MemoryUtil::resolvePtr(params[0]);
85
86 uint32 id = objects->Fetch_item_number_by_name(object_name);
87 if (id == 0xffffffff)
88 Fatal_error("fn_set_interacting - illegal object [%s]", object_name);
89
90 M->target_id = id;
91
92 M->interacting = TRUE8;
93
94 return (IR_CONT);
95 }
96
fn_clear_interacting(int32 &,int32 *)97 mcodeFunctionReturnCodes _game_session::fn_clear_interacting(int32 &, int32 *) {
98 M->interacting = FALSE8;
99
100 return (IR_STOP); // this is vital as currently the object will be
101 }
102
fn_route_to_generic_prop_interact(int32 & result,int32 * params)103 mcodeFunctionReturnCodes _game_session::fn_route_to_generic_prop_interact(int32 &result, int32 *params) {
104 // WALK-TO interact with a prop BUT DOESNT play a generic animation
105 // will call a trigger script if finds marker and script
106
107 // params 0 name of generic animation
108
109 if (L->looping == 2) {
110 L->looping = FALSE8;
111 L->pan = logic_structs[M->target_id]->prop_interact_pan;
112
113 // force to stand, frame 0, restore pre anim coordinates
114 POST_INTERACTION // fix coords and set to stand
115
116 return (IR_CONT);
117 }
118
119 return (Core_prop_interact(result, params, FALSE8, FALSE8));
120 }
121
fn_route_to_custom_prop_interact(int32 & result,int32 * params)122 mcodeFunctionReturnCodes _game_session::fn_route_to_custom_prop_interact(int32 &result, int32 *params) {
123 // WALK-TO interact with a prop BUT DOESNT play a custom non generic animation
124 // then return to script
125
126 // params 0 name of custom animation
127
128 if (L->looping == 2) {
129 L->looping = FALSE8;
130 L->pan = logic_structs[M->target_id]->prop_interact_pan;
131
132 // force to stand, frame 0, restore pre anim coordinates
133 POST_INTERACTION // fix coords and set to stand
134
135 Reset_cur_megas_custom_type();
136
137 return (IR_CONT);
138 }
139
140 return (Core_prop_interact(result, params, TRUE8, FALSE8));
141 }
142
fn_sony_door_interact(int32 & result,int32 * params)143 mcodeFunctionReturnCodes _game_session::fn_sony_door_interact(int32 &result, int32 *params) {
144 // special door situation whereby we are passed the names of two buttons and we need to work out which one to interact with
145
146 // params 0 name of first button
147 // 1 name of second button
148 // 2 number of buttons
149
150 PXfloat new_pan, diff;
151 uint32 id;
152 uint32 but_floor;
153
154 const char *button1_name = (const char *)MemoryUtil::resolvePtr(params[0]);
155 const char *button2_name = (const char *)MemoryUtil::resolvePtr(params[1]);
156
157 if ((!params[2]) || (params[2] > 2))
158 Fatal_error("fn_sony_door_interact - %d is illegal number of buttons, can be 1 or 2", params[2]);
159
160 result = FALSE8; // no button was pressed
161
162 if (!L->looping) {
163 // work out which button to interact with
164
165 id = objects->Fetch_item_number_by_name(button1_name);
166 if (id == 0xffffffff)
167 Fatal_error("fn_sony_door_interact - illegal object [%s]", button1_name);
168
169 but_floor = floor_def->Return_floor_rect(logic_structs[id]->prop_xyz.x, logic_structs[id]->prop_xyz.z, M->actor_xyz.y, 0);
170
171 // angle
172 new_pan = logic_structs[id]->prop_interact_pan; // get targets pan
173
174 // get difference between the two
175 diff = L->pan - new_pan;
176
177 // correct
178 if (diff > HALF_TURN)
179 diff -= FULL_TURN;
180 else if (diff < -HALF_TURN)
181 diff += FULL_TURN;
182
183 if ((L->owner_floor_rect == but_floor) && (PXfabs(diff) < (FULL_TURN / 5))) { // 36 deg = +/- 18 deg
184 // facing the same so this must be the button
185 M->target_id = id; // change the target
186
187 if (prop_interact_dist < SONY_DOOR_STEP_BACK_DIST)
188 M->reverse_route = TRUE8;
189
190 result = TRUE8; // button 1
191 } else {
192 // wanst button 1 - so do nothing if that was only button
193 if (params[2] == 1) {
194 return IR_CONT;
195 }
196
197 // there is another button so lets take a look to see it is named correctly
198 id = objects->Fetch_item_number_by_name(button2_name);
199 if (id == 0xffffffff)
200 Fatal_error("fn_sony_door_interact - illegal object [%s]", button2_name);
201
202 but_floor = floor_def->Return_floor_rect(logic_structs[id]->prop_xyz.x, logic_structs[id]->prop_xyz.z, M->actor_xyz.y, 0);
203
204 if (L->owner_floor_rect != but_floor)
205 return IR_CONT;
206
207 M->target_id = id; // change the target
208
209 if (prop_interact_dist < SONY_DOOR_STEP_BACK_DIST)
210 M->reverse_route = TRUE8;
211
212 result = TRUE8; // button 2
213 }
214 }
215
216 return IR_CONT;
217 }
218
fn_custom_prop_interact(int32 & result,int32 * params)219 mcodeFunctionReturnCodes _game_session::fn_custom_prop_interact(int32 &result, int32 *params) {
220 // interact with a prop and play a custom non generic animation
221 return (Core_prop_interact(result, params, TRUE8, TRUE8));
222 }
223
fn_generic_prop_interact(int32 & result,int32 * params)224 mcodeFunctionReturnCodes _game_session::fn_generic_prop_interact(int32 &result, int32 *params) {
225 // interact with a prop and play a generic animation
226 // will call a trigger script if finds marker and script
227
228 // params 0 name of generic animation
229
230 return (Core_prop_interact(result, params, FALSE8, TRUE8));
231 }
232
fn_wandering_custom_prop_interact(int32 & result,int32 * params)233 mcodeFunctionReturnCodes _game_session::fn_wandering_custom_prop_interact(int32 &result, int32 *params) {
234 // interact with a prop and play a custom non generic animation
235
236 return (Core_prop_interact(result, params, TRUE8, FALSE8));
237 }
238
fn_wandering_generic_prop_interact(int32 & result,int32 * params)239 mcodeFunctionReturnCodes _game_session::fn_wandering_generic_prop_interact(int32 &result, int32 *params) {
240 // interact with a prop and play a generic animation
241 // will call a trigger script if finds marker and script
242
243 // params 0 name of generic animation
244
245 return (Core_prop_interact(result, params, FALSE8, FALSE8));
246 }
247
Core_prop_interact(int32 &,int32 * params,bool8 custom,bool8 coord_correction)248 mcodeFunctionReturnCodes _game_session::Core_prop_interact(int32 & /*result*/, int32 *params, bool8 custom, bool8 coord_correction) {
249 //bool8 initial_turn;
250 bool8 res = FALSE8;
251 __mega_set_names anim;
252 PXreal destx, destz;
253 PXfloat diff;
254 int32 retval;
255 PXreal sub1, sub2, len, len2;
256 uint32 j;
257
258 // looping 0 init route
259 // 1 process route
260 // 2 init turn to pan
261 // 3 async wait
262 // 4 play target anim
263 // 5
264
265 const char *anim_name = NULL;
266 if (params && params[0]) {
267 anim_name = (const char *)MemoryUtil::resolvePtr(params[0]);
268 }
269
270 // set up first time in
271
272 if (!L->looping) {
273
274 // setup autoroute to coordinate
275
276 if (!custom) {
277 Zdebug("calc *generic* target anim [%s]", anim_name);
278
279 // get anim type
280 res = I->Find_anim_type(&anim, anim_name);
281 if (!res)
282 Fatal_error("Core_prop_interact cant indentify animation %s", anim_name);
283
284 if (!I->IsAnimTable(anim))
285 Fatal_error("Core_prop_interact finds [%s] doesnt have a [%s] animation", object->GetName(), params[0]);
286 } else {
287 Zdebug("calc *custom* target anim [%s]", anim_name);
288 I->Init_custom_animation(anim_name);
289 anim = __NON_GENERIC;
290 }
291
292 // start psx asyncing the anim - may already be doing so if scripts are written properly!
293 if (rs_anims->Res_open(I->get_info_name(anim), I->info_name_hash[anim], I->base_path, I->base_path_hash) == 0)
294 return IR_REPEAT;
295
296 // we are now looping, having done the init
297 L->looping = 1;
298
299 // calculate the coordinate
300 Compute_target_interaction_coordinate(anim, &destx, &destz); // uses target_id to derive initial target coord
301
302 // save target coord for later post animation correction
303 M->target_xyz.x = destx;
304 M->target_xyz.z = destz;
305
306 // first lets see if we are really quite close to the interact coordinate - if we are we'll snap
307 sub1 = (PXreal)(destx - L->mega->actor_xyz.x);
308 sub2 = (PXreal)(destz - L->mega->actor_xyz.z);
309 len = (PXreal)((sub1 * sub1) + (sub2 * sub2)); // dist
310 if (len < (35 * 35)) {
311 L->mega->actor_xyz.x = destx;
312 L->mega->actor_xyz.z = destz;
313 L->looping = 2;
314 return (IR_REPEAT);
315 }
316
317 // lets see if the interact coordinate is further away than we are - which is bad news
318
319 // first, our coordinate to the prop
320 sub1 = (PXreal)(logic_structs[M->target_id]->prop_xyz.x - L->mega->actor_xyz.x);
321 sub2 = (PXreal)(logic_structs[M->target_id]->prop_xyz.z - L->mega->actor_xyz.z);
322 len = (PXreal)((sub1 * sub1) + (sub2 * sub2)); // dist
323
324 // second, the interact point to the prop
325 sub1 = (PXreal)(destx - logic_structs[M->target_id]->prop_xyz.x);
326 sub2 = (PXreal)(destz - logic_structs[M->target_id]->prop_xyz.z);
327 len2 = (PXreal)((sub1 * sub1) + (sub2 * sub2)); // dist
328
329 M->m_main_route.___init();
330
331 // set motion type
332 if ((len2 > len) || (M->reverse_route == TRUE8)) { // if further away OR already set to reverse - must have been by fn-sony-door
333 M->m_main_route.request_form.anim_type = __STEP_BACKWARD;
334 M->reverse_route = TRUE8;
335 //initial_turn = FALSE8;
336 } else {
337 //initial_turn = TRUE8;
338
339 if (M->motion == __MOTION_WALK)
340 M->m_main_route.request_form.anim_type = __WALK;
341 else
342 M->m_main_route.request_form.anim_type = __RUN; // form.anim_type=__RUN;
343 }
344
345 // new route do prepare a route request form!
346 // initial x,z
347 M->m_main_route.request_form.initial_x = M->actor_xyz.x;
348 M->m_main_route.request_form.initial_z = M->actor_xyz.z;
349
350 // target x,z
351 M->m_main_route.request_form.dest_x = (PXreal)destx;
352 M->m_main_route.request_form.dest_z = (PXreal)destz;
353
354 Zdebug("PLAYER INTERACT to %3.2f,%3.2f from %3.2f,%3.2f", destx, destz, M->actor_xyz.x, M->actor_xyz.z);
355
356 // need characters y coordinate also
357 M->m_main_route.request_form.character_y = M->actor_xyz.y;
358
359 // this function attempts to finish on stand
360 M->m_main_route.request_form.finish_on_null_stand = TRUE8;
361 M->m_main_route.request_form.finish_on_stand = FALSE8;
362
363 // set type
364 M->m_main_route.request_form.rtype = ROUTE_points_only;
365
366 // now log and create the initial route
367 // set a barrier mask :(
368 session_barriers->Set_route_barrier_mask((int32)destx - 500, (int32)destx + 500, (int32)destz - 500, (int32)destz + 500);
369 Create_initial_route(__FULL);
370 session_barriers->Clear_route_barrier_mask();
371
372 // only one of these per cycle - we may have cheated and done a second route here but at least we can stop another if we
373 // were first
374 Set_router_busy();
375
376 // if the route could not be built
377 if (M->m_main_route.request_form.error == __ROUTE_REQUEST_PRIM_FAILED) {
378 Create_initial_route(__LASER); // lets get out of this the easy way!
379 }
380
381 // we may not actually need a route if we are very close
382 if (M->m_main_route.request_form.error == __RR_NO_ROUTE_REQUIRED) {
383 Zdebug("skipping route");
384 L->looping = 2; // bypass the route
385 return (IR_REPEAT);
386 }
387 }
388
389 // routing
390 if (L->looping == 1) {
391 if (Process_route()) {
392 // not looping any longer
393 // set to turn phase
394 L->looping = 2;
395 return (IR_REPEAT);
396 }
397 }
398
399 // set up auto turning ready for anim play
400 if (L->looping == 2) {
401 diff = logic_structs[M->target_id]->prop_interact_pan - L->pan;
402
403 // work out which way to turn
404 if (diff > HALF_TURN)
405 diff -= FULL_TURN;
406 else if (diff < -HALF_TURN)
407 diff += FULL_TURN;
408
409 // diff is now the distance to turn by and its sign denotes direction
410
411 if (diff < FLOAT_ZERO)
412 M->turn_dir = 0; // right
413 else
414 M->turn_dir = 1; // left
415
416 M->target_pan = (PXfloat)PXfabs(diff); // save positive pan distance
417 M->auto_target_pan = logic_structs[M->target_id]->prop_interact_pan; // actual target which we may clip to
418 L->auto_display_pan = L->pan; // start where we currently are
419 L->auto_panning = TRUE8;
420
421 L->looping = 3; // go straight to play anim
422 return (IR_REPEAT);
423 }
424
425 // check anim in memory
426 if (L->looping == 3) {
427
428 if (custom)
429 anim = __NON_GENERIC;
430 else
431 anim = Fetch_generic_anim_from_ascii(anim_name);
432
433 // in memory yet?
434 if (rs_anims->Res_open(I->get_info_name(anim), I->info_name_hash[anim], I->base_path, I->base_path_hash)) {
435 L->cur_anim_type = anim;
436 L->anim_pc = 0;
437 L->looping = 4; // go straight to play anim
438 }
439
440 return IR_REPEAT;
441 }
442
443 // running target animation
444 if (L->looping == 4) {
445 // get animation
446 PXanim *pAnim = (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); //
447
448 // last frame is currently displayed?
449 if ((int32)(L->anim_pc + M->anim_speed) >= (pAnim->frame_qty - 1)) {
450 L->looping = FALSE8;
451 M->reverse_route = FALSE8;
452
453 // force to stand, frame 0
454 if (coord_correction) {
455 POST_INTERACTION // fix coords and set to stand
456 } else { // was a wandering finish-where-we-finish interaction
457 L->cur_anim_type = __STAND;
458 L->anim_pc = 0;
459 }
460
461 Reset_cur_megas_custom_type();
462 return (IR_CONT);
463 }
464
465 // shift character and frame forward by the amount appropriate
466 if (!MS->Easy_frame_and_motion(L->cur_anim_type, 0, M->anim_speed)) {
467 L->looping = FALSE8;
468
469 M->reverse_route = FALSE8;
470
471 // force to stand, frame 0, restore pre anim coordinates
472 if (coord_correction) {
473 POST_INTERACTION // fix coords and set to stand
474 } else {
475 L->cur_anim_type = __STAND;
476 L->anim_pc = 0;
477 }
478
479 Reset_cur_megas_custom_type();
480 return (IR_CONT);
481 }
482
483 // is the interact marker on this frame ?
484 for (j = 0; j < M->anim_speed; j++) {
485 PXframe *frame = PXFrameEnOfAnim(L->anim_pc + j, pAnim);
486
487 if ((frame->marker_qty > INT_POS) && (INT_TYPE == (frame->markers[INT_POS].GetType()))) {
488 // run the trigger anim
489 if (!MS->Call_socket(M->target_id, "trigger", &retval)) {
490 Message_box("[%s] interact marker but no trigger script", (const char *)L->GetName());
491 Message_box("anim %s Target ID %d [%s]", master_anim_name_table[L->cur_anim_type].name, M->target_id, Fetch_object_name(M->target_id));
492 }
493
494 break; // done it
495 }
496 }
497 }
498
499 // not finished, so see you next cycle
500 return (IR_REPEAT);
501 }
502
fn_is_there_interact_object(int32 & result,int32 *)503 mcodeFunctionReturnCodes _game_session::fn_is_there_interact_object(int32 &result, int32 *) {
504 // return yes or no for whether or not an interact object exists
505
506 result = player.Fetch_player_interact_status();
507
508 return (IR_CONT);
509 }
510
fn_get_interact_object_id(int32 & result,int32 *)511 mcodeFunctionReturnCodes _game_session::fn_get_interact_object_id(int32 &result, int32 *) {
512 // return yes or no for whether or not an interact object exists
513
514 result = player.Fetch_player_interact_id();
515
516 return (IR_CONT);
517 }
518
fn_is_object_interact_object(int32 & result,int32 * params)519 mcodeFunctionReturnCodes _game_session::fn_is_object_interact_object(int32 &result, int32 *params) {
520 // return yes or no for whether or not an interact object exists
521
522 const char *object_name = (const char *)MemoryUtil::resolvePtr(params[0]);
523
524 uint32 id = objects->Fetch_item_number_by_name(object_name);
525 if (id == 0xffffffff)
526 Fatal_error("fn_is_object_interact_object - object [%s] does not exist", object_name);
527
528 if (id == player.Fetch_player_interact_id())
529 result = TRUE8;
530 else
531 result = FALSE8;
532
533 return (IR_CONT);
534 }
535
fn_unregister_for_auto_interaction(int32 &,int32 *)536 mcodeFunctionReturnCodes _game_session::fn_unregister_for_auto_interaction(int32 &, int32 *) {
537 // as the name says - for stairs, session joins, etc.
538 uint32 j;
539
540 for (j = 0; j < MAX_auto_interact; j++) {
541 if (auto_interact_list[j] == (uint8)(cur_id + 1)) {
542 Tdebug("auto_interact.txt", "- [%s] %d", object->GetName(), j);
543 auto_interact_list[j] = 0; // slot not empty
544 return IR_CONT;
545 }
546 }
547
548 Fatal_error("fn_unregister_for_auto_interaction cant unregister non registered object [%s]", object->GetName());
549
550 return IR_CONT;
551 }
552
fn_register_for_auto_interaction(int32 &,int32 *)553 mcodeFunctionReturnCodes _game_session::fn_register_for_auto_interaction(int32 &, int32 *) {
554 // as the name says - for stairs, session joins, etc.
555
556 uint32 j;
557
558 for (j = 0; j < MAX_auto_interact; j++) {
559 if (auto_interact_list[j] == (uint8)(cur_id + 1))
560 Fatal_error("fn_register_for_auto_interaction finds double registration of %s", object->GetName());
561
562 if (!auto_interact_list[j]) { // empty slot
563 auto_interact_list[j] = (uint8)(cur_id + 1);
564 Tdebug("auto_interact.txt", "+ [%s] %d", object->GetName(), j);
565 return IR_CONT;
566 }
567 }
568
569 Fatal_error("fn_register_for_auto_interaction - list full - [%s]", object->GetName());
570
571 return IR_CONT;
572 }
573
574 } // End of namespace ICB
575