scummvm/engines/icb/fn_interact.cpp
2022-07-30 21:26:14 +02:00

574 lines
19 KiB
C++

/* 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 <http://www.gnu.org/licenses/>.
*
*/
#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 DOESNT 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 DOESNT 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 cant indentify animation %s", anim_name);
if (!I->IsAnimTable(anim))
Fatal_error("Core_prop_interact finds [%s] doesnt 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 cant 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