1 /* ResidualVM - A 3D game interpreter
2 *
3 * ResidualVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the AUTHORS
5 * file distributed with this source distribution.
6 *
7 * Additional copyright for this file:
8 * Copyright (C) 1999-2000 Revolution Software Ltd.
9 * This code is based on source code created by Revolution Software,
10 * used with permission.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 *
26 */
27
28 #include "engines/icb/p4.h"
29 #include "engines/icb/common/px_common.h"
30 #include "engines/icb/common/px_floor_map.h"
31 #include "engines/icb/common/px_linkeddatafile.h"
32 #include "engines/icb/mission.h"
33 #include "engines/icb/session.h"
34 #include "engines/icb/object_structs.h"
35 #include "engines/icb/debug.h"
36 #include "engines/icb/floors.h"
37 #include "engines/icb/global_objects.h"
38 #include "engines/icb/res_man.h"
39
40 namespace ICB {
41
_floor_world()42 _floor_world::_floor_world() {}
43
___init()44 void _floor_world::___init() {
45 // init the floor and exit object
46 // works out how many floor levels are in the map and makes a list of them
47
48 // sets total_floors
49 // total_heights
50 // fills heights[]
51
52 uint32 buf_hash = NULL_HASH;
53 uint32 j, k, len;
54 PXreal temp;
55 _floor *floor;
56
57 // load the file for this session
58 // When clustered the session files have the base stripped
59 len = sprintf(temp_buf, "%s", PX_FILENAME_FLOOR_MAP);
60 if (len > ENGINE_STRING_LEN)
61 Fatal_error("_floor_world::___init string len error");
62
63 uint32 cluster_hash = MS->Fetch_session_cluster_hash();
64 floors = (_linked_data_file *)private_session_resman->Res_open(temp_buf, buf_hash, MS->Fetch_session_cluster(), cluster_hash);
65
66 // Check the file schema
67 if (floors->GetHeaderVersion() != VERSION_PXWGFLOORS)
68 Fatal_error("Incorrect version number for floor data [%s] - file has %d, engine has %d", temp_buf, floors->GetHeaderVersion(), VERSION_PXWGFLOORS);
69
70 // set this for convenience
71 total_floors = floors->Fetch_number_of_items();
72 Tdebug("floors.txt", "##total floors %d", total_floors);
73
74 // check for no floors
75 if (!total_floors) // no floors :O
76 Fatal_error("session has no floors - engine cannot proceed");
77
78 if (total_floors > MAX_floors) // general legality check to catch corrupt files
79 Fatal_error("engine stopping due to suspicious PxWGFloors file - has %d floors", total_floors);
80
81 // get some useful stats
82 total_heights = 0; // set to 0
83
84 int32 nMissing = 0;
85 for (j = 0; j < total_floors; j++) {
86 floor = (_floor *)floors->Fetch_item_by_number(j);
87
88 if (total_heights) {
89 // see if this height is already defined
90 for (k = 0; k < total_heights; k++)
91 if (heights[k] == floor->base_height)
92 break; // already here
93
94 if (k == total_heights) {
95 // not found so register the new height
96 heights[total_heights++] = floor->base_height;
97
98 if (total_heights > MAX_slices)
99 Fatal_error("_floor_world::___init has run out of slices - %d found, %d allowed", total_heights, MAX_slices);
100 }
101 } else {
102 // register the first height
103 heights[0] = floor->base_height;
104 total_heights = 1;
105 }
106 }
107
108 if (nMissing > 0) {
109 Fatal_error("%d missing cameras : Game must terminate", nMissing);
110 }
111
112 // now sort the heights
113 if (total_heights > 1) {
114 for (j = 0; j < total_heights; j++) {
115 for (k = 0; k < total_heights - 1; k++) {
116 if (heights[k] > heights[k + 1]) {
117 temp = heights[k + 1];
118 heights[k + 1] = heights[k];
119 heights[k] = temp;
120 }
121 }
122 }
123 }
124
125 // create a dummy top floor figure for floor_y_volume creation
126 heights[total_heights] = REAL_LARGE;
127
128 Tdebug("floors.txt", "\n\n\n\n%d different heights", total_heights);
129 for (j = 0; j < total_heights; j++)
130 Tdebug("floors.txt", " %3.1f", heights[j]);
131
132 Tdebug("floors.txt", "\n\n\ncreating floor y volume table\n");
133
134 // in-case second time around
135
136 // create room height table
137
138 for (j = 0; j < total_floors; j++) {
139 floor = (_floor *)floors->Fetch_item_by_number(j);
140
141 for (k = 0; k < total_heights; k++) {
142 if (floor->base_height == heights[k]) {
143 floor_y_volume[j] = (heights[k + 1] - REAL_ONE);
144 Tdebug("floors.txt", "floor %d, base %3.2f, top %3.2f", j, floor->base_height, floor_y_volume[j]);
145 }
146 }
147 }
148 }
149
On_a_floor(_mega * mega)150 bool8 _floor_world::On_a_floor(_mega *mega) {
151 // is a mega on a floor
152 // used to dismiss gunshots when on stairs, etc.
153 uint32 j;
154
155 for (j = 0; j < total_heights; j++)
156 if (mega->actor_xyz.y == heights[j])
157 return TRUE8;
158
159 return FALSE8;
160 }
161
Allign_with_floor(_mega * mega)162 void _floor_world::Allign_with_floor(_mega *mega) {
163 uint32 j;
164
165 // check against actualy heights
166 for (j = 0; j < total_heights; j++) {
167 if (mega->actor_xyz.y == heights[j])
168 return;
169 }
170
171 // not on one so align with one if we are pretty near
172 for (j = 0; j < total_heights; j++) {
173 if (PXfabs(mega->actor_xyz.y - heights[j]) < (REAL_ONE * 15)) {
174 mega->actor_xyz.y = heights[j];
175 return;
176 }
177 }
178 }
179
Return_true_y(PXreal y)180 PXreal _floor_world::Return_true_y(PXreal y) {
181 // snap a y coordinate up or down to the floor it is meant to be
182 // used by walk area camera director
183 uint32 j;
184
185 // check against actualy heights
186 for (j = 0; j < total_heights; j++)
187 if (y == heights[j])
188 return y;
189
190 // not on one so align with one if we are pretty near
191 for (j = 0; j < total_heights; j++)
192 if (PXfabs(y - heights[j]) < (REAL_ONE * 15)) {
193 y = heights[j];
194 return y;
195 }
196
197 return y;
198 }
199
~_floor_world()200 _floor_world::~_floor_world() {
201 //_floor_world destructor
202 Zdebug("*_floor_world destructing*");
203 }
204
Fetch_floor_number_by_name(const char * name)205 uint32 _floor_world::Fetch_floor_number_by_name(const char *name) {
206 // return a pointer to a named floor to an external routine - most likely a fn_funtion
207 return (floors->Fetch_item_number_by_name(name));
208 }
209
Return_floor_rect(PXreal x,PXreal z,PXreal y,uint32 rubber)210 uint32 _floor_world::Return_floor_rect(PXreal x, PXreal z, PXreal y, uint32 rubber) {
211 // find the floor LRECT that point x,y,z lies within
212 // returns rect number and pointer to _rect
213 // or PXNULL
214 uint32 j;
215
216 // search through all floors
217 for (j = 0; j < total_floors; j++) {
218 _floor *floor;
219
220 floor = (_floor *)floors->Fetch_item_by_number(j);
221
222 if (floor->base_height == (int32)y) {
223 // this floor is in our view level
224
225 // check our x,z against all the rects
226
227 // if hit then return floor number
228 if ((x >= (PXreal)(floor->rect.x1 - rubber)) && (x <= (PXreal)(floor->rect.x2 + rubber)) && (z >= (PXreal)(floor->rect.z1 - rubber)) &&
229 (z <= (PXreal)(floor->rect.z2 + rubber)))
230 return (j);
231 }
232 }
233
234 // point is not on any floor rect
235 return (PXNULL);
236 }
237
Point_on_rubber_floor(PXreal x,PXreal z,PXreal y,uint32 rubber,uint32 rect_num)238 bool8 _floor_world::Point_on_rubber_floor(PXreal x, PXreal z, PXreal y, uint32 rubber, uint32 rect_num) {
239 _floor *floor;
240
241 floor = (_floor *)floors->Fetch_item_by_number(rect_num);
242
243 if (floor->base_height == (int32)y) {
244 // if hit then return floor number
245 if ((x >= (PXreal)(floor->rect.x1 - rubber)) && (x <= (PXreal)(floor->rect.x2 + rubber)) && (z >= (PXreal)(floor->rect.z1 - rubber)) &&
246 (z <= (PXreal)(floor->rect.z2 + rubber)))
247 return TRUE8;
248 }
249
250 // point is not on floor rect
251 return FALSE8;
252 }
253
Locate_floor_rect(PXreal x,PXreal z,PXreal y,_floor ** rct)254 uint32 _floor_world::Locate_floor_rect(PXreal x, PXreal z, PXreal y, _floor **rct) {
255 // find the floor RECT that point x,y,z lies within
256
257 // returns rect number and pointer to _rect
258 // or PXNULL
259 uint32 j;
260
261 for (j = 0; j < total_floors; j++) {
262 _floor *floor;
263
264 floor = (_floor *)floors->Fetch_item_by_number(j);
265
266 if (floor->base_height == (int32)y) {
267 // this floor is in our view level
268
269 // check our x,z against all the rects
270
271 // if hit then return floor number
272 if ((x >= (PXreal)floor->rect.x1) && (x <= (PXreal)floor->rect.x2) && (z >= (PXreal)floor->rect.z1) && (z <= (PXreal)floor->rect.z2)) {
273 *rct = floor;
274 return (j);
275 }
276 }
277 }
278
279 // point is not on any floor rect
280 Message_box("no floor");
281 return (PXNULL);
282 }
283
Set_floor_rect_flag(_logic * log)284 void _floor_world::Set_floor_rect_flag(_logic *log) {
285 // find the floor RECT that character belongs to and fill in the owner_floor_rect flag
286
287 // note - there are ways to speed this up. We could record the rect and then only do a full search if the object
288 // moves outside the recorded rect again
289 uint32 j;
290 _floor *floor;
291 PXreal y;
292
293 #define FLOOR_RUBBER (20 * REAL_ONE)
294
295 // y locking
296 if (log->mega->y_locked)
297 y = log->mega->y_lock;
298 else
299 y = log->mega->actor_xyz.y;
300 // ylocking
301
302 // first see if we're one same one as last time
303 floor = (_floor *)floors->Fetch_item_by_number(log->owner_floor_rect);
304 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
305 if ((log->mega->actor_xyz.x >= (floor->rect.x1 - FLOOR_RUBBER)) && (log->mega->actor_xyz.x <= (floor->rect.x2 + FLOOR_RUBBER)) &&
306 (log->mega->actor_xyz.z >= (floor->rect.z1 - FLOOR_RUBBER)) && (log->mega->actor_xyz.z <= (floor->rect.z2 + FLOOR_RUBBER))) {
307 Zdebug("[%s]still on %d", MS->Fetch_object_name(MS->Fetch_cur_id()), log->owner_floor_rect);
308 return; // yup, still hitting!
309 }
310
311 // search through all floors
312 for (j = 0; j < total_floors; j++) {
313 floor = (_floor *)floors->Fetch_item_by_number(j);
314
315 if ((y >= (floor->base_height - (0 * REAL_ONE))) && ((y <= (floor_y_volume[j] - (0 * REAL_ONE))))) {
316 // this floor is in our view level
317 // if hit then return floor number
318 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) &&
319 (log->mega->actor_xyz.z <= floor->rect.z2)) {
320 log->owner_floor_rect = j;
321 return;
322 }
323 }
324 }
325
326 // point is not on any floor rect
327 // hmmm, well, hold previous value i guess
328
329 Tdebug("warning.txt", "Set_floor_rect_flag; %s has no floor", MS->Fetch_object_name(MS->Fetch_cur_id()));
330 }
331
Return_non_rubber_floor_no(_logic * log,uint32 cur_rubber_floor)332 uint32 _floor_world::Return_non_rubber_floor_no(_logic *log, uint32 cur_rubber_floor) {
333 // return exact box
334 // used by camera director when leaving WA's
335
336 uint32 j;
337 _floor *floor;
338
339 // first see if we're one same one as last time
340 floor = (_floor *)floors->Fetch_item_by_number(cur_rubber_floor);
341 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
342 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)) &&
343 (log->mega->actor_xyz.z <= (floor->rect.z2))) {
344 return cur_rubber_floor; // yup, still hitting!
345 }
346
347 // search through all floors
348 for (j = 0; j < total_floors; j++) {
349 floor = (_floor *)floors->Fetch_item_by_number(j);
350
351 if ((log->mega->actor_xyz.y >= floor->base_height) && ((log->mega->actor_xyz.y <= floor_y_volume[j]))) {
352 // this floor is in our view level
353 // if hit then return floor number
354 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) &&
355 (log->mega->actor_xyz.z <= floor->rect.z2)) {
356 return j;
357 }
358 }
359 }
360
361 // point is not on any floor rect
362 // hmmm, well, hold previous value i guess
363
364 return cur_rubber_floor;
365 }
366
Gravitise_y(PXreal y)367 PXreal _floor_world::Gravitise_y(PXreal y) {
368 // pull a y coordinate back to a floor height
369
370 int32 j;
371
372 for (j = total_heights - 1; j != -1; j--) { // 4 heights == j=3 == [0][1][2][3]
373 if (y >= heights[j]) {
374 return (heights[j]);
375 }
376 }
377
378 Zdebug("\n\nGravitise_y %3.2f", y);
379
380 for (j = 0; j < (int32)total_heights; j++)
381 Zdebug("%d [%3.2f]", j, heights[j]);
382
383 Fatal_error("Gravitise_y finds major height problem - %s", MS->Fetch_object_name(MS->Fetch_cur_id()));
384
385 return (y);
386 }
387
Floor_safe_gravitise_y(PXreal fY)388 PXreal _floor_world::Floor_safe_gravitise_y(PXreal fY) {
389 int32 i;
390
391 // This function does the same as Gravitise_y() but does not Fatal_error if it
392 // falls out of the bottom of the game world. This is to correct faults in the
393 // art (surprise, surprise) that were causing megas on ladders to briefly have a
394 // y-coordinate lower than the floor the ladder bottom is on.
395 for (i = total_heights - 1; i != -1; --i) {
396 if (fY >= heights[i])
397 return (heights[i]);
398 }
399
400 // Simply return the lowest floor height.
401 return (heights[0]);
402 }
403
Project_point_down_through_floors(int32 nX,int32 nY,int32 nZ)404 int32 _floor_world::Project_point_down_through_floors(int32 nX, int32 nY, int32 nZ) {
405 int32 nSliceIndex;
406 uint32 j;
407 _floor *pFloor;
408
409 // Do what the normal Gravitise_y() does to place the point on the slice height below it.
410 nSliceIndex = total_heights - 1;
411 while ((nSliceIndex > -1) && (nY < (int32)heights[nSliceIndex]))
412 --nSliceIndex;
413
414 // See which loop condition failed.
415 if (nSliceIndex == -1) {
416 // Fell out of the bottom of the floor world, but this is not an error in this function.
417 return (-1);
418 }
419
420 // Right, we have put the point on a slice. While there are slices still to go
421 // beneath the current point, we check if the point lies within a floor rectangle
422 // on that height.
423 while (nSliceIndex > -1) {
424 nY = (int32)heights[nSliceIndex];
425
426 for (j = 0; j < total_floors; ++j) {
427 pFloor = (_floor *)floors->Fetch_item_by_number(j);
428
429 if (pFloor->base_height == nY) {
430 // Floor at this height, so check its position.
431 if ((nX >= pFloor->rect.x1) && (nX <= pFloor->rect.x2) && (nZ >= pFloor->rect.z1) && (nZ <= pFloor->rect.z2)) {
432 return (nSliceIndex);
433 }
434 }
435 }
436
437 // Right, the point hit nothing on that level. Move to the slice below.
438 --nSliceIndex;
439 }
440
441 // If we fell out, it is not an error. It simply means there is no floor beneath
442 // the point we are checking.
443 return (-1);
444 }
445
446 } // End of namespace ICB
447