scummvm/engines/icb/player_interaction.cpp

457 lines
16 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/text.h"
#include "engines/icb/session.h"
#include "engines/icb/global_switches.h"
namespace ICB {
mcodeFunctionReturnCodes fn_start_player_interaction(int32 &result, int32 *params) { return (MS->fn_start_player_interaction(result, params)); }
mcodeFunctionReturnCodes fn_set_interact_distance(int32 &result, int32 *params) { return (MS->fn_set_interact_distance(result, params)); }
#define INTERACT_DISTANCE (250 * REAL_ONE)
#define MIN_INTERACT_DISTANCE (5 * REAL_ONE)
// distance to point head at - 15m
#define LOOK_AT_DISTANCE (500 * REAL_ONE)
//( ( FULL_TURN*n)/360 )
#define NEAR_PROP_DISTANCE (70 * REAL_ONE)
// was 130 original EU psx release
#define DEAD_MEGA_DISTANCE (230 * REAL_ONE)
//+/- 90deg
#define NEAR_PROP_ANGLE (FULL_TURN / 4)
void _player::Find_current_player_interact_object() {
// find the nico that represents our current interactable object
// search nicos on our level
// find within distance
// find within angle
// the name of the nico will be the name of the object
uint32 j;
PXreal sub1, sub2, len;
PXreal nearest = REAL_LARGE * REAL_LARGE; // high number to be bettered
PXreal nearest_mega = REAL_LARGE * REAL_LARGE; // high number to be bettered
PXfloat new_pan, diff;
uint32 prop_id = 0;
uint32 mega_id = 0;
uint32 look_at_prop_id = 0;
uint32 pl_id = Fetch_player_id();
bool8 armed_status = log->mega->Fetch_armed_status();
uint8 crouch_status = log->mega->Is_crouched();
interact_selected = FALSE8;
look_at_selected = FALSE8;
dead_mega = FALSE8;
bool8 evil_chosen = FALSE8;
// ** check first for megas as indicated by line-of-sight-manager - these get priority over prop nicos **
// run through all the objects calling their logic
for (j = 0; j < MS->total_objects; j++) { // object 0 is used
// object must be alive and interactable
if ((MS->logic_structs[j]->ob_status != OB_STATUS_HELD) && (MS->logic_structs[j]->player_can_interact)) { // not if the object has been manually switched out
if ((MS->logic_structs[j]->image_type == PROP) && (!armed_status) &&
(crouch_status == (MS->logic_structs[j]->three_sixty_interact & PROP_CROUCH_INTERACT))) {
if ((MS->logic_structs[j]->prop_xyz.y >= log->mega->actor_xyz.y) && (MS->logic_structs[j]->owner_floor_rect == log->owner_floor_rect))
if ((MS->logic_structs[j]->prop_xyz.y - log->mega->actor_xyz.y) < (190 * REAL_ONE)) {
sub1 = (PXreal)MS->logic_structs[j]->prop_xyz.x - log->mega->actor_xyz.x;
sub2 = (PXreal)MS->logic_structs[j]->prop_xyz.z - log->mega->actor_xyz.z;
// dist
len = (PXreal)((sub1 * sub1) + (sub2 * sub2));
// less than n centimeters away
if ((len > MIN_INTERACT_DISTANCE * MIN_INTERACT_DISTANCE) && (len < LOOK_AT_DISTANCE * LOOK_AT_DISTANCE) && (len < nearest)) {
// check for prop being a 360deg type
if (MS->logic_structs[j]->three_sixty_interact & THREE_SIXTY_INTERACT) {
new_pan = PXAngleOfVector(sub2, sub1); // work out vector
// get difference between the two
diff = new_pan - log->pan;
// correct
if (diff > HALF_TURN)
diff -= FULL_TURN;
else if (diff < -HALF_TURN)
diff += FULL_TURN;
if (PXfabs(diff) < (FULL_TURN / 10)) { // 0.1f
MS->prop_interact_dist = len; // can be used later in core_prop_interact
nearest = len;
prop_id = j + 1;
}
} else { // are we a similar pan to target object
// angle
new_pan = MS->logic_structs[j]->prop_interact_pan; // get targets pan
// get difference between the two
diff = log->pan - new_pan;
// correct
if (diff > HALF_TURN)
diff -= FULL_TURN;
else if (diff < -HALF_TURN)
diff += FULL_TURN;
if (((len < NEAR_PROP_DISTANCE * NEAR_PROP_DISTANCE) && (PXfabs(diff) < NEAR_PROP_ANGLE)) /* OR */ ||
(PXfabs(diff) < (FULL_TURN / 8))) { //*
// ok, we are facing the right direction - i.e. nearly same as interact pan
// but
// are we behind or infront - need another check
PXreal dx = (PXreal)PXsin((log->pan + (FULL_TURN / 4)) * TWO_PI);
PXreal dz = (PXreal)PXcos((log->pan + (FULL_TURN / 4)) * TWO_PI);
PXreal mx = MS->logic_structs[j]->prop_xyz.x;
PXreal mz = MS->logic_structs[j]->prop_xyz.z;
if ((dz * (mx - log->mega->actor_xyz.x)) <= (dx * (mz - log->mega->actor_xyz.z))) {
MS->prop_interact_dist = len; // can be used later in core_prop_interact
nearest = len;
prop_id = j + 1;
}
}
}
}
}
} else if ((MS->logic_structs[j]->image_type == VOXEL) && (MS->logic_structs[j]->mega->actor_xyz.y == log->mega->actor_xyz.y)) { // mega character
// we have targeted an evil but this one is not evil then skip it - regardless of proximity
if ((evil_chosen) && (!MS->logic_structs[j]->mega->is_evil))
continue;
// if there is a chi then we can target her when armed
if ((MS->is_there_a_chi) && (j == MS->chi_id) && (armed_status))
continue;
if ((g_oLineOfSight->LineOfSight(pl_id, j)) && (MS->Object_visible_to_camera(j))) { // must be on screen
sub1 = (PXreal)MS->logic_structs[j]->mega->actor_xyz.x - log->mega->actor_xyz.x;
sub2 = (PXreal)MS->logic_structs[j]->mega->actor_xyz.z - log->mega->actor_xyz.z;
// dist
len = (PXreal)((sub1 * sub1) + (sub2 * sub2));
// nearer or the current is dead or armed and the current is non-evil
if (((armed_status) && (!evil_chosen) && (MS->logic_structs[j]->mega->is_evil)) || (dead_mega) || (len < nearest_mega)) {
// we are nearer or the current is dead
// see if object is dead
if ((MS->logic_structs[j]->mega->dead) &&
(crouch_status)) { // this mega is dead and we're crouched - only register him if there isnt another
if ((!mega_id) && (len < DEAD_MEGA_DISTANCE * DEAD_MEGA_DISTANCE)) { // dead mega chosen - must be within prop type range
nearest_mega = len;
mega_id = j + 1;
dead_mega = TRUE8; // chosen a dead mega
}
} else if (!MS->logic_structs[j]->mega->dead) { // must belive if we're stood
evil_chosen = MS->logic_structs[j]->mega->is_evil;
nearest_mega = len;
mega_id = j + 1;
dead_mega = FALSE8; // chosen a live mega
}
}
}
}
}
}
// if crouching and targeting a mega the mega must be dead
// crouch props are filtered out above
if ((crouch_status) && (mega_id)) {
// if dead AND not armed or alive AND armed
if (((dead_mega) && (!armed_status)) || ((armed_status) && (!dead_mega))) {
cur_interact_id = (mega_id - 1);
interact_selected = TRUE8;
}
return;
}
// mega if armed, nearest prop or live mega, dead mega
if ((prop_id) && (nearest < nearest_mega)) { // UNARMED prop nearer than mega (wont be a prop if armed)
cur_interact_id = (prop_id - 1);
interact_selected = TRUE8;
} else if ((mega_id) && (!dead_mega)) { // live mega
cur_interact_id = (mega_id - 1);
interact_selected = TRUE8;
} else if (prop_id) { // prop
cur_interact_id = (prop_id - 1);
interact_selected = TRUE8;
}
// look at
if ((!interact_selected) && (look_at_prop_id)) {
look_at_id = look_at_prop_id - 1;
look_at_selected = TRUE8;
}
}
void _player::Render_crude_interact_highlight() {
uint32 pitch; // backbuffer pitch
uint8 *ad;
_rgb pen = {255, 0, 0, 0};
// anything highlighted?
if (interact_selected == FALSE8)
return;
// cross hair is now a development option
if (g_px->cross_hair == FALSE8)
return;
ad = surface_manager->Lock_surface(working_buffer_id);
pitch = surface_manager->Get_pitch(working_buffer_id);
// setup camera
PXcamera &camera = MS->GetCamera();
// set up nico world coords
PXvector pos;
if (MS->logic_structs[cur_interact_id]->image_type == PROP) {
pos.x = MS->logic_structs[cur_interact_id]->prop_xyz.x;
pos.y = MS->logic_structs[cur_interact_id]->prop_xyz.y;
pos.z = MS->logic_structs[cur_interact_id]->prop_xyz.z;
} else {
pos.x = MS->logic_structs[cur_interact_id]->mega->actor_xyz.x;
pos.y = MS->logic_structs[cur_interact_id]->mega->actor_xyz.y;
pos.z = MS->logic_structs[cur_interact_id]->mega->actor_xyz.z;
}
// screen pos
PXvector filmpos;
// yesno
bool8 result = FALSE8;
// compute screen coord
PXWorldToFilm(pos, camera, result, filmpos);
// print name if on screen
if (result) {
Clip_text_print(&pen, (int32)(filmpos.x + (SCREEN_WIDTH / 2)), (int32)((SCREEN_DEPTH / 2) - filmpos.y), ad, pitch, "+");
}
surface_manager->Unlock_surface(working_buffer_id);
}
__mode_return _player::Player_interact() {
// check if the player has pressed the interact button
// if so see if there's a current interact object and if so setup the interaction
// return
// __FINISHED_THIS_CYCLE, or
// __MORE_THIS_CYCLE
CGame *iobject;
uint32 j;
// first check for auto-interact objects
if ((interact_selected) && ((log->cur_anim_type == __WALK) || ((log->cur_anim_type == __RUN))))
for (j = 0; j < MAX_auto_interact; j++)
if (MS->auto_interact_list[j] == (cur_interact_id + 1)) {
// try to fetch the object
iobject = (CGame *)LinkedDataObject::Fetch_item_by_number(MS->objects, cur_interact_id);
Zdebug(" INTERACT with %s", CGameObject::GetName(iobject));
// get the address of the script we want to run
const char *pc = (const char *)LinkedDataObject::Try_fetch_item_by_hash(MS->scripts, CGameObject::GetScriptNameFullHash(iobject, OB_ACTION_CONTEXT)); //
if (pc == nullptr)
Fatal_error("Object [%s] has no interact script", CGameObject::GetName(iobject));
// now run the action context script which may or may not set a new script on level 1
RunScript(pc, iobject);
// stop for a cycle regardless
return (__FINISHED_THIS_CYCLE);
}
// check for interact button AND there being an object to interact with
if ((cur_state.IsButtonSet(__INTERACT)) && (interact_selected) && (!interact_lock) && (!stood_on_lift)) {
// try to fetch the object
iobject = (CGame *)LinkedDataObject::Fetch_item_by_number(MS->objects, cur_interact_id);
// get the address of the script we want to run
const char *pc = (const char *)LinkedDataObject::Try_fetch_item_by_hash(MS->scripts, CGameObject::GetScriptNameFullHash(iobject, OB_ACTION_CONTEXT)); //
if (pc == nullptr)
Fatal_error("Object [%s] has no interact script", CGameObject::GetName(iobject));
interact_lock = TRUE8; // switch the lock on
// reset player to either stood or stood armed
if (MS->logic_structs[Fetch_player_id()]->mega->Is_crouched())
Set_player_status(CROUCHING);
else if (MS->logic_structs[Fetch_player_id()]->mega->Fetch_armed_status())
Set_player_status(NEW_AIM);
else
Set_player_status(STOOD);
Push_player_stat();
// now run the action context script which may or may not set a new script on level 1
RunScript(pc, iobject);
// stop for a cycle regardless
return (__FINISHED_THIS_CYCLE);
} else if (!cur_state.IsButtonSet(__INTERACT)) // release the interact lock
interact_lock = FALSE8; // let go
return (__MORE_THIS_CYCLE);
}
mcodeFunctionReturnCodes _game_session::fn_set_interact_distance(int32 &, int32 *params) {
// set the distance that the player needs to be from named object to interact with it
// params: 0 - name of object, 1 - distance in cm's
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("[%s] calling fn_set_interact_distance finds [%s] is not a legal object", CGameObject::GetName(object), object_name);
if (params[1]) //positive value
logic_structs[id]->interact_dist = (PXreal)(params[1] * params[1]);
else
logic_structs[id]->interact_dist = DEFAULT_interact_distance; //default interact distance - this is the ICB figure, but ED imps can change as required
return IR_CONT;
}
mcodeFunctionReturnCodes _game_session::fn_start_player_interaction(int32 &, int32 *params) {
// do check to see if script running
// if not set it up on level 2 and change script level
// then call the new script as if from logic because, remember, this is being called from a script that is being called
// from a function ; fn_player
// S player::logic
// fn_player()
// S interact_context script
// fn_start_player_interact
// S new script
// ** if we start a new script we should write a flag for _player::Player_interact **
char *ad;
// set target id
M->target_id = player.Fetch_player_interact_id();
// set this flag to avoid interact with id=0 based problems
M->interacting = TRUE8;
// fetch action script
ad = (char *)LinkedDataObject::Try_fetch_item_by_hash(scripts, params[0] /*(uint32)params*/);
// write actual offset
L->logic[1] = ad;
// write reference for change script checks later - i.e. FN_context_chosen_script
L->logic_ref[1] = ad;
L->logic_level = 1; // reset to level 2
// action script will fall back to looping level 1
L->looping = 0; // reset to 0 for new logics
// script interpretter shouldnt write a pc back
return (IR_TERMINATE);
}
bool8 _game_session::Engine_start_interaction(const char *script, uint32 id) {
// set the current mega object interacting named 'script' in target object 'id'
CGame *iobject;
uint32 script_hash;
script_hash = HashString(script);
// get target object
iobject = (CGame *)LinkedDataObject::Fetch_item_by_number(MS->objects, id);
if (!iobject)
Fatal_error("Engine_start_interaction - named object dont exist"); // should never happen
// now try and find a script with the passed extention i.e. ???::looping
for (uint32 k = 0; k < CGameObject::GetNoScripts(iobject); k++) {
if (script_hash == CGameObject::GetScriptNamePartHash(iobject, k)) {
// script k is the one to run
// get the address of the script we want to run
char *pc = (char *)LinkedDataObject::Try_fetch_item_by_hash(scripts, CGameObject::GetScriptNameFullHash(iobject, k));
// set target id
M->target_id = id;
// set this flag to avoid interact with id=0 based problems
M->interacting = TRUE8;
// write actual offset
L->logic[1] = pc;
// write reference for change script checks later - i.e. FN_context_chosen_script
L->logic_ref[1] = pc;
L->logic_level = 1; // reset to level 2
// action script will fall back to looping level 1
L->looping = 0; // reset to 0 for new logics
return (TRUE8);
}
}
// didnt find the named script
return (FALSE8);
}
} // End of namespace ICB