mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-14 21:59:17 +00:00
446 lines
14 KiB
C++
446 lines
14 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_floor_map.h"
|
|
#include "engines/icb/common/px_linkeddatafile.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/floors.h"
|
|
#include "engines/icb/global_objects.h"
|
|
#include "engines/icb/res_man.h"
|
|
|
|
namespace ICB {
|
|
|
|
_floor_world::_floor_world() {}
|
|
|
|
void _floor_world::___init() {
|
|
// init the floor and exit object
|
|
// works out how many floor levels are in the map and makes a list of them
|
|
|
|
// sets total_floors
|
|
// total_heights
|
|
// fills heights[]
|
|
|
|
uint32 buf_hash = NULL_HASH;
|
|
uint32 j, k, len;
|
|
PXreal temp;
|
|
_floor *floor;
|
|
|
|
// load the file for this session
|
|
// When clustered the session files have the base stripped
|
|
len = sprintf(temp_buf, "%s", PX_FILENAME_FLOOR_MAP);
|
|
if (len > ENGINE_STRING_LEN)
|
|
Fatal_error("_floor_world::___init string len error");
|
|
|
|
uint32 cluster_hash = MS->Fetch_session_cluster_hash();
|
|
floors = (_linked_data_file *)private_session_resman->Res_open(temp_buf, buf_hash, MS->Fetch_session_cluster(), cluster_hash);
|
|
|
|
// Check the file schema
|
|
if (floors->GetHeaderVersion() != VERSION_PXWGFLOORS)
|
|
Fatal_error("Incorrect version number for floor data [%s] - file has %d, engine has %d", temp_buf, floors->GetHeaderVersion(), VERSION_PXWGFLOORS);
|
|
|
|
// set this for convenience
|
|
total_floors = floors->Fetch_number_of_items();
|
|
Tdebug("floors.txt", "##total floors %d", total_floors);
|
|
|
|
// check for no floors
|
|
if (!total_floors) // no floors :O
|
|
Fatal_error("session has no floors - engine cannot proceed");
|
|
|
|
if (total_floors > MAX_floors) // general legality check to catch corrupt files
|
|
Fatal_error("engine stopping due to suspicious PxWGFloors file - has %d floors", total_floors);
|
|
|
|
// get some useful stats
|
|
total_heights = 0; // set to 0
|
|
|
|
int32 nMissing = 0;
|
|
for (j = 0; j < total_floors; j++) {
|
|
floor = (_floor *)floors->Fetch_item_by_number(j);
|
|
|
|
if (total_heights) {
|
|
// see if this height is already defined
|
|
for (k = 0; k < total_heights; k++)
|
|
if (heights[k] == floor->base_height)
|
|
break; // already here
|
|
|
|
if (k == total_heights) {
|
|
// not found so register the new height
|
|
heights[total_heights++] = floor->base_height;
|
|
|
|
if (total_heights > MAX_slices)
|
|
Fatal_error("_floor_world::___init has run out of slices - %d found, %d allowed", total_heights, MAX_slices);
|
|
}
|
|
} else {
|
|
// register the first height
|
|
heights[0] = floor->base_height;
|
|
total_heights = 1;
|
|
}
|
|
}
|
|
|
|
if (nMissing > 0) {
|
|
Fatal_error("%d missing cameras : Game must terminate", nMissing);
|
|
}
|
|
|
|
// now sort the heights
|
|
if (total_heights > 1) {
|
|
for (j = 0; j < total_heights; j++) {
|
|
for (k = 0; k < total_heights - 1; k++) {
|
|
if (heights[k] > heights[k + 1]) {
|
|
temp = heights[k + 1];
|
|
heights[k + 1] = heights[k];
|
|
heights[k] = temp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// create a dummy top floor figure for floor_y_volume creation
|
|
heights[total_heights] = REAL_LARGE;
|
|
|
|
Tdebug("floors.txt", "\n\n\n\n%d different heights", total_heights);
|
|
for (j = 0; j < total_heights; j++)
|
|
Tdebug("floors.txt", " %3.1f", heights[j]);
|
|
|
|
Tdebug("floors.txt", "\n\n\ncreating floor y volume table\n");
|
|
|
|
// in-case second time around
|
|
|
|
// create room height table
|
|
|
|
for (j = 0; j < total_floors; j++) {
|
|
floor = (_floor *)floors->Fetch_item_by_number(j);
|
|
|
|
for (k = 0; k < total_heights; k++) {
|
|
if (floor->base_height == heights[k]) {
|
|
floor_y_volume[j] = (heights[k + 1] - REAL_ONE);
|
|
Tdebug("floors.txt", "floor %d, base %3.2f, top %3.2f", j, floor->base_height, floor_y_volume[j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool8 _floor_world::On_a_floor(_mega *mega) {
|
|
// is a mega on a floor
|
|
// used to dismiss gunshots when on stairs, etc.
|
|
uint32 j;
|
|
|
|
for (j = 0; j < total_heights; j++)
|
|
if (mega->actor_xyz.y == heights[j])
|
|
return TRUE8;
|
|
|
|
return FALSE8;
|
|
}
|
|
|
|
void _floor_world::Allign_with_floor(_mega *mega) {
|
|
uint32 j;
|
|
|
|
// check against actualy heights
|
|
for (j = 0; j < total_heights; j++) {
|
|
if (mega->actor_xyz.y == heights[j])
|
|
return;
|
|
}
|
|
|
|
// not on one so align with one if we are pretty near
|
|
for (j = 0; j < total_heights; j++) {
|
|
if (PXfabs(mega->actor_xyz.y - heights[j]) < (REAL_ONE * 15)) {
|
|
mega->actor_xyz.y = heights[j];
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
PXreal _floor_world::Return_true_y(PXreal y) {
|
|
// snap a y coordinate up or down to the floor it is meant to be
|
|
// used by walk area camera director
|
|
uint32 j;
|
|
|
|
// check against actualy heights
|
|
for (j = 0; j < total_heights; j++)
|
|
if (y == heights[j])
|
|
return y;
|
|
|
|
// not on one so align with one if we are pretty near
|
|
for (j = 0; j < total_heights; j++)
|
|
if (PXfabs(y - heights[j]) < (REAL_ONE * 15)) {
|
|
y = heights[j];
|
|
return y;
|
|
}
|
|
|
|
return y;
|
|
}
|
|
|
|
_floor_world::~_floor_world() {
|
|
//_floor_world destructor
|
|
Zdebug("*_floor_world destructing*");
|
|
}
|
|
|
|
uint32 _floor_world::Fetch_floor_number_by_name(const char *name) {
|
|
// return a pointer to a named floor to an external routine - most likely a fn_funtion
|
|
return (floors->Fetch_item_number_by_name(name));
|
|
}
|
|
|
|
uint32 _floor_world::Return_floor_rect(PXreal x, PXreal z, PXreal y, uint32 rubber) {
|
|
// find the floor LRECT that point x,y,z lies within
|
|
// returns rect number and pointer to _rect
|
|
// or PXNULL
|
|
uint32 j;
|
|
|
|
// search through all floors
|
|
for (j = 0; j < total_floors; j++) {
|
|
_floor *floor;
|
|
|
|
floor = (_floor *)floors->Fetch_item_by_number(j);
|
|
|
|
if (floor->base_height == (int32)y) {
|
|
// this floor is in our view level
|
|
|
|
// check our x,z against all the rects
|
|
|
|
// if hit then return floor number
|
|
if ((x >= (PXreal)(floor->rect.x1 - rubber)) && (x <= (PXreal)(floor->rect.x2 + rubber)) && (z >= (PXreal)(floor->rect.z1 - rubber)) &&
|
|
(z <= (PXreal)(floor->rect.z2 + rubber)))
|
|
return (j);
|
|
}
|
|
}
|
|
|
|
// point is not on any floor rect
|
|
return (PXNULL);
|
|
}
|
|
|
|
bool8 _floor_world::Point_on_rubber_floor(PXreal x, PXreal z, PXreal y, uint32 rubber, uint32 rect_num) {
|
|
_floor *floor;
|
|
|
|
floor = (_floor *)floors->Fetch_item_by_number(rect_num);
|
|
|
|
if (floor->base_height == (int32)y) {
|
|
// if hit then return floor number
|
|
if ((x >= (PXreal)(floor->rect.x1 - rubber)) && (x <= (PXreal)(floor->rect.x2 + rubber)) && (z >= (PXreal)(floor->rect.z1 - rubber)) &&
|
|
(z <= (PXreal)(floor->rect.z2 + rubber)))
|
|
return TRUE8;
|
|
}
|
|
|
|
// point is not on floor rect
|
|
return FALSE8;
|
|
}
|
|
|
|
uint32 _floor_world::Locate_floor_rect(PXreal x, PXreal z, PXreal y, _floor **rct) {
|
|
// find the floor RECT that point x,y,z lies within
|
|
|
|
// returns rect number and pointer to _rect
|
|
// or PXNULL
|
|
uint32 j;
|
|
|
|
for (j = 0; j < total_floors; j++) {
|
|
_floor *floor;
|
|
|
|
floor = (_floor *)floors->Fetch_item_by_number(j);
|
|
|
|
if (floor->base_height == (int32)y) {
|
|
// this floor is in our view level
|
|
|
|
// check our x,z against all the rects
|
|
|
|
// if hit then return floor number
|
|
if ((x >= (PXreal)floor->rect.x1) && (x <= (PXreal)floor->rect.x2) && (z >= (PXreal)floor->rect.z1) && (z <= (PXreal)floor->rect.z2)) {
|
|
*rct = floor;
|
|
return (j);
|
|
}
|
|
}
|
|
}
|
|
|
|
// point is not on any floor rect
|
|
Message_box("no floor");
|
|
return (PXNULL);
|
|
}
|
|
|
|
void _floor_world::Set_floor_rect_flag(_logic *log) {
|
|
// find the floor RECT that character belongs to and fill in the owner_floor_rect flag
|
|
|
|
// note - there are ways to speed this up. We could record the rect and then only do a full search if the object
|
|
// moves outside the recorded rect again
|
|
uint32 j;
|
|
_floor *floor;
|
|
PXreal y;
|
|
|
|
#define FLOOR_RUBBER (20 * REAL_ONE)
|
|
|
|
// y locking
|
|
if (log->mega->y_locked)
|
|
y = log->mega->y_lock;
|
|
else
|
|
y = log->mega->actor_xyz.y;
|
|
// ylocking
|
|
|
|
// first see if we're one same one as last time
|
|
floor = (_floor *)floors->Fetch_item_by_number(log->owner_floor_rect);
|
|
if ((y >= (floor->base_height - (0 * REAL_ONE))) && ((y <= (floor_y_volume[log->owner_floor_rect] - (0 * REAL_ONE))))) // this floor is in our view level
|
|
if ((log->mega->actor_xyz.x >= (floor->rect.x1 - FLOOR_RUBBER)) && (log->mega->actor_xyz.x <= (floor->rect.x2 + FLOOR_RUBBER)) &&
|
|
(log->mega->actor_xyz.z >= (floor->rect.z1 - FLOOR_RUBBER)) && (log->mega->actor_xyz.z <= (floor->rect.z2 + FLOOR_RUBBER))) {
|
|
Zdebug("[%s]still on %d", MS->Fetch_object_name(MS->Fetch_cur_id()), log->owner_floor_rect);
|
|
return; // yup, still hitting!
|
|
}
|
|
|
|
// search through all floors
|
|
for (j = 0; j < total_floors; j++) {
|
|
floor = (_floor *)floors->Fetch_item_by_number(j);
|
|
|
|
if ((y >= (floor->base_height - (0 * REAL_ONE))) && ((y <= (floor_y_volume[j] - (0 * REAL_ONE))))) {
|
|
// this floor is in our view level
|
|
// if hit then return floor number
|
|
if ((log->mega->actor_xyz.x >= floor->rect.x1) && (log->mega->actor_xyz.x <= floor->rect.x2) && (log->mega->actor_xyz.z >= floor->rect.z1) &&
|
|
(log->mega->actor_xyz.z <= floor->rect.z2)) {
|
|
log->owner_floor_rect = j;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// point is not on any floor rect
|
|
// hmmm, well, hold previous value i guess
|
|
|
|
Tdebug("warning.txt", "Set_floor_rect_flag; %s has no floor", MS->Fetch_object_name(MS->Fetch_cur_id()));
|
|
}
|
|
|
|
uint32 _floor_world::Return_non_rubber_floor_no(_logic *log, uint32 cur_rubber_floor) {
|
|
// return exact box
|
|
// used by camera director when leaving WA's
|
|
|
|
uint32 j;
|
|
_floor *floor;
|
|
|
|
// first see if we're one same one as last time
|
|
floor = (_floor *)floors->Fetch_item_by_number(cur_rubber_floor);
|
|
if ((log->mega->actor_xyz.y >= floor->base_height) && ((log->mega->actor_xyz.y <= floor_y_volume[log->owner_floor_rect]))) // this floor is in our view level
|
|
if ((log->mega->actor_xyz.x >= (floor->rect.x1)) && (log->mega->actor_xyz.x <= (floor->rect.x2)) && (log->mega->actor_xyz.z >= (floor->rect.z1)) &&
|
|
(log->mega->actor_xyz.z <= (floor->rect.z2))) {
|
|
return cur_rubber_floor; // yup, still hitting!
|
|
}
|
|
|
|
// search through all floors
|
|
for (j = 0; j < total_floors; j++) {
|
|
floor = (_floor *)floors->Fetch_item_by_number(j);
|
|
|
|
if ((log->mega->actor_xyz.y >= floor->base_height) && ((log->mega->actor_xyz.y <= floor_y_volume[j]))) {
|
|
// this floor is in our view level
|
|
// if hit then return floor number
|
|
if ((log->mega->actor_xyz.x >= floor->rect.x1) && (log->mega->actor_xyz.x <= floor->rect.x2) && (log->mega->actor_xyz.z >= floor->rect.z1) &&
|
|
(log->mega->actor_xyz.z <= floor->rect.z2)) {
|
|
return j;
|
|
}
|
|
}
|
|
}
|
|
|
|
// point is not on any floor rect
|
|
// hmmm, well, hold previous value i guess
|
|
|
|
return cur_rubber_floor;
|
|
}
|
|
|
|
PXreal _floor_world::Gravitise_y(PXreal y) {
|
|
// pull a y coordinate back to a floor height
|
|
|
|
int32 j;
|
|
|
|
for (j = total_heights - 1; j != -1; j--) { // 4 heights == j=3 == [0][1][2][3]
|
|
if (y >= heights[j]) {
|
|
return (heights[j]);
|
|
}
|
|
}
|
|
|
|
Zdebug("\n\nGravitise_y %3.2f", y);
|
|
|
|
for (j = 0; j < (int32)total_heights; j++)
|
|
Zdebug("%d [%3.2f]", j, heights[j]);
|
|
|
|
Fatal_error("Gravitise_y finds major height problem - %s", MS->Fetch_object_name(MS->Fetch_cur_id()));
|
|
|
|
return (y);
|
|
}
|
|
|
|
PXreal _floor_world::Floor_safe_gravitise_y(PXreal fY) {
|
|
int32 i;
|
|
|
|
// This function does the same as Gravitise_y() but does not Fatal_error if it
|
|
// falls out of the bottom of the game world. This is to correct faults in the
|
|
// art (surprise, surprise) that were causing megas on ladders to briefly have a
|
|
// y-coordinate lower than the floor the ladder bottom is on.
|
|
for (i = total_heights - 1; i != -1; --i) {
|
|
if (fY >= heights[i])
|
|
return (heights[i]);
|
|
}
|
|
|
|
// Simply return the lowest floor height.
|
|
return (heights[0]);
|
|
}
|
|
|
|
int32 _floor_world::Project_point_down_through_floors(int32 nX, int32 nY, int32 nZ) {
|
|
int32 nSliceIndex;
|
|
uint32 j;
|
|
_floor *pFloor;
|
|
|
|
// Do what the normal Gravitise_y() does to place the point on the slice height below it.
|
|
nSliceIndex = total_heights - 1;
|
|
while ((nSliceIndex > -1) && (nY < (int32)heights[nSliceIndex]))
|
|
--nSliceIndex;
|
|
|
|
// See which loop condition failed.
|
|
if (nSliceIndex == -1) {
|
|
// Fell out of the bottom of the floor world, but this is not an error in this function.
|
|
return (-1);
|
|
}
|
|
|
|
// Right, we have put the point on a slice. While there are slices still to go
|
|
// beneath the current point, we check if the point lies within a floor rectangle
|
|
// on that height.
|
|
while (nSliceIndex > -1) {
|
|
nY = (int32)heights[nSliceIndex];
|
|
|
|
for (j = 0; j < total_floors; ++j) {
|
|
pFloor = (_floor *)floors->Fetch_item_by_number(j);
|
|
|
|
if (pFloor->base_height == nY) {
|
|
// Floor at this height, so check its position.
|
|
if ((nX >= pFloor->rect.x1) && (nX <= pFloor->rect.x2) && (nZ >= pFloor->rect.z1) && (nZ <= pFloor->rect.z2)) {
|
|
return (nSliceIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Right, the point hit nothing on that level. Move to the slice below.
|
|
--nSliceIndex;
|
|
}
|
|
|
|
// If we fell out, it is not an error. It simply means there is no floor beneath
|
|
// the point we are checking.
|
|
return (-1);
|
|
}
|
|
|
|
} // End of namespace ICB
|