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