/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * Additional copyright for this file: * Copyright (C) 1999-2000 Revolution Software Ltd. * This code is based on source code created by Revolution Software, * used with permission. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "engines/icb/p4.h" #include "engines/icb/common/px_common.h" #include "engines/icb/common/px_linkeddatafile.h" #include "engines/icb/common/ptr_util.h" #include "engines/icb/mission.h" #include "engines/icb/session.h" #include "engines/icb/object_structs.h" #include "engines/icb/debug.h" #include "engines/icb/player.h" #include "engines/icb/direct_input.h" #include "engines/icb/barriers.h" #include "engines/icb/common/px_route_barriers.h" #include "engines/icb/global_objects.h" #include "engines/icb/animation_mega_set.h" #include "engines/icb/mission.h" #include "engines/icb/common/px_scriptengine.h" #include "engines/icb/session.h" #include "engines/icb/global_switches.h" #include "engines/icb/res_man.h" #include "engines/icb/floors.h" namespace ICB { mcodeFunctionReturnCodes fn_generic_prop_interact(int32 &result, int32 *params) { return (MS->fn_generic_prop_interact(result, params)); } mcodeFunctionReturnCodes fn_custom_prop_interact(int32 &result, int32 *params) { return (MS->fn_custom_prop_interact(result, params)); } mcodeFunctionReturnCodes fn_is_there_interact_object(int32 &result, int32 *params) { return (MS->fn_is_there_interact_object(result, params)); } mcodeFunctionReturnCodes fn_get_interact_object_id(int32 &result, int32 *params) { return (MS->fn_get_interact_object_id(result, params)); } mcodeFunctionReturnCodes fn_is_object_interact_object(int32 &result, int32 *params) { return (MS->fn_is_object_interact_object(result, params)); } mcodeFunctionReturnCodes fn_register_for_auto_interaction(int32 &result, int32 *params) { return (MS->fn_register_for_auto_interaction(result, params)); } mcodeFunctionReturnCodes fn_route_to_custom_prop_interact(int32 &result, int32 *params) { return (MS->fn_route_to_custom_prop_interact(result, params)); } mcodeFunctionReturnCodes fn_route_to_generic_prop_interact(int32 &result, int32 *params) { return (MS->fn_route_to_generic_prop_interact(result, params)); } mcodeFunctionReturnCodes fn_sony_door_interact(int32 &result, int32 *params) { return (MS->fn_sony_door_interact(result, params)); } mcodeFunctionReturnCodes fn_unregister_for_auto_interaction(int32 &result, int32 *params) { return (MS->fn_unregister_for_auto_interaction(result, params)); } mcodeFunctionReturnCodes fn_wandering_custom_prop_interact(int32 &result, int32 *params) { return (MS->fn_wandering_custom_prop_interact(result, params)); } mcodeFunctionReturnCodes fn_wandering_generic_prop_interact(int32 &result, int32 *params) { return (MS->fn_wandering_generic_prop_interact(result, params)); } #define SONY_DOOR_STEP_BACK_DIST ((50 * REAL_ONE) * (50 * REAL_ONE)) #define SONY_DOOR_PRESS_DIST ((100 * REAL_ONE) * (100 * REAL_ONE)) mcodeFunctionReturnCodes _game_session::fn_set_interacting(int32 &, int32 *params) { // set interting and id of target // so we can run prop interact type animation functions outside of an interaction // params 0 name of target const char *object_name = (const char *)MemoryUtil::resolvePtr(params[0]); uint32 id = LinkedDataObject::Fetch_item_number_by_name(objects, object_name); if (id == 0xffffffff) Fatal_error("fn_set_interacting - illegal object [%s]", object_name); M->target_id = id; M->interacting = TRUE8; return (IR_CONT); } mcodeFunctionReturnCodes _game_session::fn_clear_interacting(int32 &, int32 *) { M->interacting = FALSE8; return (IR_STOP); // this is vital as currently the object will be } mcodeFunctionReturnCodes _game_session::fn_route_to_generic_prop_interact(int32 &result, int32 *params) { // WALK-TO interact with a prop BUT DOESN'T play a generic animation // will call a trigger script if finds marker and script // params 0 name of generic animation if (L->looping == 2) { L->looping = FALSE8; L->pan = logic_structs[M->target_id]->prop_interact_pan; // force to stand, frame 0, restore pre anim coordinates POST_INTERACTION // fix coords and set to stand return (IR_CONT); } return (Core_prop_interact(result, params, FALSE8, FALSE8)); } mcodeFunctionReturnCodes _game_session::fn_route_to_custom_prop_interact(int32 &result, int32 *params) { // WALK-TO interact with a prop BUT DOESN'T play a custom non generic animation // then return to script // params 0 name of custom animation if (L->looping == 2) { L->looping = FALSE8; L->pan = logic_structs[M->target_id]->prop_interact_pan; // force to stand, frame 0, restore pre anim coordinates POST_INTERACTION // fix coords and set to stand Reset_cur_megas_custom_type(); return (IR_CONT); } return (Core_prop_interact(result, params, TRUE8, FALSE8)); } mcodeFunctionReturnCodes _game_session::fn_sony_door_interact(int32 &result, int32 *params) { // special door situation whereby we are passed the names of two buttons and we need to work out which one to interact with // params 0 name of first button // 1 name of second button // 2 number of buttons PXfloat new_pan, diff; uint32 id; uint32 but_floor; const char *button1_name = (const char *)MemoryUtil::resolvePtr(params[0]); const char *button2_name = (const char *)MemoryUtil::resolvePtr(params[1]); if ((!params[2]) || (params[2] > 2)) Fatal_error("fn_sony_door_interact - %d is illegal number of buttons, can be 1 or 2", params[2]); result = FALSE8; // no button was pressed if (!L->looping) { // work out which button to interact with id = LinkedDataObject::Fetch_item_number_by_name(objects, button1_name); if (id == 0xffffffff) Fatal_error("fn_sony_door_interact - illegal object [%s]", button1_name); but_floor = floor_def->Return_floor_rect(logic_structs[id]->prop_xyz.x, logic_structs[id]->prop_xyz.z, M->actor_xyz.y, 0); // angle new_pan = logic_structs[id]->prop_interact_pan; // get targets pan // get difference between the two diff = L->pan - new_pan; // correct if (diff > HALF_TURN) diff -= FULL_TURN; else if (diff < -HALF_TURN) diff += FULL_TURN; if ((L->owner_floor_rect == but_floor) && (PXfabs(diff) < (FULL_TURN / 5))) { // 36 deg = +/- 18 deg // facing the same so this must be the button M->target_id = id; // change the target if (prop_interact_dist < SONY_DOOR_STEP_BACK_DIST) M->reverse_route = TRUE8; result = TRUE8; // button 1 } else { // wanst button 1 - so do nothing if that was only button if (params[2] == 1) { return IR_CONT; } // there is another button so lets take a look to see it is named correctly id = LinkedDataObject::Fetch_item_number_by_name(objects, button2_name); if (id == 0xffffffff) Fatal_error("fn_sony_door_interact - illegal object [%s]", button2_name); but_floor = floor_def->Return_floor_rect(logic_structs[id]->prop_xyz.x, logic_structs[id]->prop_xyz.z, M->actor_xyz.y, 0); if (L->owner_floor_rect != but_floor) return IR_CONT; M->target_id = id; // change the target if (prop_interact_dist < SONY_DOOR_STEP_BACK_DIST) M->reverse_route = TRUE8; result = TRUE8; // button 2 } } return IR_CONT; } mcodeFunctionReturnCodes _game_session::fn_custom_prop_interact(int32 &result, int32 *params) { // interact with a prop and play a custom non generic animation return (Core_prop_interact(result, params, TRUE8, TRUE8)); } mcodeFunctionReturnCodes _game_session::fn_generic_prop_interact(int32 &result, int32 *params) { // interact with a prop and play a generic animation // will call a trigger script if finds marker and script // params 0 name of generic animation return (Core_prop_interact(result, params, FALSE8, TRUE8)); } mcodeFunctionReturnCodes _game_session::fn_wandering_custom_prop_interact(int32 &result, int32 *params) { // interact with a prop and play a custom non generic animation return (Core_prop_interact(result, params, TRUE8, FALSE8)); } mcodeFunctionReturnCodes _game_session::fn_wandering_generic_prop_interact(int32 &result, int32 *params) { // interact with a prop and play a generic animation // will call a trigger script if finds marker and script // params 0 name of generic animation return (Core_prop_interact(result, params, FALSE8, FALSE8)); } mcodeFunctionReturnCodes _game_session::Core_prop_interact(int32 & /*result*/, int32 *params, bool8 custom, bool8 coord_correction) { //bool8 initial_turn; bool8 res = FALSE8; __mega_set_names anim; PXreal destx, destz; PXfloat diff; int32 retval; PXreal sub1, sub2, len, len2; uint32 j; // looping 0 init route // 1 process route // 2 init turn to pan // 3 async wait // 4 play target anim // 5 const char *anim_name = nullptr; if (params && params[0]) { anim_name = (const char *)MemoryUtil::resolvePtr(params[0]); } // set up first time in if (!L->looping) { // setup autoroute to coordinate if (!custom) { Zdebug("calc *generic* target anim [%s]", anim_name); // get anim type res = I->Find_anim_type(&anim, anim_name); if (!res) Fatal_error("Core_prop_interact can't indentify animation %s", anim_name); if (!I->IsAnimTable(anim)) Fatal_error("Core_prop_interact finds [%s] doesn't have a [%s] animation", CGameObject::GetName(object), params[0]); } else { Zdebug("calc *custom* target anim [%s]", anim_name); I->Init_custom_animation(anim_name); anim = __NON_GENERIC; } // start psx asyncing the anim - may already be doing so if scripts are written properly! if (rs_anims->Res_open(I->get_info_name(anim), I->info_name_hash[anim], I->base_path, I->base_path_hash) == nullptr) return IR_REPEAT; // we are now looping, having done the init L->looping = 1; // calculate the coordinate Compute_target_interaction_coordinate(anim, &destx, &destz); // uses target_id to derive initial target coord // save target coord for later post animation correction M->target_xyz.x = destx; M->target_xyz.z = destz; // first lets see if we are really quite close to the interact coordinate - if we are we'll snap sub1 = (PXreal)(destx - L->mega->actor_xyz.x); sub2 = (PXreal)(destz - L->mega->actor_xyz.z); len = (PXreal)((sub1 * sub1) + (sub2 * sub2)); // dist if (len < (35 * 35)) { L->mega->actor_xyz.x = destx; L->mega->actor_xyz.z = destz; L->looping = 2; return (IR_REPEAT); } // lets see if the interact coordinate is further away than we are - which is bad news // first, our coordinate to the prop sub1 = (PXreal)(logic_structs[M->target_id]->prop_xyz.x - L->mega->actor_xyz.x); sub2 = (PXreal)(logic_structs[M->target_id]->prop_xyz.z - L->mega->actor_xyz.z); len = (PXreal)((sub1 * sub1) + (sub2 * sub2)); // dist // second, the interact point to the prop sub1 = (PXreal)(destx - logic_structs[M->target_id]->prop_xyz.x); sub2 = (PXreal)(destz - logic_structs[M->target_id]->prop_xyz.z); len2 = (PXreal)((sub1 * sub1) + (sub2 * sub2)); // dist M->m_main_route.___init(); // set motion type if ((len2 > len) || (M->reverse_route == TRUE8)) { // if further away OR already set to reverse - must have been by fn-sony-door M->m_main_route.request_form.anim_type = __STEP_BACKWARD; M->reverse_route = TRUE8; //initial_turn = FALSE8; } else { //initial_turn = TRUE8; if (M->motion == __MOTION_WALK) M->m_main_route.request_form.anim_type = __WALK; else M->m_main_route.request_form.anim_type = __RUN; // form.anim_type=__RUN; } // new route do prepare a route request form! // initial x,z M->m_main_route.request_form.initial_x = M->actor_xyz.x; M->m_main_route.request_form.initial_z = M->actor_xyz.z; // target x,z M->m_main_route.request_form.dest_x = (PXreal)destx; M->m_main_route.request_form.dest_z = (PXreal)destz; Zdebug("PLAYER INTERACT to %3.2f,%3.2f from %3.2f,%3.2f", destx, destz, M->actor_xyz.x, M->actor_xyz.z); // need characters y coordinate also M->m_main_route.request_form.character_y = M->actor_xyz.y; // this function attempts to finish on stand M->m_main_route.request_form.finish_on_null_stand = TRUE8; M->m_main_route.request_form.finish_on_stand = FALSE8; // set type M->m_main_route.request_form.rtype = ROUTE_points_only; // now log and create the initial route // set a barrier mask :( session_barriers->Set_route_barrier_mask((int32)destx - 500, (int32)destx + 500, (int32)destz - 500, (int32)destz + 500); Create_initial_route(__FULL); session_barriers->Clear_route_barrier_mask(); // 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 // were first Set_router_busy(); // if the route could not be built if (M->m_main_route.request_form.error == __ROUTE_REQUEST_PRIM_FAILED) { Create_initial_route(__LASER); // lets get out of this the easy way! } // we may not actually need a route if we are very close if (M->m_main_route.request_form.error == __RR_NO_ROUTE_REQUIRED) { Zdebug("skipping route"); L->looping = 2; // bypass the route return (IR_REPEAT); } } // routing if (L->looping == 1) { if (Process_route()) { // not looping any longer // set to turn phase L->looping = 2; return (IR_REPEAT); } } // set up auto turning ready for anim play if (L->looping == 2) { diff = logic_structs[M->target_id]->prop_interact_pan - L->pan; // work out which way to turn if (diff > HALF_TURN) diff -= FULL_TURN; else if (diff < -HALF_TURN) diff += FULL_TURN; // diff is now the distance to turn by and its sign denotes direction if (diff < FLOAT_ZERO) M->turn_dir = 0; // right else M->turn_dir = 1; // left M->target_pan = (PXfloat)PXfabs(diff); // save positive pan distance M->auto_target_pan = logic_structs[M->target_id]->prop_interact_pan; // actual target which we may clip to L->auto_display_pan = L->pan; // start where we currently are L->auto_panning = TRUE8; L->looping = 3; // go straight to play anim return (IR_REPEAT); } // check anim in memory if (L->looping == 3) { if (custom) anim = __NON_GENERIC; else anim = Fetch_generic_anim_from_ascii(anim_name); // in memory yet? if (rs_anims->Res_open(I->get_info_name(anim), I->info_name_hash[anim], I->base_path, I->base_path_hash)) { L->cur_anim_type = anim; L->anim_pc = 0; L->looping = 4; // go straight to play anim } return IR_REPEAT; } // running target animation if (L->looping == 4) { // get animation 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); // // last frame is currently displayed? if ((int32)(L->anim_pc + M->anim_speed) >= (pAnim->frame_qty - 1)) { L->looping = FALSE8; M->reverse_route = FALSE8; // force to stand, frame 0 if (coord_correction) { POST_INTERACTION // fix coords and set to stand } else { // was a wandering finish-where-we-finish interaction L->cur_anim_type = __STAND; L->anim_pc = 0; } Reset_cur_megas_custom_type(); return (IR_CONT); } // shift character and frame forward by the amount appropriate if (!MS->Easy_frame_and_motion(L->cur_anim_type, 0, M->anim_speed)) { L->looping = FALSE8; M->reverse_route = FALSE8; // force to stand, frame 0, restore pre anim coordinates if (coord_correction) { POST_INTERACTION // fix coords and set to stand } else { L->cur_anim_type = __STAND; L->anim_pc = 0; } Reset_cur_megas_custom_type(); return (IR_CONT); } // is the interact marker on this frame ? for (j = 0; j < M->anim_speed; j++) { PXframe *frame = PXFrameEnOfAnim(L->anim_pc + j, pAnim); if ((frame->marker_qty > INT_POS) && (INT_TYPE == (PXmarker_PSX_Object::GetType(&frame->markers[INT_POS])))) { // run the trigger anim if (!MS->Call_socket(M->target_id, "trigger", &retval)) { Message_box("[%s] interact marker but no trigger script", (const char *)L->GetName()); 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)); } break; // done it } } } // not finished, so see you next cycle return (IR_REPEAT); } mcodeFunctionReturnCodes _game_session::fn_is_there_interact_object(int32 &result, int32 *) { // return yes or no for whether or not an interact object exists result = player.Fetch_player_interact_status(); return (IR_CONT); } mcodeFunctionReturnCodes _game_session::fn_get_interact_object_id(int32 &result, int32 *) { // return yes or no for whether or not an interact object exists result = player.Fetch_player_interact_id(); return (IR_CONT); } mcodeFunctionReturnCodes _game_session::fn_is_object_interact_object(int32 &result, int32 *params) { // return yes or no for whether or not an interact object exists const char *object_name = (const char *)MemoryUtil::resolvePtr(params[0]); uint32 id = LinkedDataObject::Fetch_item_number_by_name(objects, object_name); if (id == 0xffffffff) Fatal_error("fn_is_object_interact_object - object [%s] does not exist", object_name); if (id == player.Fetch_player_interact_id()) result = TRUE8; else result = FALSE8; return (IR_CONT); } mcodeFunctionReturnCodes _game_session::fn_unregister_for_auto_interaction(int32 &, int32 *) { // as the name says - for stairs, session joins, etc. uint32 j; for (j = 0; j < MAX_auto_interact; j++) { if (auto_interact_list[j] == (uint8)(cur_id + 1)) { Tdebug("auto_interact.txt", "- [%s] %d", CGameObject::GetName(object), j); auto_interact_list[j] = 0; // slot not empty return IR_CONT; } } Fatal_error("fn_unregister_for_auto_interaction can't unregister non registered object [%s]", CGameObject::GetName(object)); return IR_CONT; } mcodeFunctionReturnCodes _game_session::fn_register_for_auto_interaction(int32 &, int32 *) { // as the name says - for stairs, session joins, etc. uint32 j; for (j = 0; j < MAX_auto_interact; j++) { if (auto_interact_list[j] == (uint8)(cur_id + 1)) Fatal_error("fn_register_for_auto_interaction finds double registration of %s", CGameObject::GetName(object)); if (!auto_interact_list[j]) { // empty slot auto_interact_list[j] = (uint8)(cur_id + 1); Tdebug("auto_interact.txt", "+ [%s] %d", CGameObject::GetName(object), j); return IR_CONT; } } Fatal_error("fn_register_for_auto_interaction - list full - [%s]", CGameObject::GetName(object)); return IR_CONT; } } // End of namespace ICB