scummvm/engines/icb/camera.cpp

600 lines
20 KiB
C++

/* ResidualVM - A 3D game interpreter
*
* ResidualVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the AUTHORS
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "engines/icb/p4.h"
#include "engines/icb/common/px_common.h"
#include "engines/icb/common/px_game_object.h"
#include "engines/icb/common/px_scriptengine.h"
#include "engines/icb/common/ptr_util.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/global_objects.h"
#include "engines/icb/global_switches.h"
#include "engines/icb/res_man.h"
#include "engines/icb/floors.h"
#include "engines/icb/mission.h"
namespace ICB {
#define RUBBER 30
#define REAL_RUBBER (30 * REAL_ONE)
PXvector *posi;
uint32 this_rect;
_floor *obfloor;
mcodeFunctionReturnCodes fn_floor_and_floor_camera_linked(int32 &result, int32 *params) { return (MS->fn_floor_and_floor_camera_linked(result, params)); }
mcodeFunctionReturnCodes fn_switch_to_manual_camera(int32 &result, int32 *params) { return (MS->fn_switch_to_manual_camera(result, params)); }
mcodeFunctionReturnCodes fn_cancel_manual_camera(int32 &result, int32 *params) { return (MS->fn_cancel_manual_camera(result, params)); }
mcodeFunctionReturnCodes fn_is_current_camera(int32 &result, int32 *params) { return (MS->fn_is_current_camera(result, params)); }
mcodeFunctionReturnCodes fn_is_current_location(int32 &result, int32 *params) { return (MS->fn_is_current_location(result, params)); }
mcodeFunctionReturnCodes _game_session::fn_floor_and_floor_camera_linked(int32 &, int32 *params) {
// params 0 ascii name of home floor
// 1 ascii name of viewable floor name
// search for a camera of this name
// confirm floor exists
// add floor number to cameras extra floor list
uint32 home_floor_num;
uint32 floor_num;
const char *home_floor_name = (const char *)MemoryUtil::resolvePtr(params[0]);
const char *floor_name = (const char *)MemoryUtil::resolvePtr(params[1]);
home_floor_num = floor_def->Fetch_floor_number_by_name(home_floor_name);
if (home_floor_num == PX_LINKED_DATA_FILE_ERROR)
Fatal_error("fn_floor_and_floor_camera_linked cant find floor [%s]", home_floor_name);
floor_num = floor_def->Fetch_floor_number_by_name(floor_name);
if (floor_num == PX_LINKED_DATA_FILE_ERROR)
Fatal_error("fn_floor_and_floor_camera_linked cant find floor [%s]", floor_name);
if (home_floor_num == floor_num)
Fatal_error("fn_floor_and_floor_camera_linked finds [%s] and [%s] are same floor!", home_floor_name, floor_name);
// add viewable floor number to the camera that displays our primary home floor
uint32 index1 = floor_to_camera_index[home_floor_num];
uint32 index2 = floor_to_camera_index[floor_num];
cam_floor_list[index1].extra_floors[cam_floor_list[index1].num_extra_floors++] = floor_num;
cam_floor_list[index2].extra_floors[cam_floor_list[index2].num_extra_floors++] = home_floor_num;
if (cam_floor_list[index1].num_extra_floors == MAX_extra_floors)
Fatal_error("fn_floor_and_floor_camera_linked too many extra floors");
return IR_CONT;
}
void _game_session::Build_camera_table() {
// read each floor to note the name of the camera
// each new camera name is put into a list of names
// the floor number gets the numer of that camerea in the list
uint32 j, k;
_floor *floor;
uint32 tot;
Zdebug("\n***********building camera table**************");
num_cameras = 0; // reset
// reset extra cams table
for (j = 0; j < MAX_floors; j++)
cam_floor_list[j].num_extra_floors = 0;
tot = floor_def->Fetch_total_floors();
if (!tot)
Fatal_error("Build_camera_table finds no floors?");
for (j = 0; j < tot; j++) {
floor = (_floor *)floor_def->Fetch_floor_number(j);
if (floor->camera_name_offset) {
// is this camera name already been assigned a slot?
k = 0;
while ((k < num_cameras) && (strcmp(camera_name_list[k], (((char *)floor) + floor->camera_name_offset))))
++k;
if (k == num_cameras) { // didnt find this camera
Zdebug(" new camera %d [%s] [%s]", num_cameras, (((char *)floor) + floor->camera_name_offset), floor->camera_cluster);
camera_name_list[num_cameras] = (((char *)floor) + floor->camera_name_offset); // add camera name to list of names
camera_cluster_list[num_cameras] = floor->camera_cluster;
floor_to_camera_index[j] = num_cameras; // set this floors camera number
num_cameras++;
} else {
floor_to_camera_index[j] = k; // set this floors camera number
}
Zdebug(" floor %d gets camera number %d", j, floor_to_camera_index[j]);
} else {
// camera name is missing
floor_to_camera_index[j] = NO_CAMERA_CHOSEN;
Zdebug("floor %d camera missing!!!!!!!!!!!", j);
}
}
Zdebug("***********building camera table**************\n");
}
bool8 _game_session::Object_visible_to_camera(uint32 id) {
// is id visible to current camera
uint32 j, num_extra;
#define FLOOR_NO logic_structs[id]->owner_floor_rect
#define MM logic_structs[id]->mega
// Don't want to see held objects
if (logic_structs[id]->ob_status == OB_STATUS_HELD)
return (FALSE8);
// high level user clip
if ((MM) && (!MM->display_me))
return FALSE8;
// check for no camera chosen - 1st cycle
if (cur_camera_number == NO_CAMERA_CHOSEN)
return (FALSE8);
// if following the player then always TRUE8 - need this because of rubber band
if ((!g_mission->camera_follow_id_overide) && (id == player.Fetch_player_id()))
return TRUE8;
if (floor_to_camera_index[FLOOR_NO] == cur_camera_number)
return (TRUE8);
// now check if the current floor is registered as an extra floor viewable from current camera
num_extra = cam_floor_list[cur_camera_number].num_extra_floors;
for (j = 0; j < num_extra; j++)
if (cam_floor_list[cur_camera_number].extra_floors[j] == FLOOR_NO)
return (TRUE8);
// definitely not on screen
return (FALSE8);
}
void _game_session::Reset_camera_director() {
// by masking this off we force a new camera to be initialised in current view mode
cur_camera_number = NO_CAMERA_CHOSEN;
manual_camera = FALSE8;
wa_camera = FALSE8;
wa_tied_to_pin = FALSE8;
wa_tied_to_exit_pin = FALSE8;
this_rect = 0; // will be legal floor which is all that matters
}
int32 Contains(int32 x1, int32 y1, int32 x2, int32 y2, int32 mx, int32 my) {
int32 tmp, x3;
x1 = x1 << 1;
y1 = y1 << 1;
x2 = x2 << 1;
y2 = y2 << 1;
mx = (mx << 1) + 1;
my = (my << 1) + 1;
if (((y1 < my) && (y2 > my)) || ((y1 > my) && (y2 < my))) {
if (x1 > x2) {
tmp = x1;
x1 = x2;
x2 = tmp;
tmp = y1;
y1 = y2;
y2 = tmp;
}
if (y1 < y2)
x3 = x1 + (my - y1) * (x2 - x1) / (y2 - y1);
else
x3 = x2 - (my - y2) * (x2 - x1) / (y1 - y2);
if (x3 < mx)
return 1;
}
return 0;
}
void _game_session::Camera_director() {
// check which object the camera is linked to
// check which floor rect that object is on
// if no camera run a script
// else if new camera switch it in
const __aWalkArea *wa;
uint32 k;
uint32 hit;
PXreal sub1, sub2, len, y;
Prepare_camera_floors();
// a manual script camera completely overides the floor and WA system cameras
if ((manual_camera) || (camera_lock))
return;
// do walkarea stuff first
// are we currently on one?
// if so are we still on it
// if not are we on one in the list
// if so start it
// else go through normal floor system
if (wa_camera) {
// we are currently using a WA camera
wa = MS->wa_list[wa_number];
y = floor_def->Return_true_y((PXreal)wa->y);
if ((y >= obfloor->base_height) && (y < (floor_def->Fetch_floors_volume_height(this_rect)))) {
if (wa_tied_to_pin) {
// we are still tied to the pin point
// if we are within the stretch distance from the pin then we remain on the wa camera - even if we are now
// actually outside of it
sub1 = (PXreal)posi->x - wa_pin_x;
sub2 = (PXreal)posi->z - wa_pin_z;
// dist
len = (PXreal)((sub1 * sub1) + (sub2 * sub2));
Tdebug("rubber.txt", "len %3.2f pos %3.2f, %3.2f pin %3.2f, %3.2f", len, posi->x, posi->z, wa_pin_x, wa_pin_z);
if (len < (PXreal)(REAL_RUBBER * REAL_RUBBER))
return; // still constrained by the band
// band is broken
wa_tied_to_pin = FALSE8; // release
}
if ((posi->x > wa->x) && (posi->x < (wa->x + wa->w)) && (posi->z > wa->z) && (posi->z < (wa->z + wa->h))) {
// we're inside the rectangle
// are we within the polygon
hit = 0; // result is 0, miss
for (k = 0; k < (wa->noPoints - 1); k++) {
if (Contains(wa->points[k].x, wa->points[k].z, wa->points[k + 1].x, wa->points[k + 1].z, (uint32)posi->x, (uint32)posi->z))
hit = 1 - hit;
}
if (hit) { // hurrah - still hitting
cur_camera_number = floor_to_camera_index[this_rect];
return;
}
}
}
// only stick an off pin in if we're not wandering straight onto another
if (!Process_wa_list()) {
// not hitting anymore
if (!g_mission->camera_follow_id_overide)
this_rect = floor_def->Return_non_rubber_floor_no(logic_structs[player.Fetch_player_id()], this_rect);
else
this_rect = floor_def->Return_non_rubber_floor_no(logic_structs[g_mission->camera_follow_id_overide], this_rect);
// stick a pin in that we must leave before hitting a wa again
wa_pin_x = posi->x;
wa_pin_y = posi->y;
wa_pin_z = posi->z;
wa_tied_to_exit_pin = TRUE8;
cur_camera_number = NO_CAMERA_CHOSEN; // force a choose
wa_camera = FALSE8; // not any more
}
}
// not on one so check em all to see if we are
// but first check to see if we are tied to a get-off-wa pin
if (wa_tied_to_exit_pin) {
if (wa_pin_y != posi->y) { // as soon as we change y the pin breaks
wa_tied_to_exit_pin = FALSE8; // off
} else {
sub1 = (PXreal)posi->x - wa_pin_x;
sub2 = (PXreal)posi->z - wa_pin_z;
// dist
len = (PXreal)((sub1 * sub1) + (sub2 * sub2));
if (len > (PXreal)(REAL_RUBBER * REAL_RUBBER)) {
// band is broken
wa_tied_to_exit_pin = FALSE8; // release
}
}
} else {
Process_wa_list();
}
// not on a WA camera so check via the floor rects
if (this_rect == PXNULL) { // object not on a rect??
// we cant pick a camera set view
// if a set is set up then that will continue to be used
// if there isnt a set at all (this is first cycle) then engine will switch to nethack mode
return;
}
// has player changed camera - work this out from primary camera
if (cur_camera_number != floor_to_camera_index[this_rect]) {
// we are now on a floor with a different camera
// BUT
// we allow some elastic on the old one before switching
// get floor that we last changed camera on
// if we are within its bounds then remain
if (cur_camera_number != NO_CAMERA_CHOSEN) {
_floor *floor;
floor = (_floor *)floor_def->Fetch_floor_number(anchor_floor);
if ((posi->y == (PXreal)(floor->base_height)) && (posi->x >= (PXreal)(floor->rect.x1 - RUBBER)) && (posi->x <= (PXreal)(floor->rect.x2 + RUBBER)) &&
(posi->z >= (PXreal)(floor->rect.z1 - RUBBER)) && (posi->z <= (PXreal)(floor->rect.z2 + RUBBER)))
return; // still within the rubber banded camera
}
if (floor_to_camera_index[this_rect] == 0xffffffff) { // no named camera so its a more complex logic switch
// ok, lets assume that there was no special camera!
g_px->display_mode = TEMP_NETHACK; // stick us into temporary nethack mode which will bounce out again if it can
Zdebug("no named camera! - entering TEMP_NETHACK");
} else { // ok, there is a named camera! and we know its different from current
// different from current
Zdebug(" make cam=%s %s", camera_name_list[floor_to_camera_index[this_rect]], camera_cluster_list[floor_to_camera_index[this_rect]]);
// set camera number
cur_camera_number = floor_to_camera_index[this_rect];
anchor_floor = this_rect;
// if we're not in NETHACK mode then switch in the set
if (g_px->display_mode != NETHACK) {
g_px->display_mode = THREED; // force back in-case we were in TEMP_NETHACK
// init the new set - Initialise_set will record the name of the new camera
// it will also handle missing set files and bung us into TEMP_NETHACK mode if it has to
Initialise_set(camera_name_list[floor_to_camera_index[this_rect]], camera_cluster_list[floor_to_camera_index[this_rect]]); // name);
// force in the anims of megas not playing stand - techs and the like
MS->One_logic_cycle();
}
}
}
}
void _game_session::Prepare_camera_floors() {
// get floor numbers from followed object
// get a mega class objects world position - can be player or other mega
if (!g_mission->camera_follow_id_overide) {
if (!player.Player_exists())
Fatal_error("camera director cant choose a scene as player object has been shut down");
if (logic_structs[player.Fetch_player_id()]->ob_status == OB_STATUS_HELD)
Fatal_error("camera director cant choose a scene as player object has been shut down");
posi = (PXvector *)&logic_structs[player.Fetch_player_id()]->mega->actor_xyz;
this_rect = logic_structs[player.Fetch_player_id()]->owner_floor_rect;
} else {
// following another mega character
posi = (PXvector *)&logic_structs[g_mission->camera_follow_id_overide]->mega->actor_xyz;
this_rect = logic_structs[g_mission->camera_follow_id_overide]->owner_floor_rect;
}
// fetch the floor
obfloor = (_floor *)floor_def->Fetch_floor_number(this_rect);
}
bool8 _game_session::Process_wa_list() {
const __aWalkArea *wa;
uint32 j, k;
uint32 hit;
char *name;
PXreal y;
for (j = 0; j < MS->total_was; j++) {
wa = MS->wa_list[j];
y = floor_def->Return_true_y((PXreal)wa->y);
if ((y >= obfloor->base_height) && (y < (floor_def->Fetch_floors_volume_height(this_rect)))) {
if ((posi->x > wa->x) && (posi->x < (wa->x + wa->w)) && (posi->z > wa->z) && (posi->z < (wa->z + wa->h))) {
// we're inside the rectangle
// are we within the polygon
hit = 0; // result is 0, miss
for (k = 0; k < (wa->noPoints - 1); k++) {
if (Contains(wa->points[k].x, wa->points[k].z, wa->points[k + 1].x, wa->points[k + 1].z, (uint32)posi->x, (uint32)posi->z))
hit = 1 - hit;
}
if (hit) { // hey we hit the closed poly
name = (char *)const_cast<ICB::__point *>(&wa->points[wa->noPoints]);
Tdebug("cam_changes.txt", " WA camera name %s cluster %s", name, wa->cameraCluster);
Initialise_set(name, wa->cameraCluster); // name, clusterVersion of the name
wa_camera = TRUE8;
wa_number = j;
wa_pin_x = posi->x;
wa_pin_z = posi->z;
wa_tied_to_pin = TRUE8;
// in case we are coming here afresh - from a reset camera director - i.e. from nethack mode
// find the floor and set the current camera
// in other words we fake the system to think that the floor camera is chosen - thats how we decide
// who is on camera.
if (!g_mission->camera_follow_id_overide) {
this_rect = floor_def->Return_floor_rect(posi->x, posi->z, posi->y, 0);
} else {
this_rect = floor_def->Return_non_rubber_floor_no(logic_structs[g_mission->camera_follow_id_overide], this_rect);
}
// set camera number
cur_camera_number = floor_to_camera_index[this_rect];
Tdebug("cam_changes.txt", " floor %d", cur_camera_number);
return TRUE8;
}
}
}
}
return FALSE8;
}
mcodeFunctionReturnCodes _game_session::fn_switch_to_manual_camera(int32 &, int32 *params) {
// switch in a manual camera
// params 0 stub name of room - CORRIDOR\pc\camera
// 1 stub name of camera - corridor\pc\CAMERA
// 2 name of primary floor
uint32 floor_num;
uint32 len;
char h_buf[8];
const char *room_name = (const char *)MemoryUtil::resolvePtr(params[0]);
const char *camera_name = (const char *)MemoryUtil::resolvePtr(params[1]);
const char *floor_name = (const char *)MemoryUtil::resolvePtr(params[2]);
// get primary floor number
floor_num = floor_def->Fetch_floor_number_by_name(floor_name);
// set camera number
cur_camera_number = floor_to_camera_index[floor_num];
manual_camera = TRUE8;
len = sprintf(manual_camera_name, "%s\\pc\\%s", room_name, camera_name);
if (len > ENGINE_STRING_LEN)
Fatal_error("fn_switch_to_manual_camera string len error");
HashFile(manual_camera_name, h_buf);
Tdebug("cam_changes.txt", " built name %s %s", (const char *)temp_buf, h_buf);
Initialise_set(manual_camera_name, h_buf); // name);
return IR_CONT;
}
mcodeFunctionReturnCodes _game_session::fn_is_current_camera(int32 &result, int32 *params) {
// check passed string against current camera
// params 0 ascii name
const char *camera_name = (const char *)MemoryUtil::resolvePtr(params[0]);
// check for not set initialised yet - 1st game cycle
if (!set.OK()) {
result = 0;
return IR_CONT;
}
if (!strstr(set.GetSetName(), camera_name))
result = 0; // no
else
result = 1;
return IR_CONT;
}
mcodeFunctionReturnCodes _game_session::fn_is_current_location(int32 &result, int32 *params) {
char h_buf[8];
uint32 len;
const char *location_name = (const char *)MemoryUtil::resolvePtr(params[0]);
// First we need to know which location the player is in, because the information level
// for this is automatically at full.
//uint32 nPlayerFloorIndex = MS->logic_structs[MS->player.Fetch_player_id()]->owner_floor_rect;
Message_box("is %s current location?", location_name);
len = sprintf(manual_camera_name, "%s\\pc\\%s", location_name, set.GetSetName());
if (len > ENGINE_STRING_LEN)
Fatal_error("fn_is_current_location string len error");
HashFile(manual_camera_name, h_buf);
result = 1;
// continue script
return IR_CONT;
}
mcodeFunctionReturnCodes _game_session::fn_cancel_manual_camera(int32 &, int32 *) {
// cancel the manual camera
Tdebug("cam_changes.txt", "Releasing manual camera");
if (!manual_camera)
return IR_CONT;
Reset_camera_director();
return (IR_CONT);
}
// Compute if mega's are on or off current camera and send events to them to say "ON_CAMERA" / "OFF_CAMERA"
void _game_session::UpdateOnOffCamera() {
PXcamera &camera = GetCamera();
_logic *log;
for (uint32 j = 0; j < number_of_voxel_ids; j++) {
// fetch the logic structure for the game object that has a voxel image to render
log = logic_structs[voxel_id_list[j]];
// shift the this cycle camera flag to last cycle camera flag
log->mega->ShiftViewState();
// person owned by current camera floor? TEMP check
if (Object_visible_to_camera(voxel_id_list[j])) {
bool8 result = TRUE8;
PXvector filmPosition;
PXWorldToFilm(log->mega->actor_xyz, camera, result, filmPosition);
// i.e. his feet are visible !
if (result && (filmPosition.z < -g_actor_hither_plane))
log->mega->SetThisViewState(ON_CAMERA);
}
// Now test the view state flags and send an appropriate event
// Just walked on camera
if (log->mega->viewState == OFF_ON_CAMERA) {
g_oEventManager->PostNamedEvent(EVENT_ON_CAMERA, voxel_id_list[j]);
}
// Just walked off camera
else if (log->mega->viewState == ON_OFF_CAMERA) {
g_oEventManager->PostNamedEvent(EVENT_OFF_CAMERA, voxel_id_list[j]);
}
}
}
} // End of namespace ICB