1 #include <math.h>
2 #include "Font_Control.h"
3 #include "Handle_Items.h"
4 #include "Structure.h"
5 #include "TileDef.h"
6 #include "Timer_Control.h"
7 #include "WCheck.h"
8 #include "Isometric_Utils.h"
9 #include "Debug.h"
10 #include "LOS.h"
11 #include "Animation_Control.h"
12 #include "Random.h"
13 #include "Soldier_Control.h"
14 #include "Overhead.h"
15 #include "Weapons.h"
16 #include "OppList.h"
17 #include "Bullets.h"
18 #include "Items.h"
19 #include "Soldier_Profile.h"
20 #include "WorldMan.h"
21 #include "Rotting_Corpses.h"
22 #include "Keys.h"
23 #include "Message.h"
24 #include "Structure_Wrap.h"
25 #include "Campaign.h"
26 #include "Environment.h"
27 #include "PathAI.h"
28 #include "Soldier_Macros.h"
29 #include "StrategicMap.h"
30 #include "Quests.h"
31 #include "Interface.h"
32 #include "Points.h"
33 #include "Smell.h"
34 #include "Text.h"
35 
36 #include "CalibreModel.h"
37 #include "ContentManager.h"
38 #include "GameInstance.h"
39 #include "WeaponModels.h"
40 #include "Logger.h"
41 
42 #define STEPS_FOR_BULLET_MOVE_TRAILS				10
43 #define STEPS_FOR_BULLET_MOVE_SMALL_TRAILS			5
44 #define STEPS_FOR_BULLET_MOVE_FIRE_TRAILS			5
45 
46 #define ALWAYS_CONSIDER_HIT					(STRUCTURE_WALLSTUFF | STRUCTURE_CAVEWALL | STRUCTURE_FENCE)
47 
48 
49 static const FIXEDPT gqStandardWallHeight         = INT32_TO_FIXEDPT(WALL_HEIGHT_UNITS);
50 static const FIXEDPT gqStandardWindowBottomHeight = INT32_TO_FIXEDPT(WINDOW_BOTTOM_HEIGHT_UNITS);
51 static const FIXEDPT gqStandardWindowTopHeight    = INT32_TO_FIXEDPT(WINDOW_TOP_HEIGHT_UNITS);
52 
53 
54 static const DOUBLE ddShotgunSpread[3][BUCKSHOT_SHOTS][2] =
55 {
56 	{
57 		// spread of about 2 degrees in all directions
58 		// Horiz, Vert
59 		{0.0, 0.0},
60 		{-0.012, 0.0},
61 		{+0.012, 0.0},
62 		{0.0, -0.012},
63 		{0.0, +0.012},
64 		{-0.008, -0.008},
65 		{-0.008, +0.008},
66 		{+0.008, -0.008},
67 		{+0.008, +0.008}
68 	},
69 	{
70 		// duckbill flattens the spread and makes it wider horizontally (5 degrees)
71 		// Horiz, Vert
72 		{0.0, 0.0},
73 		{-0.008, 0.0},
74 		{+0.008, 0.0},
75 		{-0.016, 0.0},
76 		{+0.016, 0.0},
77 		{-0.024, 0.0},
78 		{+0.024, 0.0},
79 		{-0.032, 0.0},
80 		{+0.032, 0.0},
81 	},
82 	{
83 		// flamethrower more spread out
84 		// Horiz, Vert
85 		{0.0, 0.0},
86 		{-0.120, 0.0},
87 		{+0.120, 0.0},
88 		{0.0, -0.120},
89 		{0.0, +0.120},
90 		{-0.080, -0.080},
91 		{-0.080, +0.080},
92 		{+0.080, -0.080},
93 		{+0.080, +0.080}
94 	},
95 
96 };
97 
98 static const UINT8 gubTreeSightReduction[ANIM_STAND + 1] =
99 {
100 	0,
101 	8, // prone
102 	0,
103 	7, // crouched
104 	0,
105 	0,
106 	6 // standing
107 };
108 
109 #define MAX_DIST_FOR_LESS_THAN_MAX_CHANCE_TO_HIT_STRUCTURE	25
110 
111 #define MAX_CHANCE_OF_HITTING_STRUCTURE			90
112 
113 static const UINT32 guiStructureHitChance[MAX_DIST_FOR_LESS_THAN_MAX_CHANCE_TO_HIT_STRUCTURE + 1] =
114 {
115 	0, // 0 tiles
116 	0,
117 	0,
118 	2,
119 	4,
120 	7, // 5 tiles
121 	10,
122 	14,
123 	18,
124 	23,
125 	28, // 10 tiles
126 	34,
127 	40,
128 	47,
129 	54,
130 	60, // 15 tiles
131 	66,
132 	71,
133 	74,
134 	76,
135 	78, // 20 tiles
136 	80,
137 	82,
138 	84,
139 	86,
140 	88, // 25 tiles
141 };
142 
143 
144 #define PERCENT_BULLET_SLOWED_BY_RANGE				25
145 
146 #define MIN_DIST_FOR_HIT_FRIENDS				30
147 #define MIN_DIST_FOR_HIT_FRIENDS_UNAIMED			15
148 #define MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE			3
149 
150 #define RADIANS_IN_CIRCLE					6.283
151 #define DEGREES_22_5						(RADIANS_IN_CIRCLE * 22.5 / 360)
152 #define DEGREES_45						(RADIANS_IN_CIRCLE * 45 / 360)
153 // note: these values are in RADIANS!!
154 // equal to 15 degrees
155 #define MAX_AIMING_SCREWUP					(RADIANS_IN_CIRCLE * 15 / 360)
156 // min aiming screwup is X degrees, gets divided by distance in tiles
157 #define MIN_AIMING_SCREWUP					(RADIANS_IN_CIRCLE * 22 / 360)
158 //#define MAX_AIMING_SCREWUP					0.2618
159 // equal to 10 degrees
160 //#define MAX_AIMING_SCREWUP_VERTIC				0.1745
161 
162 #define SMELL_REDUCTION_FOR_NEARBY_OBSTACLE			80
163 
164 #define STANDING_CUBES						3
165 
166 // MoveBullet and ChanceToGetThrough use this array to maintain which
167 // of which structures in a tile might be hit by a bullet.
168 
169 #define MAX_LOCAL_STRUCTURES					20
170 
171 static STRUCTURE* gpLocalStructure[MAX_LOCAL_STRUCTURES];
172 static UINT32     guiLocalStructureCTH[MAX_LOCAL_STRUCTURES];
173 static UINT8      gubLocalStructureNumTimesHit[MAX_LOCAL_STRUCTURES];
174 
175 
176 #ifdef LOS_DEBUG
177 LOSResults gLOSTestResults = {0};
178 #endif
179 
180 
FloatToFixed(FLOAT dN)181 static FIXEDPT FloatToFixed(FLOAT dN)
182 {
183 	FIXEDPT qN;
184 	// verify that dN is within the range storable by FIXEDPT?
185 
186 	// first get the whole part
187 	qN = (INT32) (dN * FIXEDPT_FRACTIONAL_RESOLUTION);
188 
189 	//qN = INT32_TO_FIXEDPT( (INT32)dN );
190 	// now add the fractional part
191 	//qN += (INT32)(((dN - (INT32) dN)) * FIXEDPT_FRACTIONAL_RESOLUTION);
192 
193 	return( qN );
194 }
195 
196 
FixedToFloat(FIXEDPT qN)197 static FLOAT FixedToFloat(FIXEDPT qN)
198 {
199 	return( ((FLOAT) qN)	/ FIXEDPT_FRACTIONAL_RESOLUTION );
200 }
201 
202 //
203 // fixed-point arithmetic stuff ends here
204 //
205 
206 
Distance3D(FLOAT dDeltaX,FLOAT dDeltaY,FLOAT dDeltaZ)207 static FLOAT Distance3D(FLOAT dDeltaX, FLOAT dDeltaY, FLOAT dDeltaZ)
208 {
209 	return ((FLOAT) sqrt( (DOUBLE)(dDeltaX * dDeltaX) +
210 			(DOUBLE)(dDeltaY * dDeltaY) + (DOUBLE)(dDeltaZ * dDeltaZ)));
211 }
212 
213 
Distance2D(FLOAT dDeltaX,FLOAT dDeltaY)214 static FLOAT Distance2D(FLOAT dDeltaX, FLOAT dDeltaY)
215 {
216 	return( (FLOAT) sqrt( (DOUBLE)(dDeltaX * dDeltaX) + (DOUBLE)(dDeltaY * dDeltaY) ));
217 }
218 
219 enum LocationCode
220 {
221 	LOC_OTHER,
222 	LOC_0_4,
223 	LOC_3_4,
224 	LOC_4_0,
225 	LOC_4_3,
226 	LOC_4_4
227 };
228 
229 
ResolveHitOnWall(STRUCTURE * pStructure,INT32 iGridNo,INT8 bLOSIndexX,INT8 bLOSIndexY,DOUBLE ddHorizAngle)230 static BOOLEAN ResolveHitOnWall(STRUCTURE* pStructure, INT32 iGridNo, INT8 bLOSIndexX, INT8 bLOSIndexY, DOUBLE ddHorizAngle)
231 {
232 	BOOLEAN fNorthSouth, fEastWest;
233 	BOOLEAN fTopLeft, fTopRight;
234 	INT8    bLocation = LOC_OTHER;
235 
236 	switch ( bLOSIndexX )
237 	{
238 		case 0:
239 			if ( bLOSIndexY == 4 )
240 			{
241 				bLocation = LOC_0_4;
242 			}
243 			break;
244 		case 3:
245 			if ( bLOSIndexY == 4 )
246 			{
247 				bLocation = LOC_3_4;
248 			}
249 			break;
250 		case 4:
251 			switch( bLOSIndexY )
252 			{
253 				case 0:
254 					bLocation = LOC_4_0;
255 					break;
256 				case 3:
257 					bLocation = LOC_4_3;
258 					break;
259 				case 4:
260 					bLocation = LOC_4_4;
261 					break;
262 				default:
263 					break;
264 			}
265 			break;
266 		default:
267 			break;
268 	}
269 
270 	if ( bLocation == LOC_OTHER )
271 	{
272 		// these spots always block
273 		return( TRUE );
274 	}
275 
276 	// use cartesian angles for god's sakes -CJC
277 	ddHorizAngle = -ddHorizAngle;
278 
279 	fNorthSouth = ((ddHorizAngle < (0) && ddHorizAngle > (-PI * 1 / 2)) ||
280 			(ddHorizAngle > (PI * 1 / 2) && ddHorizAngle < (PI)));
281 	fEastWest = ((ddHorizAngle > (0) && ddHorizAngle < (PI * 1 / 2)) ||
282 			(ddHorizAngle < (-PI * 1 / 2) && ddHorizAngle > (-PI)));
283 
284 	fTopLeft = (pStructure->ubWallOrientation == INSIDE_TOP_LEFT ||
285 			pStructure->ubWallOrientation == OUTSIDE_TOP_LEFT);
286 	fTopRight = (pStructure->ubWallOrientation == INSIDE_TOP_RIGHT ||
287 			pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT);
288 
289 	if ( fNorthSouth )
290 	{
291 		// Check N-S at west corner: 4,4 4,3 0,4
292 		if ( bLocation == LOC_4_3 || bLocation == LOC_4_4 )
293 		{
294 			// if wall orientation is top-right, then check S of this location
295 			// if wall orientation is top-left, then check E of this location
296 			// if no wall of same orientation there, let bullet through
297 			if ( fTopRight )
298 			{
299 				if (!WallOrClosedDoorExistsOfTopRightOrientation((INT16) (iGridNo + DirectionInc(SOUTH))) &&
300 					!WallOrClosedDoorExistsOfTopLeftOrientation((INT16) (iGridNo)) &&
301 					!OpenRightOrientedDoorWithDoorOnRightOfEdgeExists((INT16) (iGridNo + DirectionInc(SOUTH))))
302 				{
303 					return( FALSE );
304 				}
305 
306 			}
307 
308 		}
309 		else if ( bLocation == LOC_0_4 )
310 		{
311 			if ( fTopLeft )
312 			{
313 				if (!WallOrClosedDoorExistsOfTopLeftOrientation((INT16) (iGridNo + DirectionInc(WEST))) &&
314 					!WallOrClosedDoorExistsOfTopRightOrientation((INT16) (iGridNo + DirectionInc( SOUTHWEST))) &&
315 					!OpenLeftOrientedDoorWithDoorOnLeftOfEdgeExists((INT16) (iGridNo + DirectionInc( WEST))))
316 				{
317 					return( FALSE );
318 				}
319 			}
320 
321 		}
322 
323 		// Check N-S at east corner: 4,4 3,4 4,0
324 		if ( bLocation == LOC_4_4 || bLocation == LOC_3_4 )
325 		{
326 			if ( fTopLeft )
327 			{
328 				if (!WallOrClosedDoorExistsOfTopLeftOrientation((INT16) (iGridNo + DirectionInc(EAST))) &&
329 					!WallOrClosedDoorExistsOfTopRightOrientation((INT16) (iGridNo)) &&
330 					!OpenLeftOrientedDoorWithDoorOnLeftOfEdgeExists((INT16) (iGridNo + DirectionInc(EAST))))
331 				{
332 					return( FALSE );
333 				}
334 			}
335 		}
336 		else if ( bLocation == LOC_4_0 )
337 		{
338 			// if door is normal and OPEN and outside type then we let N-S pass
339 			if ( (pStructure->fFlags & STRUCTURE_DOOR) && (pStructure->fFlags & STRUCTURE_OPEN) )
340 			{
341 				if (pStructure->ubWallOrientation == OUTSIDE_TOP_LEFT ||
342 					pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT)
343 				{
344 					return( FALSE );
345 				}
346 			}
347 			else if ( fTopRight )
348 			{
349 				if (!WallOrClosedDoorExistsOfTopLeftOrientation((INT16) (iGridNo + DirectionInc(NORTHEAST))) &&
350 					!WallOrClosedDoorExistsOfTopRightOrientation((INT16) (iGridNo + DirectionInc( NORTH))) &&
351 					!OpenLeftOrientedDoorWithDoorOnLeftOfEdgeExists((INT16) (iGridNo + DirectionInc( NORTHEAST))))
352 				{
353 					return( FALSE );
354 				}
355 			}
356 		}
357 	}
358 
359 	if ( fEastWest )
360 	{
361 		// Check E-W at north corner:   4,4   4,0   0,4
362 		if ( bLocation == LOC_4_4 )
363 		{
364 			if ( pStructure->ubWallOrientation == NO_ORIENTATION)
365 			{
366 				// very top north corner of building, and going (screenwise) west or east
367 				return( FALSE );
368 			}
369 		}
370 		else if ( bLocation == LOC_4_0 )
371 		{
372 			// maybe looking E-W at (screenwise) north corner of building
373 			// if wall orientation is top-right, then check N of this location
374 			// if no wall of same orientation there, let bullet through
375 			if ( fTopRight )
376 			{
377 				if (!WallOrClosedDoorExistsOfTopRightOrientation((INT16) (iGridNo + DirectionInc(NORTH))) &&
378 					!WallOrClosedDoorExistsOfTopLeftOrientation((INT16) (iGridNo + DirectionInc( NORTH))))
379 				{
380 					return( FALSE );
381 				}
382 			}
383 		}
384 		else if ( bLocation == LOC_0_4 )
385 		{
386 			// if normal door and OPEN and inside type then we let E-W pass
387 			if ( (pStructure->fFlags & STRUCTURE_DOOR) && (pStructure->fFlags & STRUCTURE_OPEN) )
388 			{
389 				if (pStructure->ubWallOrientation == INSIDE_TOP_LEFT ||
390 					pStructure->ubWallOrientation == INSIDE_TOP_RIGHT)
391 				{
392 					return( FALSE );
393 				}
394 			}
395 
396 			// if wall orientation is top-left, then check W of this location
397 			// if no wall of same orientation there, let bullet through
398 			if ( fTopLeft )
399 			{
400 				if (!WallOrClosedDoorExistsOfTopLeftOrientation((INT16) (iGridNo + DirectionInc(WEST))) &&
401 					!WallOrClosedDoorExistsOfTopRightOrientation((INT16) (iGridNo + DirectionInc( WEST))))
402 				{
403 					return( FALSE );
404 				}
405 			}
406 
407 		}
408 
409 		// Check E-W at south corner:   4,4 3,4 4,3
410 		if ( bLocation == LOC_3_4 || bLocation == LOC_4_4 || bLocation == LOC_4_3 )
411 		{
412 			if ((bLocation == LOC_3_4 && fTopLeft) || (bLocation == LOC_4_3 && fTopRight) ||
413 				(bLocation == LOC_4_4))
414 			{
415 				if (!WallOrClosedDoorExistsOfTopLeftOrientation((INT16) (iGridNo + DirectionInc(EAST))) &&
416 					!WallOrClosedDoorExistsOfTopRightOrientation((INT16) (iGridNo + DirectionInc( SOUTH))) &&
417 					!OpenLeftOrientedDoorWithDoorOnLeftOfEdgeExists((INT16) (iGridNo + DirectionInc( EAST))) &&
418 					!OpenRightOrientedDoorWithDoorOnRightOfEdgeExists((INT16) (iGridNo + DirectionInc(SOUTH))))
419 				{
420 					return( FALSE );
421 				}
422 			}
423 		}
424 
425 	}
426 
427 
428 
429 
430 
431 	// currently handled:
432 	// E-W at north corner:  (4,4), (0,4), (4,0)
433 	// N-S at east corner: (4,4)
434 	// N-S at west corner: (4,4)
435 
436 	// could add:
437 	// N-S at east corner: (3, 4), (4, 0)
438 	// N-S at west corner: (0, 4), (4, 3)
439 	// E-W at south corner: (4, 4), (3, 4), (4, 3) (completely new)
440 
441 	/*
442 	// possibly shooting at corner in which case we should let it pass
443 	if ( bLOSIndexX == 0)
444 	{
445 		if ( bLOSIndexY == (PROFILE_Y_SIZE - 1))
446 		{
447 			// maybe looking E-W at (screenwise) north corner of building, or through open door
448 			if ( ( ddHorizAngle > (0) && ddHorizAngle < (PI * 1 / 2) ) || ( ddHorizAngle < (-PI * 1 / 2) && ddHorizAngle > ( -PI ) ) )
449 			{
450 				// if door is normal and OPEN and inside type then we let E-W pass
451 				if ( (pStructure->fFlags & STRUCTURE_DOOR) && (pStructure->fFlags & STRUCTURE_OPEN) )
452 				{
453 					if ( pStructure->ubWallOrientation == INSIDE_TOP_LEFT || pStructure->ubWallOrientation == INSIDE_TOP_RIGHT )
454 					{
455 						fResolveHit = FALSE;
456 					}
457 				}
458 
459 				// if wall orientation is top-left, then check W of this location
460 				// if no wall of same orientation there, let bullet through
461 				if ( pStructure->ubWallOrientation == INSIDE_TOP_LEFT || pStructure->ubWallOrientation == OUTSIDE_TOP_LEFT )
462 				{
463 					if (!WallOrClosedDoorExistsOfTopLeftOrientation( (INT16) (iGridNo + DirectionInc( WEST )) ) &&
464 							!WallOrClosedDoorExistsOfTopRightOrientation( (INT16) (iGridNo + DirectionInc( WEST )) ) )
465 					{
466 						fResolveHit = FALSE;
467 					}
468 				}
469 			}
470 			else if ( ( ddHorizAngle < (0) && ddHorizAngle > ( -PI * 1 / 2) ) || ( ddHorizAngle > ( PI * 1 / 2 ) && ddHorizAngle < (PI) ) )
471 			{
472 				// maybe looking N-S at (screenwise) west corner of building
473 
474 				// if wall orientation is top-left, then check W of this location
475 				// if no wall of same orientation there, let bullet through
476 				if ( pStructure->ubWallOrientation == INSIDE_TOP_LEFT || pStructure->ubWallOrientation == OUTSIDE_TOP_LEFT )
477 				{
478 					if ( !WallOrClosedDoorExistsOfTopLeftOrientation( (INT16) (iGridNo + DirectionInc( WEST )) ) &&
479 						!WallOrClosedDoorExistsOfTopRightOrientation( (INT16) (iGridNo + DirectionInc( SOUTHWEST ) ) ) &&
480 						!OpenLeftOrientedDoorWithDoorOnLeftOfEdgeExists( (INT16) (iGridNo + DirectionInc( WEST )) ) )
481 					{
482 						fResolveHit = FALSE;
483 					}
484 				}
485 
486 			}
487 
488 		}
489 	}
490 	else if (bLOSIndexX == (PROFILE_X_SIZE - 1))
491 	{
492 		if (bLOSIndexY == 0)
493 		{
494 			// maybe looking E-W at (screenwise) north corner of building
495 			if ( ( ddHorizAngle > (0) && ddHorizAngle < (PI * 1 / 2) ) || ( ddHorizAngle < (-PI * 1 / 2) && ddHorizAngle > ( -PI ) ) )
496 			{
497 				// if wall orientation is top-right, then check N of this location
498 				// if no wall of same orientation there, let bullet through
499 				if ( pStructure->ubWallOrientation == INSIDE_TOP_RIGHT || pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT )
500 				{
501 					if (!WallOrClosedDoorExistsOfTopRightOrientation( (INT16) (iGridNo + DirectionInc( NORTH )) ) &&
502 							!WallOrClosedDoorExistsOfTopLeftOrientation( (INT16) (iGridNo + DirectionInc( NORTH )) ) )
503 					{
504 						fResolveHit = FALSE;
505 					}
506 				}
507 			}
508 			else
509 			{
510 				// if door is normal and OPEN and outside type then we let N-S pass
511 				if ( (pStructure->fFlags & STRUCTURE_DOOR) && (pStructure->fFlags & STRUCTURE_OPEN) )
512 				{
513 					if ( pStructure->ubWallOrientation == OUTSIDE_TOP_LEFT || pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT )
514 					{
515 						fResolveHit = FALSE;
516 					}
517 				}
518 			}
519 		}
520 		else if ( bLOSIndexY == (PROFILE_Y_SIZE - 1) || bLOSIndexY == (PROFILE_Y_SIZE - 2) )
521 		{
522 			// maybe (SCREENWISE) west or east corner of building and looking N
523 			if ( ( ddHorizAngle < (0) && ddHorizAngle > ( -PI * 1 / 2) ) || ( ddHorizAngle > ( PI * 1 / 2 ) && ddHorizAngle < (PI) ) )
524 			{
525 				// if wall orientation is top-right, then check S of this location
526 				// if wall orientation is top-left, then check E of this location
527 				// if no wall of same orientation there, let bullet through
528 				if ( pStructure->ubWallOrientation == INSIDE_TOP_LEFT || pStructure->ubWallOrientation == OUTSIDE_TOP_LEFT )
529 				{
530 					if ( !WallOrClosedDoorExistsOfTopLeftOrientation( (INT16) (iGridNo + DirectionInc( EAST )) ) &&
531 						!WallOrClosedDoorExistsOfTopRightOrientation( (INT16) (iGridNo) ) &&
532 						!OpenLeftOrientedDoorWithDoorOnLeftOfEdgeExists( (INT16) (iGridNo + DirectionInc( EAST )) ) )
533 					{
534 						fResolveHit = FALSE;
535 					}
536 				}
537 				else if ( pStructure->ubWallOrientation == INSIDE_TOP_RIGHT || pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT )
538 				{
539 					if (!WallOrClosedDoorExistsOfTopRightOrientation( (INT16) (iGridNo + DirectionInc( SOUTH )) ) &&
540 							!WallOrClosedDoorExistsOfTopLeftOrientation( (INT16) (iGridNo) ) &&
541 							!OpenRightOrientedDoorWithDoorOnRightOfEdgeExists( (INT16) (iGridNo + DirectionInc( SOUTH )) ) )
542 					{
543 						fResolveHit = FALSE;
544 					}
545 
546 				}
547 			}
548 			// the following only at 4,4
549 			else if ( bLOSIndexY == (PROFILE_Y_SIZE - 1) )
550 			{
551 				if ( pStructure->ubWallOrientation == NO_ORIENTATION)
552 				{
553 					// very top north corner of building, and going (screenwise) west or east
554 					fResolveHit = FALSE;
555 				}
556 			}
557 		}
558 	}*/
559 
560 	return( TRUE );
561 }
562 
563 
564 
565 // The line of sight code is now used to simulate smelling through the air (for monsters);
566 // It obeys the following rules:
567 // - ignores trees and vegetation
568 // - ignores people
569 // - should always start off with head height for both source and target, so that lying down makes no difference
570 // - stop at closed windows
571 // - stop for other obstacles
572 //
573 // Just for reference, normal sight obeys the following rules:
574 // - trees & vegetation reduce the maximum sighting distance
575 // - ignores people
576 // - starts at height relative to stance
577 // - ignores windows
578 // - stops at other obstacles
LineOfSightTest(GridNo start_pos,FLOAT dStartZ,GridNo end_pos,FLOAT dEndZ,UINT8 ubTileSightLimit,UINT8 ubTreeSightReduction,INT8 bAware,INT8 bCamouflage,BOOLEAN fSmell,INT16 * psWindowGridNo)579 static INT32 LineOfSightTest(GridNo start_pos, FLOAT dStartZ, GridNo end_pos, FLOAT dEndZ, UINT8 ubTileSightLimit, UINT8 ubTreeSightReduction, INT8 bAware, INT8 bCamouflage, BOOLEAN fSmell, INT16* psWindowGridNo)
580 {
581 	// Parameters...
582 	// the X,Y,Z triplets should be obvious
583 	// TileSightLimit is the max # of tiles of distance visible
584 	// TreeSightReduction is the reduction in 10ths of tiles in max visibility for each LOS cube (5th of a tile) of
585 	// vegetation hit
586 	// Aware is whether the looker is aware of the target
587 	// Smell is whether this is a sight or a smell test
588 
589 	// Now returns not a boolean but the adjusted (by cover) distance to the target, or 0 for unseen
590 
591 	FIXEDPT qCurrX;
592 	FIXEDPT qCurrY;
593 	FIXEDPT qCurrZ;
594 
595 	INT32 iGridNo;
596 	INT32 iCurrTileX;
597 	INT32 iCurrTileY;
598 
599 	INT8  bLOSIndexX;
600 	INT8  bLOSIndexY;
601 	INT8  bOldLOSIndexX;
602 	INT8  bOldLOSIndexY;
603 	INT32 iOldCubesZ;
604 
605 	INT32 iCurrCubesZ;
606 
607 	FIXEDPT qLandHeight;
608 	INT32 iCurrAboveLevelZ;
609 	INT32 iCurrCubesAboveLevelZ;
610 	INT32 iStartCubesAboveLevelZ;
611 	INT32 iEndCubesAboveLevelZ;
612 	INT32 iStartCubesZ;
613 	INT32 iEndCubesZ;
614 
615 	INT16 sDesiredLevel;
616 
617 
618 	INT32 iOldTileX;
619 	INT32 iOldTileY;
620 
621 	FLOAT dDeltaX;
622 	FLOAT dDeltaY;
623 	FLOAT dDeltaZ;
624 
625 	FIXEDPT qIncrX;
626 	FIXEDPT qIncrY;
627 	FIXEDPT qIncrZ;
628 
629 	FLOAT dDistance;
630 
631 	INT32 iDistance;
632 	INT32 iSightLimit = ubTileSightLimit * CELL_X_SIZE;
633 	INT32 iAdjSightLimit = iSightLimit;
634 
635 	INT32 iLoop;
636 
637 	MAP_ELEMENT *pMapElement;
638 	STRUCTURE *pStructure;
639 	STRUCTURE *pRoofStructure = NULL;
640 
641 	BOOLEAN fCheckForRoof;
642 	FIXEDPT qLastZ;
643 
644 	FIXEDPT qDistToTravelX;
645 	FIXEDPT qDistToTravelY;
646 	INT32 iStepsToTravelX;
647 	INT32 iStepsToTravelY;
648 	INT32 iStepsToTravel;
649 	BOOLEAN fResolveHit;
650 	DOUBLE ddHorizAngle;
651 	INT32 iStructureHeight;
652 
653 	FIXEDPT qWallHeight;
654 	BOOLEAN fOpaque;
655 	INT8 bSmoke = 0;
656 
657 	if ( gTacticalStatus.uiFlags & DISALLOW_SIGHT )
658 	{
659 		return( 0 );
660 	}
661 
662 	if (iSightLimit == 0)
663 	{
664 		// blind!
665 		return( 0 );
666 	}
667 
668 	if (!bAware && !fSmell)
669 	{
670 		// trees are x3 as good at reducing sight if looker is unaware
671 		// and increase that up to double for camouflage!
672 		ubTreeSightReduction = (ubTreeSightReduction * 3) * (100 + bCamouflage) / 100;
673 	}
674 	// verify start and end to make sure we'll always be inside the map
675 
676 	// hack end location to the centre of the tile, because there was a problem
677 	// seeing a presumably off-centre merc...
678 
679 	INT16 start_cell_x;
680 	INT16 start_cell_y;
681 	ConvertGridNoToCenterCellXY(start_pos, &start_cell_x, &start_cell_y);
682 	const float dStartX = start_cell_x;
683 	const float dStartY = start_cell_y;
684 
685 	INT16 end_cell_x;
686 	INT16 end_cell_y;
687 	ConvertGridNoToCenterCellXY(end_pos, &end_cell_x, &end_cell_y);
688 	const float dEndX = end_cell_x;
689 	const float dEndY = end_cell_y;
690 
691 	dDeltaX = dEndX - dStartX;
692 	dDeltaY = dEndY - dStartY;
693 	dDeltaZ = dEndZ - dStartZ;
694 
695 	dDistance = Distance3D( dDeltaX, dDeltaY, CONVERT_HEIGHTUNITS_TO_DISTANCE( dDeltaZ ));
696 	iDistance = (INT32) dDistance;
697 
698 	if ( iDistance == 0 )
699 	{
700 		return( FALSE );
701 	}
702 
703 	if ( dDistance != (FLOAT) iDistance )
704 	{
705 		// add 1 step to account for fraction
706 		iDistance += 1;
707 	}
708 
709 	if ( iDistance > iSightLimit)
710 	{
711 		// out of visual range
712 		return( 0 );
713 	}
714 
715 	ddHorizAngle = atan2( dDeltaY, dDeltaX );
716 
717 	#ifdef LOS_DEBUG
718 	gLOSTestResults = LOSResults{};
719 	gLOSTestResults.fLOSTestPerformed = TRUE;
720 	gLOSTestResults.iStartX = (INT32) dStartX;
721 	gLOSTestResults.iStartY = (INT32) dStartY;
722 	gLOSTestResults.iStartZ = (INT32) dStartZ;
723 	gLOSTestResults.iEndX = (INT32) dEndX;
724 	gLOSTestResults.iEndY = (INT32) dEndY;
725 	gLOSTestResults.iEndZ = (INT32) dEndZ;
726 	gLOSTestResults.iMaxDistance = (INT32) iSightLimit;
727 	gLOSTestResults.iDistance = (INT32) dDistance;
728 	#endif
729 
730 	qIncrX = FloatToFixed( dDeltaX / (FLOAT)iDistance );
731 	qIncrY = FloatToFixed( dDeltaY / (FLOAT)iDistance );
732 	qIncrZ = FloatToFixed( dDeltaZ / (FLOAT)iDistance );
733 
734 	fCheckForRoof = FALSE;
735 
736 	// figure out starting and ending cubes
737 	iGridNo = GETWORLDINDEXFROMWORLDCOORDS( (INT32)dStartX, (INT32)dStartY );
738 	qCurrZ = FloatToFixed( dStartZ );
739 	qLandHeight = INT32_TO_FIXEDPT( CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ iGridNo ].sHeight ) );
740 	iCurrAboveLevelZ = FIXEDPT_TO_INT32( qCurrZ - qLandHeight );
741 
742 	iStartCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( iCurrAboveLevelZ );
743 	iStartCubesAboveLevelZ = iStartCubesZ;
744 	if (iStartCubesAboveLevelZ >= STRUCTURE_ON_GROUND_MAX)
745 	{
746 		iStartCubesAboveLevelZ -= STRUCTURE_ON_ROOF;
747 	}
748 
749 	// check to see if we need to check for roofs based on the starting gridno
750 	qWallHeight = gqStandardWallHeight + qLandHeight;
751 	if ( qCurrZ < qWallHeight )
752 	{
753 		// possibly going up through a roof on this level
754 		qCurrZ = FloatToFixed( dEndZ );
755 
756 		if ( qCurrZ > qWallHeight )
757 		{
758 			fCheckForRoof = TRUE;
759 		}
760 
761 	}
762 	else // >
763 	{
764 		// possibly going down through a roof on this level
765 		qCurrZ = FloatToFixed( dEndZ );
766 
767 		if (qCurrZ < qWallHeight)
768 		{
769 			fCheckForRoof = TRUE;
770 		}
771 	}
772 
773 	iGridNo = GETWORLDINDEXFROMWORLDCOORDS( (INT32)dEndX, (INT32)dEndY );
774 	qCurrZ = FloatToFixed( dEndZ );
775 	qLandHeight = INT32_TO_FIXEDPT( CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ iGridNo ].sHeight ) );
776 	iCurrAboveLevelZ = FIXEDPT_TO_INT32( qCurrZ - qLandHeight );
777 	iEndCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( iCurrAboveLevelZ );
778 	iEndCubesAboveLevelZ = iEndCubesZ;
779 	if (iEndCubesAboveLevelZ >= STRUCTURE_ON_GROUND_MAX)
780 	{
781 		iEndCubesAboveLevelZ -= STRUCTURE_ON_ROOF;
782 	}
783 
784 	// check to see if we need to check for roofs based on the starting gridno
785 	qWallHeight = gqStandardWallHeight + qLandHeight;
786 
787 	if ( qCurrZ < qWallHeight )
788 	{
789 		// possibly going down through a roof on this level
790 		qCurrZ = FloatToFixed( dStartZ );
791 
792 		if ( qCurrZ > qWallHeight )
793 		{
794 			fCheckForRoof = TRUE;
795 		}
796 
797 	}
798 	else // >
799 	{
800 		// possibly going up through a roof on this level
801 		qCurrZ = FloatToFixed( dStartZ );
802 
803 		if (qCurrZ < qWallHeight)
804 		{
805 			fCheckForRoof = TRUE;
806 		}
807 	}
808 
809 	// apply increments for first move
810 
811 	// first move will be 1 step
812 	// plus a fractional part equal to half of the difference between the delta and
813 	// the increment times the distance
814 
815 	qCurrX = FloatToFixed( dStartX ) + qIncrX + ( FloatToFixed( dDeltaX ) - qIncrX * iDistance ) / 2;
816 	qCurrY = FloatToFixed( dStartY ) + qIncrY + ( FloatToFixed( dDeltaY ) - qIncrY * iDistance ) / 2;
817 	qCurrZ = FloatToFixed( dStartZ ) + qIncrZ + ( FloatToFixed( dDeltaZ ) - qIncrZ * iDistance ) / 2;
818 
819 	iCurrTileX = FIXEDPT_TO_TILE_NUM( qCurrX );
820 	iCurrTileY = FIXEDPT_TO_TILE_NUM( qCurrY );
821 	bLOSIndexX = FIXEDPT_TO_LOS_INDEX( qCurrX );
822 	bLOSIndexY = FIXEDPT_TO_LOS_INDEX( qCurrY );
823 	iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( qCurrZ ) );
824 
825 	iLoop = 1;
826 
827 	do
828 	{
829 		// check a particular tile
830 
831 		// retrieve values from world for this particular tile
832 		iGridNo = iCurrTileX + iCurrTileY * WORLD_COLS;
833 		pMapElement = &(gpWorldLevelData[ iGridNo ]);
834 		qLandHeight = INT32_TO_FIXEDPT( CONVERT_PIXELS_TO_HEIGHTUNITS( pMapElement->sHeight ) );
835 		qWallHeight = gqStandardWallHeight + qLandHeight;
836 
837 		if (fCheckForRoof)
838 		{
839 			pRoofStructure = FindStructure( (INT16) iGridNo, STRUCTURE_ROOF );
840 
841 			if ( pRoofStructure )
842 			{
843 
844 				qLastZ = qCurrZ - qIncrZ;
845 
846 				// if just on going to next tile we cross boundary, then roof stops sight here!
847 				if ( (qLastZ > qWallHeight && qCurrZ <= qWallHeight) || (qLastZ < qWallHeight && qCurrZ >= qWallHeight))
848 				{
849 					// hit a roof
850 					return( 0 );
851 				}
852 
853 			}
854 
855 		}
856 
857 		// record old tile location for loop purposes
858 		iOldTileX = iCurrTileX;
859 		iOldTileY = iCurrTileY;
860 		do
861 		{
862 			// check a particular location within the tile
863 
864 			// check for collision with the ground
865 			iCurrAboveLevelZ = FIXEDPT_TO_INT32( qCurrZ - qLandHeight );
866 			if (iCurrAboveLevelZ < 0)
867 			{
868 				// ground is in the way!
869 				#ifdef LOS_DEBUG
870 					gLOSTestResults.iStoppedX = FIXEDPT_TO_INT32( qCurrX );
871 					gLOSTestResults.iStoppedY = FIXEDPT_TO_INT32( qCurrY );
872 					gLOSTestResults.iStoppedZ = FIXEDPT_TO_INT32( qCurrZ );
873 					// subtract one to compensate for rounding up when negative
874 					gLOSTestResults.iCurrCubesZ = 0;
875 				#endif
876 				return( 0 );
877 			}
878 			// check for the existence of structures
879 			pStructure = pMapElement->pStructureHead;
880 			if (pStructure == NULL)
881 			{	// no structures in this tile, AND THAT INCLUDES ROOFS! :-)
882 
883 				// new system; figure out how many steps until we cross the next edge
884 				// and then fast forward that many steps.
885 
886 				iOldTileX = iCurrTileX;
887 				iOldTileY = iCurrTileY;
888 				iOldCubesZ = iCurrCubesZ;
889 
890 				if (qIncrX > 0)
891 				{
892 					qDistToTravelX = INT32_TO_FIXEDPT( CELL_X_SIZE ) - (qCurrX % INT32_TO_FIXEDPT( CELL_X_SIZE ));
893 					iStepsToTravelX = qDistToTravelX / qIncrX;
894 				}
895 				else if (qIncrX < 0)
896 				{
897 					qDistToTravelX = qCurrX % INT32_TO_FIXEDPT( CELL_X_SIZE );
898 					iStepsToTravelX = qDistToTravelX / -qIncrX;
899 				}
900 				else
901 				{
902 					// make sure we don't consider X a limit :-)
903 					iStepsToTravelX = 1000000;
904 				}
905 
906 				if (qIncrY > 0)
907 				{
908 					qDistToTravelY = INT32_TO_FIXEDPT( CELL_Y_SIZE ) - (qCurrY % INT32_TO_FIXEDPT( CELL_Y_SIZE ));
909 					iStepsToTravelY = qDistToTravelY / qIncrY;
910 				}
911 				else if (qIncrY < 0)
912 				{
913 					qDistToTravelY = qCurrY % INT32_TO_FIXEDPT( CELL_Y_SIZE );
914 					iStepsToTravelY = qDistToTravelY / -qIncrY;
915 				}
916 				else
917 				{
918 					// make sure we don't consider Y a limit :-)
919 					iStepsToTravelY = 1000000;
920 				}
921 
922 				iStepsToTravel = __min( iStepsToTravelX, iStepsToTravelY ) + 1;
923 
924 				/*
925 				if (qIncrX > 0)
926 				{
927 					qDistToTravelX = INT32_TO_FIXEDPT( CELL_X_SIZE ) - (qCurrX % INT32_TO_FIXEDPT( CELL_X_SIZE ));
928 					iStepsToTravelX = qDistToTravelX / qIncrX;
929 				}
930 				else if (qIncrX < 0)
931 				{
932 					qDistToTravelX = qCurrX % INT32_TO_FIXEDPT( CELL_X_SIZE );
933 					iStepsToTravelX = qDistToTravelX / (-qIncrX);
934 				}
935 				else
936 				{
937 					// make sure we don't consider X a limit :-)
938 					iStepsToTravelX = 1000000;
939 				}
940 
941 				if (qIncrY > 0)
942 				{
943 					qDistToTravelY = INT32_TO_FIXEDPT( CELL_Y_SIZE ) - (qCurrY % INT32_TO_FIXEDPT( CELL_Y_SIZE ));
944 					iStepsToTravelY = qDistToTravelY / qIncrY;
945 				}
946 				else if (qIncrY < 0)
947 				{
948 					qDistToTravelY = qCurrY % INT32_TO_FIXEDPT( CELL_Y_SIZE );
949 					iStepsToTravelY = qDistToTravelY / (-qIncrY);
950 				}
951 				else
952 				{
953 					// make sure we don't consider Y a limit :-)
954 					iStepsToTravelY = 1000000;
955 				}
956 
957 				// add 1 to the # of steps to travel to go INTO the next tile
958 				iStepsToTravel = __min( iStepsToTravelX, iStepsToTravelY ) + 1;
959 				//iStepsToTravel = 1;*/
960 
961 				qCurrX += qIncrX * iStepsToTravel;
962 				qCurrY += qIncrY * iStepsToTravel;
963 				qCurrZ += qIncrZ * iStepsToTravel;
964 				iLoop += iStepsToTravel;
965 
966 				// check for ground collision
967 				if (qCurrZ < qLandHeight && iLoop < iDistance)
968 				{
969 					// ground is in the way!
970 					#ifdef LOS_DEBUG
971 						gLOSTestResults.iStoppedX = FIXEDPT_TO_INT32( qCurrX );
972 						gLOSTestResults.iStoppedY = FIXEDPT_TO_INT32( qCurrY );
973 						gLOSTestResults.iStoppedZ = FIXEDPT_TO_INT32( qCurrZ );
974 						// subtract one to compensate for rounding up when negative
975 						gLOSTestResults.iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( iCurrCubesAboveLevelZ ) - 1;
976 					#endif
977 					return( 0 );
978 				}
979 
980 				// figure out the new tile location
981 				iCurrTileX = FIXEDPT_TO_TILE_NUM( qCurrX );
982 				iCurrTileY = FIXEDPT_TO_TILE_NUM( qCurrY );
983 				iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( qCurrZ ) );
984 				bLOSIndexX = FIXEDPT_TO_LOS_INDEX( qCurrX );
985 				bLOSIndexY = FIXEDPT_TO_LOS_INDEX( qCurrY );
986 			}
987 			else
988 			{	// there are structures in this tile
989 
990 				iCurrCubesAboveLevelZ = CONVERT_HEIGHTUNITS_TO_INDEX( iCurrAboveLevelZ );
991 				// figure out the LOS cube level of the current point
992 
993 				if (iCurrCubesAboveLevelZ < STRUCTURE_ON_ROOF_MAX)
994 				{
995 					if (iCurrCubesAboveLevelZ < STRUCTURE_ON_GROUND_MAX)
996 					{
997 						// check objects on the ground
998 						sDesiredLevel = STRUCTURE_ON_GROUND;
999 					}
1000 					else
1001 					{
1002 						// check objects on roofs
1003 						sDesiredLevel = STRUCTURE_ON_ROOF;
1004 						iCurrCubesAboveLevelZ -= STRUCTURE_ON_ROOF;
1005 					}
1006 					// check structures for collision
1007 					while (pStructure != NULL)
1008 					{
1009 						// transparent structures should be skipped
1010 						// normal roof structures should be skipped
1011 						// here because their only bits are roof lips
1012 						// and those should act as transparent
1013 						fOpaque = (pStructure->fFlags & STRUCTURE_TRANSPARENT) == 0;
1014 						if ( pStructure->fFlags & STRUCTURE_ROOF )
1015 						{
1016 							// roof lip; allow sighting if person on roof is near
1017 							if ( (iLoop < 2 * CELL_X_SIZE || (iDistance - iLoop) < 2 * CELL_X_SIZE ) )
1018 							{
1019 								if ( iLoop <= CELL_X_SIZE + 1 || (iDistance - iLoop ) <= CELL_X_SIZE + 1 )
1020 								{
1021 									// right near edge, allow sighting at 3 tiles from roof edge if prone
1022 									// less if standing, and we can tell that with iStartCubesZ and iEndCubesZ
1023 									if ( iStartCubesZ < iEndCubesZ )
1024 									{
1025 										// looking up, so reduce for the target stance-height according to iEndCubesZ
1026 										if ( iDistance >= (3 - iEndCubesAboveLevelZ) * CELL_X_SIZE )
1027 										{
1028 											fOpaque = FALSE;
1029 										}
1030 									}
1031 									else
1032 									{
1033 										if ( iDistance >= (3 - iStartCubesAboveLevelZ) * CELL_X_SIZE )
1034 										{
1035 											fOpaque = FALSE;
1036 										}
1037 									}
1038 
1039 								}
1040 								else
1041 								{
1042 									if ( iDistance >= 12 * CELL_X_SIZE )
1043 									{
1044 										fOpaque = FALSE;
1045 									}
1046 								}
1047 							}
1048 						}
1049 
1050 						if ( fOpaque )
1051 						{
1052 							if (pStructure->sCubeOffset == sDesiredLevel)
1053 							{
1054 								if (((*(pStructure->pShape))[bLOSIndexX][bLOSIndexY] & AtHeight[iCurrCubesAboveLevelZ]) > 0)
1055 								{
1056 									if (fSmell)
1057 									{
1058 										if (pStructure->fFlags & STRUCTURE_TREE)
1059 										{
1060 											// smell not stopped by vegetation
1061 										}
1062 										else if ((pStructure->fFlags & STRUCTURE_WALLNWINDOW) && (pStructure->fFlags & STRUCTURE_OPEN))
1063 										{
1064 											// open window, smell not stopped
1065 										}
1066 										else
1067 										{
1068 											if (pStructure->fFlags & STRUCTURE_WALLSTUFF)
1069 											{
1070 												// possibly at corner in which case we should let it pass
1071 												fResolveHit = ResolveHitOnWall( pStructure, iGridNo, bLOSIndexX, bLOSIndexY, ddHorizAngle );
1072 											}
1073 											else
1074 											{
1075 												fResolveHit = TRUE;
1076 											}
1077 											if (fResolveHit)
1078 											{
1079 												// CJC, May 30:  smell reduced by obstacles but not
1080 												// stopped if obstacle within 10 tiles
1081 												iAdjSightLimit -= SMELL_REDUCTION_FOR_NEARBY_OBSTACLE;
1082 												if (iLoop > 100 || iDistance > iAdjSightLimit)
1083 												{
1084 													// out of visual range
1085 													#ifdef LOS_DEBUG
1086 													gLOSTestResults.fOutOfRange = TRUE;
1087 													gLOSTestResults.iCurrCubesZ = iCurrCubesZ;
1088 													#endif
1089 													return( 0 );
1090 												}
1091 
1092 												/*
1093 												// smell-line stopped by obstacle!
1094 												#ifdef LOS_DEBUG
1095 													gLOSTestResults.iStoppedX = FIXEDPT_TO_INT32( qCurrX );
1096 													gLOSTestResults.iStoppedY = FIXEDPT_TO_INT32( qCurrY );
1097 													gLOSTestResults.iStoppedZ = FIXEDPT_TO_INT32( qCurrZ );
1098 													gLOSTestResults.iCurrCubesZ = iCurrCubesAboveLevelZ;
1099 												#endif
1100 												return( 0 );*/
1101 											}
1102 										}
1103 									}
1104 									else
1105 									{
1106 										if (pStructure->fFlags & STRUCTURE_TREE)
1107 										{
1108 											// don't count trees close to the person
1109 											if (iLoop > CLOSE_TO_FIRER)
1110 											{
1111 												if (iLoop > 100)
1112 												{
1113 													// at longer range increase the value of tree cover
1114 													iAdjSightLimit -= (ubTreeSightReduction * iLoop) / 100;
1115 												}
1116 												else
1117 												{
1118 													// use standard value
1119 													iAdjSightLimit -= ubTreeSightReduction;
1120 												}
1121 												#ifdef LOS_DEBUG
1122 												gLOSTestResults.ubTreeSpotsHit++;
1123 												gLOSTestResults.iMaxDistance = iSightLimit;
1124 												#endif
1125 												if (iDistance > iAdjSightLimit)
1126 												{
1127 													// out of visual range
1128 													#ifdef LOS_DEBUG
1129 													gLOSTestResults.fOutOfRange = TRUE;
1130 													gLOSTestResults.iCurrCubesZ = iCurrCubesZ;
1131 													#endif
1132 													return( 0 );
1133 												}
1134 											}
1135 										}
1136 										else if ( (pStructure->fFlags & STRUCTURE_WALLNWINDOW) && !(pStructure->fFlags & STRUCTURE_SPECIAL) && qCurrZ >= (gqStandardWindowBottomHeight + qLandHeight) && qCurrZ <= (gqStandardWindowTopHeight + qLandHeight))
1137 										{
1138 											// do nothing; windows are transparent (except ones marked as special)
1139 											if (psWindowGridNo != NULL)
1140 											{
1141 												// we're supposed to note the location of this window!
1142 												// but if a location has already been set then there are two windows, in which case
1143 												// we abort
1144 												if (*psWindowGridNo == NOWHERE)
1145 												{
1146 													*psWindowGridNo = (INT16) iGridNo;
1147 													return( iLoop );
1148 												}
1149 												else
1150 												{
1151 													//*psWindowGridNo = NOWHERE;
1152 													//return( iLoop );
1153 												}
1154 											}
1155 										}
1156 										else
1157 										{
1158 											if (pStructure->fFlags & STRUCTURE_WALLSTUFF)
1159 											{
1160 												// possibly shooting at corner in which case we should let it pass
1161 												fResolveHit = ResolveHitOnWall( pStructure, iGridNo, bLOSIndexX, bLOSIndexY, ddHorizAngle );
1162 											}
1163 											else
1164 											{
1165 												if (iCurrCubesAboveLevelZ < (STANDING_CUBES - 1) )
1166 												{
1167 													if ( (iLoop <= CLOSE_TO_FIRER) && (iCurrCubesAboveLevelZ <= iStartCubesAboveLevelZ) )
1168 													{
1169 														// if we are in the same vertical cube as the start,
1170 														// and this is the height of the structure, then allow sight to go through
1171 														// NB cubes are 0 based, heights 1 based
1172 														iStructureHeight = StructureHeight( pStructure );
1173 														fResolveHit = ( iCurrCubesAboveLevelZ != (iStructureHeight - 1) );
1174 													}
1175 													else if ( (iLoop >= (iDistance - CLOSE_TO_FIRER) ) && (iCurrCubesAboveLevelZ <= iEndCubesZ) && bAware )
1176 													{
1177 														// if we are in the same vertical cube as our destination,
1178 														// and this is the height of the structure, and we are aware
1179 														// then allow sight to go through
1180 														// NB cubes are 0 based, heights 1 based
1181 														iStructureHeight = StructureHeight( pStructure );
1182 														fResolveHit = ( iCurrCubesAboveLevelZ != (iStructureHeight - 1) );
1183 													}
1184 													else
1185 													{
1186 														fResolveHit = TRUE;
1187 													}
1188 												}
1189 												else
1190 												{
1191 													fResolveHit = TRUE;
1192 												}
1193 											}
1194 											if (fResolveHit)
1195 											{
1196 												// hit the obstacle!
1197 												#ifdef LOS_DEBUG
1198 													gLOSTestResults.iStoppedX = FIXEDPT_TO_INT32( qCurrX );
1199 													gLOSTestResults.iStoppedY = FIXEDPT_TO_INT32( qCurrY );
1200 													gLOSTestResults.iStoppedZ = FIXEDPT_TO_INT32( qCurrZ );
1201 													gLOSTestResults.iCurrCubesZ = iCurrCubesAboveLevelZ;
1202 												#endif
1203 												return( 0 );
1204 											}
1205 										}
1206 									}
1207 								}
1208 							}
1209 						}
1210 						pStructure = pStructure->pNext;
1211 					}
1212 				}
1213 				// got past all structures; go to next location within
1214 				// tile, horizontally or vertically
1215 				bOldLOSIndexX = bLOSIndexX;
1216 				bOldLOSIndexY = bLOSIndexY;
1217 				iOldCubesZ = iCurrCubesZ;
1218 				do
1219 				{
1220 					qCurrX += qIncrX;
1221 					qCurrY += qIncrY;
1222 					if (pRoofStructure)
1223 					{
1224 						qLastZ = qCurrZ;
1225 						qCurrZ += qIncrZ;
1226 						if ( (qLastZ > qWallHeight && qCurrZ <= qWallHeight) || (qLastZ < qWallHeight && qCurrZ >= qWallHeight))
1227 						{
1228 							// hit a roof
1229 							return( 0 );
1230 						}
1231 					}
1232 					else
1233 					{
1234 						qCurrZ += qIncrZ;
1235 					}
1236 
1237 					iLoop++;
1238 					bLOSIndexX = FIXEDPT_TO_LOS_INDEX( qCurrX );
1239 					bLOSIndexY = FIXEDPT_TO_LOS_INDEX( qCurrY );
1240 					iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( qCurrZ ) );
1241 					// shouldn't need to check whether we are not at maximum range because
1242 					// that will be caught below and this loop shouldn't go for more than a
1243 					// couple of iterations.
1244 				}
1245 				while( (bLOSIndexX == bOldLOSIndexX) && (bLOSIndexY == bOldLOSIndexY) && (iCurrCubesZ == iOldCubesZ));
1246 				iCurrTileX = FIXEDPT_TO_TILE_NUM( qCurrX );
1247 				iCurrTileY = FIXEDPT_TO_TILE_NUM( qCurrY );
1248 			}
1249 		} while( (iCurrTileX == iOldTileX) && (iCurrTileY == iOldTileY) && (iLoop < iDistance));
1250 
1251 		// leaving a tile, check to see if it had gas in it
1252 		if ( pMapElement->ubExtFlags[0] & (MAPELEMENT_EXT_SMOKE | MAPELEMENT_EXT_TEARGAS | MAPELEMENT_EXT_MUSTARDGAS) )
1253 		{
1254 			if ( (pMapElement->ubExtFlags[0] & MAPELEMENT_EXT_SMOKE) && !fSmell )
1255 			{
1256 				bSmoke++;
1257 
1258 				// we can only see 3 tiles in smoke
1259 				// (2 if we're IN smoke)
1260 
1261 				if ( bSmoke >= 3 )
1262 				{
1263 					iAdjSightLimit = 0;
1264 				}
1265 				// unpopular
1266 				/*
1267 				else
1268 				{
1269 					// losing 1/3rd results in chances to hit which are WAY too low when firing from out of
1270 					// two tiles of smoke... changing this to a 1/6 penalty
1271 
1272 					iAdjSightLimit -= iSightLimit / 6;
1273 				}*/
1274 			}
1275 			else
1276 			{
1277 				// reduce by 2 tiles per tile of tear gas or mustard gas
1278 				iAdjSightLimit -= 2 * CELL_X_SIZE;
1279 			}
1280 
1281 			if ( iAdjSightLimit <= 0 )
1282 			{
1283 				// can't see, period!
1284 				return( 0 );
1285 			}
1286 		}
1287 
1288 	} while( iLoop < iDistance );
1289 	// unless the distance is integral, after the loop there will be a
1290 	// fractional amount of distance remaining which is unchecked
1291 	// but we shouldn't(?) need to check it because the target is there!
1292 	#ifdef LOS_DEBUG
1293 	gLOSTestResults.fLOSClear = TRUE;
1294 	#endif
1295 	// this somewhat complicated formula does the following:
1296 	// it starts with the distance to the target
1297 	// it adds the difference between the original and adjusted sight limit, = the amount of cover
1298 	// it then scales the value based on the difference between the original sight limit and the
1299 	//   very maximum possible in best lighting conditions
1300 	return( (iDistance + (iSightLimit - iAdjSightLimit)) * (MaxDistanceVisible() * CELL_X_SIZE) / iSightLimit );
1301 }
1302 
1303 
CalculateSoldierZPos(const SOLDIERTYPE * pSoldier,UINT8 ubPosType,FLOAT * pdZPos)1304 BOOLEAN CalculateSoldierZPos(const SOLDIERTYPE* pSoldier, UINT8 ubPosType, FLOAT* pdZPos)
1305 {
1306 	UINT8 ubHeight;
1307 
1308 	if ( pSoldier->ubBodyType == CROW )
1309 	{
1310 		// Crow always as prone...
1311 		ubHeight = ANIM_PRONE;
1312 	}
1313 	else if (pSoldier->bOverTerrainType == DEEP_WATER)
1314 	{
1315 		// treat as prone
1316 		ubHeight = ANIM_PRONE;
1317 	}
1318 	else if ( pSoldier->bOverTerrainType == LOW_WATER || pSoldier->bOverTerrainType == MED_WATER )
1319 	{
1320 		// treat as crouched
1321 		ubHeight = ANIM_CROUCH;
1322 	}
1323 	else
1324 	{
1325 		if ( CREATURE_OR_BLOODCAT( pSoldier ) || pSoldier->ubBodyType == COW )
1326 		{
1327 			// this if statement is to avoid the 'creature weak spot' target
1328 			// spot for creatures
1329 			if ( ubPosType == HEAD_TARGET_POS || ubPosType == LEGS_TARGET_POS )
1330 			{
1331 				// override!
1332 				ubPosType = TORSO_TARGET_POS;
1333 			}
1334 		}
1335 		else if ( TANK( pSoldier ) )
1336 		{
1337 			// high up!
1338 			ubPosType = HEAD_TARGET_POS;
1339 		}
1340 
1341 		ubHeight = gAnimControl[ pSoldier->usAnimState ].ubEndHeight;
1342 	}
1343 
1344 	switch( ubPosType )
1345 	{
1346 		case LOS_POS:
1347 			switch ( ubHeight )
1348 			{
1349 				case ANIM_STAND:
1350 					*pdZPos = STANDING_LOS_POS;
1351 					break;
1352 				case ANIM_CROUCH:
1353 					*pdZPos = CROUCHED_LOS_POS;
1354 					break;
1355 				case ANIM_PRONE:
1356 					*pdZPos = PRONE_LOS_POS;
1357 					break;
1358 				default:
1359 					return( FALSE );
1360 			}
1361 			break;
1362 		case FIRING_POS:
1363 			switch ( ubHeight )
1364 			{
1365 				case ANIM_STAND:
1366 					*pdZPos = STANDING_FIRING_POS;
1367 					break;
1368 				case ANIM_CROUCH:
1369 					*pdZPos = CROUCHED_FIRING_POS;
1370 					break;
1371 				case ANIM_PRONE:
1372 					*pdZPos = PRONE_FIRING_POS;
1373 					break;
1374 				default:
1375 					return( FALSE );
1376 			}
1377 			break;
1378 		case TARGET_POS:
1379 			switch ( ubHeight )
1380 			{
1381 				case ANIM_STAND:
1382 					*pdZPos = STANDING_TARGET_POS;
1383 					break;
1384 				case ANIM_CROUCH:
1385 					*pdZPos = CROUCHED_TARGET_POS;
1386 					break;
1387 				case ANIM_PRONE:
1388 					*pdZPos = PRONE_TARGET_POS;
1389 					break;
1390 				default:
1391 					return( FALSE );
1392 			}
1393 			break;
1394 		case HEAD_TARGET_POS:
1395 			switch ( ubHeight )
1396 			{
1397 				case ANIM_STAND:
1398 					*pdZPos = STANDING_HEAD_TARGET_POS;
1399 					break;
1400 				case ANIM_CROUCH:
1401 					*pdZPos = CROUCHED_HEAD_TARGET_POS;
1402 					break;
1403 				case ANIM_PRONE:
1404 					*pdZPos = PRONE_HEAD_TARGET_POS;
1405 					break;
1406 				default:
1407 					return( FALSE );
1408 			}
1409 			break;
1410 		case TORSO_TARGET_POS:
1411 			switch ( ubHeight )
1412 			{
1413 				case ANIM_STAND:
1414 					*pdZPos = STANDING_TORSO_TARGET_POS;
1415 					break;
1416 				case ANIM_CROUCH:
1417 					*pdZPos = CROUCHED_TORSO_TARGET_POS;
1418 					break;
1419 				case ANIM_PRONE:
1420 					*pdZPos = PRONE_TORSO_TARGET_POS;
1421 					break;
1422 				default:
1423 					return( FALSE );
1424 			}
1425 			break;
1426 		case LEGS_TARGET_POS:
1427 			switch ( ubHeight )
1428 			{
1429 				case ANIM_STAND:
1430 					*pdZPos = STANDING_LEGS_TARGET_POS;
1431 					break;
1432 				case ANIM_CROUCH:
1433 					*pdZPos = CROUCHED_LEGS_TARGET_POS;
1434 					break;
1435 				case ANIM_PRONE:
1436 					*pdZPos = PRONE_LEGS_TARGET_POS;
1437 					break;
1438 				default:
1439 					return( FALSE );
1440 			}
1441 			break;
1442 		case HEIGHT:
1443 			switch ( ubHeight )
1444 			{
1445 				case ANIM_STAND:
1446 					*pdZPos = STANDING_HEIGHT;
1447 					break;
1448 				case ANIM_CROUCH:
1449 					*pdZPos = CROUCHED_HEIGHT;
1450 					break;
1451 				case ANIM_PRONE:
1452 					*pdZPos = PRONE_HEIGHT;
1453 					break;
1454 				default:
1455 					return( FALSE );
1456 			}
1457 			break;
1458 	}
1459 	if ( pSoldier->ubBodyType == HATKIDCIV || pSoldier->ubBodyType == KIDCIV )
1460 	{
1461 		// reduce value for kids who are 2/3 the height of regular people
1462 		*pdZPos = (*pdZPos * 2) / 3;
1463 	}
1464 	else if ( pSoldier->ubBodyType == ROBOTNOWEAPON || pSoldier->ubBodyType == LARVAE_MONSTER || pSoldier->ubBodyType == INFANT_MONSTER || pSoldier->ubBodyType == BLOODCAT )
1465 	{
1466 		// robot is 1/3 the height of regular people
1467 		*pdZPos = *pdZPos / 3;
1468 	}
1469 	else if ( TANK ( pSoldier ) )
1470 	{
1471 		*pdZPos = (*pdZPos * 4) / 3;
1472 	}
1473 
1474 	if (pSoldier->bLevel > 0)
1475 	{
1476 		// on a roof
1477 		*pdZPos += WALL_HEIGHT_UNITS;
1478 	}
1479 
1480 	// IF this is a plane, strafe!
1481 	// ATE: Don;t panic - this is temp - to be changed to a status flag....
1482 	if ( pSoldier->ubID == MAX_NUM_SOLDIERS )
1483 	{
1484 		*pdZPos = ( WALL_HEIGHT_UNITS * 2 ) - 1;
1485 	}
1486 
1487 	*pdZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[pSoldier->sGridNo].sHeight );
1488 	return( TRUE );
1489 }
1490 
1491 
SoldierToSoldierLineOfSightTest(const SOLDIERTYPE * const pStartSoldier,const SOLDIERTYPE * const pEndSoldier,UINT8 ubTileSightLimit,const INT8 bAware)1492 INT32 SoldierToSoldierLineOfSightTest(const SOLDIERTYPE* const pStartSoldier, const SOLDIERTYPE* const pEndSoldier, UINT8 ubTileSightLimit, const INT8 bAware)
1493 {
1494 	FLOAT dStartZPos, dEndZPos;
1495 	BOOLEAN fOk;
1496 	BOOLEAN fSmell;
1497 	INT8 bEffectiveCamo;
1498 	UINT8 ubTreeReduction;
1499 
1500 	// TO ADD: if target is camouflaged and in cover, reduce sight distance by 30%
1501 	// TO ADD: if in tear gas, reduce sight limit to 2 tiles
1502 	CHECKF( pStartSoldier );
1503 	CHECKF( pEndSoldier );
1504 	fOk = CalculateSoldierZPos( pStartSoldier, LOS_POS, &dStartZPos );
1505 	CHECKF( fOk );
1506 
1507 	if ( gWorldSectorX == 5 && gWorldSectorY == MAP_ROW_N )
1508 	{
1509 		// in the bloodcat arena sector, skip sight between army & bloodcats
1510 		if ( pStartSoldier->bTeam == ENEMY_TEAM && pEndSoldier->bTeam == CREATURE_TEAM )
1511 		{
1512 			return( 0 );
1513 		}
1514 		if ( pStartSoldier->bTeam == CREATURE_TEAM && pEndSoldier->bTeam == ENEMY_TEAM )
1515 		{
1516 			return( 0 );
1517 		}
1518 	}
1519 
1520 
1521 	if (pStartSoldier->uiStatusFlags & SOLDIER_MONSTER)
1522 	{
1523 		// monsters use smell instead of sight!
1524 		dEndZPos = STANDING_LOS_POS; // should avoid low rocks etc
1525 		if (pEndSoldier->bLevel > 0)
1526 		{
1527 			// on a roof
1528 			dEndZPos += WALL_HEIGHT_UNITS;
1529 		}
1530 		dEndZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[pEndSoldier->sGridNo].sHeight );
1531 		fSmell = TRUE;
1532 	}
1533 	else
1534 	{
1535 		fOk = CalculateSoldierZPos( pEndSoldier, LOS_POS, &dEndZPos );
1536 		CHECKF( fOk );
1537 		fSmell = FALSE;
1538 	}
1539 
1540 	if ( TANK( pStartSoldier ) )
1541 	{
1542 		INT16 sDistance;
1543 
1544 		sDistance = PythSpacesAway( pStartSoldier->sGridNo, pEndSoldier->sGridNo );
1545 
1546 		if ( sDistance <= 8 )
1547 		{
1548 			// blind spot?
1549 			if ( dEndZPos <= PRONE_LOS_POS )
1550 			{
1551 				return( FALSE );
1552 			}
1553 			else if ( sDistance <= 4 && dEndZPos <= CROUCHED_LOS_POS )
1554 			{
1555 				return( FALSE );
1556 			}
1557 		}
1558 	}
1559 
1560 	if ( pEndSoldier->bCamo && !bAware )
1561 	{
1562 
1563 		INT32 iTemp;
1564 
1565 		// reduce effects of camo of 5% per tile moved last turn
1566 		if ( pEndSoldier->ubBodyType == BLOODCAT )
1567 		{
1568 			bEffectiveCamo = 100 - pEndSoldier->bTilesMoved * 5;
1569 		}
1570 		else
1571 		{
1572 			bEffectiveCamo = pEndSoldier->bCamo * (100 - pEndSoldier->bTilesMoved * 5) / 100;
1573 		}
1574 		bEffectiveCamo = __max( bEffectiveCamo, 0 );
1575 
1576 		if ( gAnimControl[ pEndSoldier->usAnimState ].ubEndHeight < ANIM_STAND )
1577 		{
1578 			// reduce visibility by up to a third for camouflage!
1579 			switch( pEndSoldier->bOverTerrainType )
1580 			{
1581 				case FLAT_GROUND:
1582 				case LOW_GRASS:
1583 				case HIGH_GRASS:
1584 					iTemp = ubTileSightLimit;
1585 					iTemp -= iTemp * (bEffectiveCamo / 3) / 100;
1586 					ubTileSightLimit = (UINT8) iTemp;
1587 					break;
1588 				default:
1589 					break;
1590 			}
1591 		}
1592 	}
1593 	else
1594 	{
1595 		bEffectiveCamo = 0;
1596 	}
1597 
1598 	if ( TANK( pEndSoldier ) )
1599 	{
1600 		ubTreeReduction = 0;
1601 	}
1602 	else
1603 	{
1604 		ubTreeReduction = gubTreeSightReduction[ gAnimControl[pEndSoldier->usAnimState].ubEndHeight ];
1605 	}
1606 
1607 	return LineOfSightTest(pStartSoldier->sGridNo, dStartZPos, pEndSoldier->sGridNo, dEndZPos, ubTileSightLimit, ubTreeReduction, bAware, bEffectiveCamo, fSmell, NULL);
1608 }
1609 
SoldierToLocationWindowTest(const SOLDIERTYPE * pStartSoldier,INT16 sEndGridNo)1610 INT16 SoldierToLocationWindowTest(const SOLDIERTYPE* pStartSoldier, INT16 sEndGridNo)
1611 {
1612 	// figure out if there is a SINGLE window between the looker and target
1613 	FLOAT dStartZPos, dEndZPos;
1614 
1615 	CHECKF( pStartSoldier );
1616 	dStartZPos = FixedToFloat( ((gqStandardWindowTopHeight + gqStandardWindowBottomHeight) / 2) );
1617 	if (pStartSoldier->bLevel > 0)
1618 	{
1619 		// on a roof
1620 		dStartZPos += WALL_HEIGHT_UNITS;
1621 	}
1622 	dStartZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[pStartSoldier->sGridNo].sHeight );
1623 	dEndZPos = dStartZPos;
1624 
1625 	// We don't want to consider distance limits here so pass in tile sight limit of 255
1626 	// and consider trees as little as possible
1627 	INT16 sWindowGridNo = NOWHERE;
1628 	LineOfSightTest(pStartSoldier->sGridNo, dStartZPos, sEndGridNo, dEndZPos, 255, 0, TRUE, 0, FALSE, &sWindowGridNo);
1629 	if (sWindowGridNo == pStartSoldier->sGridNo) sWindowGridNo = NOWHERE; // XXX TODO0012
1630 	return( sWindowGridNo );
1631 }
1632 
1633 
SoldierTo3DLocationLineOfSightTest(const SOLDIERTYPE * pStartSoldier,INT16 sGridNo,INT8 bLevel,INT8 bCubeLevel,UINT8 ubTileSightLimit,INT8 bAware)1634 INT32 SoldierTo3DLocationLineOfSightTest(const SOLDIERTYPE* pStartSoldier, INT16 sGridNo, INT8 bLevel, INT8 bCubeLevel, UINT8 ubTileSightLimit, INT8 bAware)
1635 {
1636 	FLOAT dStartZPos, dEndZPos;
1637 	BOOLEAN fOk;
1638 
1639 	CHECKF( pStartSoldier );
1640 
1641 	fOk = CalculateSoldierZPos( pStartSoldier, LOS_POS, &dStartZPos );
1642 	CHECKF( fOk );
1643 
1644 	if (bCubeLevel > 0)
1645 	{
1646 		dEndZPos = ((FLOAT) (bCubeLevel + bLevel * PROFILE_Z_SIZE ) - 0.5f) * HEIGHT_UNITS_PER_INDEX;
1647 		dEndZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ sGridNo ].sHeight );
1648 	}
1649 	else
1650 	{
1651 		const SOLDIERTYPE* const tgt = WhoIsThere2(sGridNo, bLevel);
1652 		if (tgt != NULL)
1653 		{
1654 			// there's a merc there; do a soldier-to-soldier test
1655 			return SoldierToSoldierLineOfSightTest(pStartSoldier, tgt, ubTileSightLimit, bAware);
1656 		}
1657 		// else... assume standing height
1658 		dEndZPos = STANDING_LOS_POS + bLevel * HEIGHT_UNITS;
1659 		// add in ground height
1660 		dEndZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ sGridNo ].sHeight );
1661 	}
1662 
1663 	return LineOfSightTest(pStartSoldier->sGridNo, dStartZPos, sGridNo, dEndZPos, ubTileSightLimit, gubTreeSightReduction[ANIM_STAND], bAware, 0, FALSE, NULL);
1664 }
1665 
SoldierToBodyPartLineOfSightTest(const SOLDIERTYPE * pStartSoldier,INT16 sGridNo,INT8 bLevel,UINT8 ubAimLocation,UINT8 ubTileSightLimit,INT8 bAware)1666 INT32 SoldierToBodyPartLineOfSightTest( const SOLDIERTYPE * pStartSoldier, INT16 sGridNo, INT8 bLevel, UINT8 ubAimLocation, UINT8 ubTileSightLimit, INT8 bAware )
1667 {
1668 	FLOAT dStartZPos, dEndZPos;
1669 	BOOLEAN fOk;
1670 	UINT8 ubPosType;
1671 
1672 	const SOLDIERTYPE* const pEndSoldier = WhoIsThere2(sGridNo, bLevel);
1673 	// CJC August 13, 2002: for this routine to work there MUST be a target at the location specified
1674 	if (pEndSoldier == NULL) return 0;
1675 
1676 	CHECKF( pStartSoldier );
1677 
1678 	fOk = CalculateSoldierZPos( pStartSoldier, LOS_POS, &dStartZPos );
1679 	CHECKF( fOk );
1680 
1681 	switch( ubAimLocation )
1682 	{
1683 		case AIM_SHOT_HEAD:
1684 			ubPosType = HEAD_TARGET_POS;
1685 			break;
1686 		case AIM_SHOT_TORSO:
1687 			ubPosType = TORSO_TARGET_POS;
1688 			break;
1689 		case AIM_SHOT_LEGS:
1690 			ubPosType = LEGS_TARGET_POS;
1691 			break;
1692 		default:
1693 			ubPosType = TARGET_POS;
1694 			break;
1695 	}
1696 
1697 	fOk = CalculateSoldierZPos( pEndSoldier, ubPosType, &dEndZPos );
1698 	if (!fOk)
1699 	{
1700 		return( FALSE );
1701 	}
1702 
1703 	return LineOfSightTest(pStartSoldier->sGridNo, dStartZPos, sGridNo, dEndZPos, ubTileSightLimit, gubTreeSightReduction[ANIM_STAND], bAware, 0, FALSE, NULL);
1704 }
1705 
1706 
SoldierToVirtualSoldierLineOfSightTest(const SOLDIERTYPE * pStartSoldier,INT16 sGridNo,INT8 bLevel,INT8 bStance,UINT8 ubTileSightLimit,INT8 bAware)1707 INT32 SoldierToVirtualSoldierLineOfSightTest(const SOLDIERTYPE* pStartSoldier, INT16 sGridNo, INT8 bLevel, INT8 bStance, UINT8 ubTileSightLimit, INT8 bAware)
1708 {
1709 	FLOAT dStartZPos, dEndZPos;
1710 	BOOLEAN fOk;
1711 
1712 	CHECKF( pStartSoldier );
1713 
1714 	fOk = CalculateSoldierZPos( pStartSoldier, LOS_POS, &dStartZPos );
1715 	CHECKF( fOk );
1716 
1717 	// manually calculate destination Z position.
1718 	switch( bStance )
1719 	{
1720 		case ANIM_STAND:
1721 			dEndZPos = STANDING_LOS_POS;
1722 			break;
1723 		case ANIM_CROUCH:
1724 			dEndZPos = CROUCHED_LOS_POS;
1725 			break;
1726 		case ANIM_PRONE:
1727 			dEndZPos = PRONE_LOS_POS;
1728 			break;
1729 		default:
1730 			return( FALSE );
1731 	}
1732 	dEndZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[sGridNo].sHeight );
1733 	if (bLevel > 0)
1734 	{
1735 		// on a roof
1736 		dEndZPos += WALL_HEIGHT_UNITS;
1737 	}
1738 
1739 	return LineOfSightTest(pStartSoldier->sGridNo, dStartZPos, sGridNo, dEndZPos, ubTileSightLimit, gubTreeSightReduction[ANIM_STAND], bAware, 0, FALSE, NULL);
1740 }
1741 
SoldierToLocationLineOfSightTest(SOLDIERTYPE * pStartSoldier,INT16 sGridNo,UINT8 ubTileSightLimit,INT8 bAware)1742 INT32 SoldierToLocationLineOfSightTest( SOLDIERTYPE * pStartSoldier, INT16 sGridNo, UINT8 ubTileSightLimit, INT8 bAware )
1743 {
1744 	return( SoldierTo3DLocationLineOfSightTest( pStartSoldier, sGridNo, 0, 0, ubTileSightLimit, bAware ) );
1745 }
1746 
LocationToLocationLineOfSightTest(INT16 sStartGridNo,INT8 bStartLevel,INT16 sEndGridNo,INT8 bEndLevel,UINT8 ubTileSightLimit,INT8 bAware)1747 INT32 LocationToLocationLineOfSightTest( INT16 sStartGridNo, INT8 bStartLevel, INT16 sEndGridNo, INT8 bEndLevel, UINT8 ubTileSightLimit, INT8 bAware )
1748 {
1749 	FLOAT dStartZPos, dEndZPos;
1750 
1751 	const SOLDIERTYPE* const start_soldier = WhoIsThere2(sStartGridNo, bStartLevel);
1752 	if (start_soldier != NULL)
1753 	{
1754 		return SoldierTo3DLocationLineOfSightTest(start_soldier, sEndGridNo, bEndLevel, 0, ubTileSightLimit, bAware);
1755 	}
1756 
1757 	// else... assume standing heights
1758 	dStartZPos = STANDING_LOS_POS + bStartLevel * HEIGHT_UNITS;
1759 	// add in ground height
1760 	dStartZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ sStartGridNo ].sHeight );
1761 
1762 	dEndZPos = STANDING_LOS_POS + bEndLevel * HEIGHT_UNITS;
1763 	// add in ground height
1764 	dEndZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ sEndGridNo ].sHeight );
1765 
1766 	return LineOfSightTest(sStartGridNo, dStartZPos, sEndGridNo, dEndZPos, ubTileSightLimit, gubTreeSightReduction[ANIM_STAND], bAware, 0, FALSE, NULL);
1767 }
1768 
1769 /*
1770 INT32 BulletImpactReducedByRange( INT32 iImpact, INT32 iDistanceTravelled, INT32 iRange )
1771 {
1772 	// for now, don't reduce, because did weird stuff to AI!
1773 	return( iImpact );
1774 
1775 	// only start reducing impact at distances greater than one range
1776 	//return( __max( 1, iImpact * ( 100 - ( PERCENT_BULLET_SLOWED_BY_RANGE * iDistanceTravelled ) / iRange ) / 100 ) );
1777 
1778 }*/
1779 
1780 
BulletHitMerc(BULLET * pBullet,STRUCTURE * pStructure,BOOLEAN fIntended)1781 static BOOLEAN BulletHitMerc(BULLET* pBullet, STRUCTURE* pStructure, BOOLEAN fIntended)
1782 {
1783 	SOLDIERTYPE* const pFirer = pBullet->pFirer;
1784 	INT32 iImpact, iDamage;
1785 	FLOAT dZPosRelToMerc;
1786 	UINT8 ubHitLocation = AIM_SHOT_RANDOM;
1787 	UINT8 ubAttackDirection;
1788 	UINT8 ubAmmoType;
1789 	UINT32 uiChanceThrough;
1790 	UINT8 ubSpecial = FIRE_WEAPON_NO_SPECIAL;
1791 	INT16 sHitBy;
1792 	BOOLEAN fStopped = TRUE;
1793 	INT8 bSlot;
1794 	INT8 bHeadSlot = NO_SLOT;
1795 	OBJECTTYPE Object;
1796 	INT16 sNewGridNo;
1797 	BOOLEAN fCanSpewBlood = FALSE;
1798 	INT8 bSpewBloodLevel;
1799 
1800 	// structure IDs for mercs match their merc IDs
1801 	SOLDIERTYPE& tgt = GetMan(pStructure->usStructureID);
1802 
1803 	if (pBullet->usFlags & BULLET_FLAG_KNIFE)
1804 	{
1805 		// Place knife on guy....
1806 
1807 		// See if they have room ( and make sure it's not in hand pos?
1808 		bSlot = FindEmptySlotWithin(&tgt, BIGPOCK1POS, SMALLPOCK8POS );
1809 		if (bSlot == NO_SLOT)
1810 		{
1811 			// Add item
1812 			CreateItem(BLOODY_THROWING_KNIFE, (INT8) pBullet->ubItemStatus, &Object);
1813 
1814 			AddItemToPool(tgt.sGridNo, &Object, INVISIBLE, tgt.bLevel, 0, 0);
1815 
1816 			// Make team look for items
1817 			NotifySoldiersToLookforItems( );
1818 		}
1819 		else
1820 		{
1821 			CreateItem(BLOODY_THROWING_KNIFE, (INT8) pBullet->ubItemStatus, &tgt.inv[bSlot]);
1822 		}
1823 
1824 		ubAmmoType = AMMO_KNIFE;
1825 	}
1826 	else
1827 	{
1828 		ubAmmoType = pFirer->inv[pFirer->ubAttackingHand].ubGunAmmoType;
1829 	}
1830 
1831 	// at least partly compensate for "near miss" increases for this guy, after all, the bullet
1832 	// actually hit him!
1833 	// take this out for now at least... no longer certain that he was awarded a suppression pt
1834 	// when the bullet got near him
1835 	//--tgt.ubSuppressionPoints;
1836 
1837 	if (tgt.uiStatusFlags & SOLDIER_VEHICLE || (tgt.ubBodyType == COW || tgt.ubBodyType == CROW || tgt.ubBodyType == BLOODCAT))
1838 	{
1839 		//ubHitLocation = pStructure->ubVehicleHitLocation;
1840 		ubHitLocation = AIM_SHOT_TORSO;
1841 	}
1842 	else
1843 	{
1844 		// Determine where the person was hit...
1845 
1846 		if (CREATURE_OR_BLOODCAT(&tgt))
1847 		{
1848 			ubHitLocation = AIM_SHOT_TORSO;
1849 
1850 			// adult monster types have a weak spot
1851 			if (ADULTFEMALEMONSTER <= tgt.ubBodyType && tgt.ubBodyType <= YAM_MONSTER)
1852 			{
1853 				ubAttackDirection = (UINT8)GetDirectionToGridNoFromGridNo( pBullet->pFirer->sGridNo, tgt.sGridNo);
1854 				if (ubAttackDirection == tgt.bDirection ||
1855 					ubAttackDirection == OneCCDirection(tgt.bDirection) ||
1856 					ubAttackDirection == OneCDirection(tgt.bDirection))
1857 				{
1858 					// may hit weak spot!
1859 					if (0) // check fact
1860 					{
1861 						uiChanceThrough = 30;
1862 					}
1863 					else
1864 					{
1865 						uiChanceThrough = 1;
1866 					}
1867 
1868 					if (PreRandom( 100 ) < uiChanceThrough)
1869 					{
1870 						ubHitLocation = AIM_SHOT_GLAND;
1871 					}
1872 				}
1873 			}
1874 		}
1875 
1876 		if (ubHitLocation == AIM_SHOT_RANDOM) // i.e. if not set yet
1877 		{
1878 			if (tgt.bOverTerrainType == DEEP_WATER)
1879 			{
1880 				// automatic head hit!
1881 				ubHitLocation = AIM_SHOT_HEAD;
1882 			}
1883 			else
1884 			{
1885 				switch (gAnimControl[tgt.usAnimState].ubEndHeight)
1886 				{
1887 					case ANIM_STAND:
1888 						dZPosRelToMerc = FixedToFloat( pBullet->qCurrZ ) - CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[pBullet->sGridNo].sHeight );
1889 						if ( dZPosRelToMerc > HEIGHT_UNITS )
1890 						{
1891 							dZPosRelToMerc -= HEIGHT_UNITS;
1892 						}
1893 						if (dZPosRelToMerc > STANDING_HEAD_BOTTOM_POS)
1894 						{
1895 							ubHitLocation = AIM_SHOT_HEAD;
1896 						}
1897 						else if (dZPosRelToMerc < STANDING_TORSO_BOTTOM_POS )
1898 						{
1899 							ubHitLocation = AIM_SHOT_LEGS;
1900 						}
1901 						else
1902 						{
1903 							ubHitLocation = AIM_SHOT_TORSO;
1904 						}
1905 						break;
1906 					case ANIM_CROUCH:
1907 						dZPosRelToMerc = FixedToFloat( pBullet->qCurrZ ) - CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[pBullet->sGridNo].sHeight );
1908 						if ( dZPosRelToMerc > HEIGHT_UNITS )
1909 						{
1910 							dZPosRelToMerc -= HEIGHT_UNITS;
1911 						}
1912 						if (dZPosRelToMerc > CROUCHED_HEAD_BOTTOM_POS)
1913 						{
1914 							ubHitLocation = AIM_SHOT_HEAD;
1915 						}
1916 						else if ( dZPosRelToMerc < CROUCHED_TORSO_BOTTOM_POS )
1917 						{
1918 							// prevent targets in water from being hit in legs
1919 							ubHitLocation = AIM_SHOT_LEGS;
1920 						}
1921 						else
1922 						{
1923 							ubHitLocation = AIM_SHOT_TORSO;
1924 						}
1925 						break;
1926 					case ANIM_PRONE:
1927 						ubHitLocation = AIM_SHOT_TORSO;
1928 						break;
1929 
1930 				}
1931 			}
1932 		}
1933 
1934 		if (ubAmmoType == AMMO_MONSTER && (ubHitLocation == AIM_SHOT_HEAD) && !(tgt.uiStatusFlags & SOLDIER_MONSTER))
1935 		{
1936 			UINT8 ubOppositeDirection;
1937 
1938 			ubAttackDirection   = (UINT8)GetDirectionToGridNoFromGridNo(pBullet->pFirer->sGridNo, tgt.sGridNo);
1939 			ubOppositeDirection = OppositeDirection(ubAttackDirection);
1940 
1941 			if (ubOppositeDirection != tgt.bDirection &&
1942 				ubAttackDirection != OneCCDirection(tgt.bDirection) &&
1943 				ubAttackDirection != OneCDirection(tgt.bDirection))
1944 			{
1945 				// lucky bastard was facing away!
1946 			}
1947 			else if ((tgt.inv[HEAD1POS].usItem == NIGHTGOGGLES || tgt.inv[HEAD1POS].usItem == SUNGOGGLES ||
1948 				tgt.inv[HEAD1POS].usItem == GASMASK) && static_cast<INT8>(PreRandom(100)) < tgt.inv[HEAD1POS].bStatus[0])
1949 			{
1950 				// lucky bastard was wearing protective stuff
1951 				bHeadSlot = HEAD1POS;
1952 			}
1953 			else if ((tgt.inv[HEAD2POS].usItem == NIGHTGOGGLES || tgt.inv[HEAD2POS].usItem == SUNGOGGLES ||
1954 				tgt.inv[HEAD2POS].usItem == GASMASK) && static_cast<INT8>(PreRandom(100)) < tgt.inv[HEAD2POS].bStatus[0])
1955 			{
1956 				// lucky bastard was wearing protective stuff
1957 				bHeadSlot = HEAD2POS;
1958 			}
1959 			else
1960 			{
1961 				// splat!!
1962 				ubSpecial = FIRE_WEAPON_BLINDED_BY_SPIT_SPECIAL;
1963 			}
1964 
1965 		}
1966 
1967 	}
1968 
1969 	// Determine damage, checking guy's armour, etc
1970 	INT16 const sRange = GetRangeInCellCoordsFromGridNoDiff(pFirer->sGridNo, tgt.sGridNo);
1971 	if ( gTacticalStatus.uiFlags & GODMODE  && !(pFirer->uiStatusFlags & SOLDIER_PC))
1972 	{
1973 		// in god mode, and firer is computer controlled
1974 		iImpact = 0;
1975 		iDamage = 0;
1976 	}
1977 	else if (fIntended)
1978 	{
1979 		if (pFirer->bOppList[tgt.ubID] == SEEN_CURRENTLY)
1980 		{
1981 			sHitBy = pBullet->sHitBy;
1982 		}
1983 		else
1984 		{
1985 			// hard to aim at something far away being reported by someone else!
1986 			sHitBy = pBullet->sHitBy / 2;
1987 		}
1988 		// hit the intended target which was in our LOS
1989 		// reduce due to range
1990 		iImpact = pBullet->iImpact; //BulletImpactReducedByRange( pBullet->iImpact, pBullet->iLoop, pBullet->iRange );
1991 		iImpact -= pBullet->iImpactReduction;
1992 		if (iImpact < 0)
1993 		{
1994 			// shouldn't happen but
1995 			iImpact = 0;
1996 		}
1997 		iDamage = BulletImpact(pFirer, &tgt, ubHitLocation, iImpact, sHitBy, &ubSpecial);
1998 		// handle hit here...
1999 		if( pFirer->bTeam == 0 )
2000 		{
2001 			// issue #1140 : sync usShotsFired with usShotsHit and so that merc statistic <= 100%
2002 			if (pFirer->target->bLife > 0) gMercProfiles[ pFirer->ubProfile ].usShotsHit++;
2003 		}
2004 
2005 		// intentionally shot
2006 		tgt.fIntendedTarget = TRUE;
2007 
2008 		if (pBullet->usFlags & BULLET_FLAG_BUCKSHOT && &tgt == pFirer->target)
2009 		{
2010 			++tgt.bNumPelletsHitBy;
2011 		}
2012 	}
2013 	else
2014 	{
2015 		// if an accidental target was hit, don't give a bonus for good aim!
2016 		sHitBy = 0;
2017 		iImpact = pBullet->iImpact;
2018 		//iImpact = BulletImpactReducedByRange( pBullet->iImpact, pBullet->iLoop, pBullet->iRange );
2019 		iImpact -= pBullet->iImpactReduction;
2020 		if (iImpact < 0)
2021 		{
2022 			// shouldn't happen but
2023 			iImpact = 0;
2024 		}
2025 		iDamage = BulletImpact(pFirer, &tgt, ubHitLocation, iImpact, sHitBy, &ubSpecial);
2026 
2027 		// accidentally shot
2028 		tgt.fIntendedTarget = FALSE;
2029 	}
2030 
2031 	if ( ubAmmoType == AMMO_MONSTER )
2032 	{
2033 		if (bHeadSlot != NO_SLOT)
2034 		{
2035 			tgt.inv[bHeadSlot].bStatus[0] -= (INT8)(iImpact / 2 + Random(iImpact / 2));
2036 			if (tgt.inv[bHeadSlot].bStatus[0] <= USABLE)
2037 			{
2038 				if (tgt.inv[bHeadSlot].bStatus[0] <= 0)
2039 				{
2040 					DeleteObj(&tgt.inv[bHeadSlot]);
2041 					DirtyMercPanelInterface(&tgt, DIRTYLEVEL2);
2042 				}
2043 				// say curse?
2044 			}
2045 		}
2046 	}
2047 	else if (ubHitLocation == AIM_SHOT_HEAD)
2048 	{
2049 		// bullet to the head may damage any head item
2050 		bHeadSlot = HEAD1POS + (INT8) Random( 2 );
2051 		if (tgt.inv[bHeadSlot].usItem != NOTHING)
2052 		{
2053 			tgt.inv[bHeadSlot].bStatus[0] -= (INT8)(Random(iImpact / 2));
2054 			if (tgt.inv[bHeadSlot].bStatus[0] < 0)
2055 			{
2056 				// just break it...
2057 				tgt.inv[bHeadSlot].bStatus[0] = 1;
2058 			}
2059 		}
2060 	}
2061 
2062 	// check to see if the guy is a friendly?..if so, up the number of times wounded
2063 	if (tgt.bTeam == OUR_TEAM)
2064 	{
2065 		++gMercProfiles[tgt.ubProfile].usTimesWounded;
2066 	}
2067 
2068 	// check to see if someone was accidentally hit when no target was specified by the player
2069 	if (pFirer->bTeam == OUR_TEAM && pFirer->target == NULL && tgt.bNeutral)
2070 	{
2071 		if (tgt.ubCivilianGroup == KINGPIN_CIV_GROUP || tgt.ubCivilianGroup == HICKS_CIV_GROUP)
2072 		{
2073 			// hicks and kingpin are touchy!
2074 			pFirer->target = &tgt;
2075 		}
2076 		else if ( Random( 100 ) < 60 )
2077 		{
2078 			// get touchy
2079 			pFirer->target = &tgt;
2080 		}
2081 
2082 	}
2083 
2084 	if ( (pFirer->bDoBurst) && (ubSpecial == FIRE_WEAPON_NO_SPECIAL) )
2085 	{
2086 		// the animation required by the bullet hit (head explosion etc) overrides the
2087 		// hit-by-a-burst animation
2088 		ubSpecial = FIRE_WEAPON_BURST_SPECIAL;
2089 	}
2090 
2091 	// breath loss is based on original impact of bullet
2092 	INT16  const breath_loss   = (iImpact * BP_GET_WOUNDED * (tgt.bBreathMax * 100 - tgt.sBreathRed)) / 10000;
2093 	UINT16 const hit_direction = GetDirectionFromGridNo(pFirer->sGridNo, &tgt);
2094 
2095 	// now check to see if the bullet goes THROUGH this person! (not vehicles)
2096 	if (!(tgt.uiStatusFlags & SOLDIER_VEHICLE) &&
2097 		(ubAmmoType == AMMO_REGULAR || ubAmmoType == AMMO_AP || ubAmmoType == AMMO_SUPER_AP) &&
2098 		!EXPLOSIVE_GUN(pFirer->usAttackingWeapon))
2099 	{
2100 		// if we do more damage than expected, then the bullet will be more likely
2101 		// to be lodged in the body
2102 
2103 		// if we do less than expected, then the bullet has been slowed and less
2104 		// likely to continue
2105 
2106 		// higher chance for bigger guns, because they'll go through the back armour
2107 
2108 		// reduce impact to match damage, if damage wasn't more than the impact
2109 		// due to good aim, etc.
2110 		if (iDamage < iImpact)
2111 		{
2112 			iImpact = iDamage;
2113 		}
2114 		uiChanceThrough = (UINT8) __max( 0, ( iImpact - 20 ) );
2115 		if (PreRandom( 100 ) < uiChanceThrough )
2116 		{
2117 			// bullet MAY go through
2118 			// adjust for bullet going through person
2119 			iImpact -= CalcBodyImpactReduction( ubAmmoType, ubHitLocation );
2120 			// adjust for other side of armour!
2121 			iImpact -= TotalArmourProtection(tgt, ubHitLocation, iImpact, ubAmmoType);
2122 			if (iImpact > 0)
2123 			{
2124 				pBullet->iImpact = (INT8) iImpact;
2125 				// bullet was NOT stopped
2126 				fStopped = FALSE;
2127 			}
2128 		}
2129 	}
2130 
2131 	if (fStopped)
2132 	{
2133 		RemoveBullet(pBullet);
2134 	}
2135 	else
2136 	{
2137 		// ATE: I'm in enemy territory again, evil CC's world :)
2138 		// This looks like the place I should add code to spew blood on the ground
2139 		// The algorithm I'm going to use is given the current gridno of bullet,
2140 		// get a new gridno based on direction it was moving.  Check to see if we're not
2141 		// going through walls, etc by testing for a path, unless on the roof, in which case it would always
2142 		// be legal, but the bLevel May change...
2143 		sNewGridNo = NewGridNo(pBullet->sGridNo, DirectionInc(OppositeDirection(hit_direction)));
2144 
2145 		bSpewBloodLevel = tgt.bLevel;
2146 		fCanSpewBlood   = TRUE;
2147 
2148 		// If on anything other than bLevel of 0, we can pretty much freely spew blood
2149 		if ( bSpewBloodLevel == 0 )
2150 		{
2151 			if (gubWorldMovementCosts[sNewGridNo][OppositeDirection(hit_direction)][0] >= TRAVELCOST_BLOCKED)
2152 			{
2153 				fCanSpewBlood = FALSE;
2154 			}
2155 		}
2156 		else
2157 		{
2158 			// If a roof does not exist here, make level = 0
2159 			if ( !IsRoofPresentAtGridno( sNewGridNo ) )
2160 			{
2161 				bSpewBloodLevel = 0;
2162 			}
2163 		}
2164 
2165 		if ( fCanSpewBlood )
2166 		{
2167 			// Drop blood dude!
2168 			InternalDropBlood(sNewGridNo, bSpewBloodLevel, HUMAN, MAXBLOODQUANTITY, 1);
2169 		}
2170 	}
2171 
2172 	if (gTacticalStatus.ubCurrentTeam != OUR_TEAM && tgt.bTeam == OUR_TEAM)
2173 	{
2174 		// someone has been hit so no close-call quotes
2175 		gTacticalStatus.fSomeoneHit = TRUE;
2176 	}
2177 
2178 	// handle hit!
2179 	WeaponHit(&tgt, pFirer->usAttackingWeapon, iDamage, breath_loss, hit_direction, tgt.dXPos, tgt.dYPos, 20, sRange, pFirer, ubSpecial, ubHitLocation);
2180 	return( fStopped );
2181 }
2182 
2183 
BulletHitStructure(BULLET * pBullet,UINT16 usStructureID,INT32 iImpact,BOOLEAN fStopped)2184 static void BulletHitStructure(BULLET* pBullet, UINT16 usStructureID, INT32 iImpact, BOOLEAN fStopped)
2185 {
2186 	const FIXEDPT qCurrX = pBullet->qCurrX;
2187 	const FIXEDPT qCurrY = pBullet->qCurrY;
2188 	const FIXEDPT qCurrZ = pBullet->qCurrZ;
2189 	INT16 sXPos = FIXEDPT_TO_INT32(qCurrX + FloatToFixed(0.5f)); // + 0.5);
2190 	INT16 sYPos = FIXEDPT_TO_INT32(qCurrY + FloatToFixed(0.5f)); // (dCurrY + 0.5);
2191 	INT16 sZPos = CONVERT_HEIGHTUNITS_TO_PIXELS((INT16)FIXEDPT_TO_INT32(qCurrZ + FloatToFixed(0.5f)));// dCurrZ + 0.5) );
2192 	StructureHit(pBullet, sXPos, sYPos, sZPos, usStructureID, iImpact, fStopped);
2193 }
2194 
2195 
BulletHitWindow(BULLET * pBullet,INT16 sGridNo,UINT16 usStructureID,BOOLEAN fBlowWindowSouth)2196 static void BulletHitWindow(BULLET* pBullet, INT16 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth)
2197 {
2198 	WindowHit( sGridNo, usStructureID, fBlowWindowSouth, FALSE );
2199 }
2200 
2201 
ChanceOfBulletHittingStructure(INT32 iDistance,INT32 iDistanceToTarget,INT16 sHitBy)2202 static UINT32 ChanceOfBulletHittingStructure(INT32 iDistance, INT32 iDistanceToTarget, INT16 sHitBy)
2203 {
2204 	INT32 iCloseToCoverPenalty;
2205 
2206 	if (iDistance / CELL_X_SIZE > MAX_DIST_FOR_LESS_THAN_MAX_CHANCE_TO_HIT_STRUCTURE)
2207 	{
2208 		return( MAX_CHANCE_OF_HITTING_STRUCTURE );
2209 	}
2210 	else
2211 	{
2212 		iCloseToCoverPenalty = iDistance / 5 - (iDistanceToTarget - iDistance);
2213 		if (iCloseToCoverPenalty < 0)
2214 		{
2215 			iCloseToCoverPenalty = 0;
2216 		}
2217 		if (sHitBy < 0)
2218 		{
2219 			// add 20% to distance so that misses hit nearer obstacles a bit more
2220 			iDistance += iDistance / 5;
2221 		}
2222 		if ( ( (iDistance + iCloseToCoverPenalty) / CELL_X_SIZE ) > MAX_DIST_FOR_LESS_THAN_MAX_CHANCE_TO_HIT_STRUCTURE )
2223 		{
2224 			return( MAX_CHANCE_OF_HITTING_STRUCTURE );
2225 		}
2226 		else
2227 		{
2228 			return( guiStructureHitChance[ (iDistance + iCloseToCoverPenalty) / CELL_X_SIZE ] );
2229 		}
2230 	}
2231 }
2232 
2233 
StructureResistanceIncreasedByRange(INT32 iImpactReduction,INT32 iGunRange,INT32 iDistance)2234 static INT32 StructureResistanceIncreasedByRange(INT32 iImpactReduction, INT32 iGunRange, INT32 iDistance)
2235 {
2236 	return( iImpactReduction * ( 100 + PERCENT_BULLET_SLOWED_BY_RANGE * (iDistance - iGunRange) / iGunRange ) / 100 );
2237 	/*
2238 	if ( iDistance > iGunRange )
2239 	{
2240 		return( iImpactReduction * ( 100 + PERCENT_BULLET_SLOWED_BY_RANGE * (iDistance - iGunRange) / iGunRange ) / 100 );
2241 	}
2242 	else
2243 	{
2244 		return( iImpactReduction );
2245 	}*/
2246 }
2247 
2248 
HandleBulletStructureInteraction(BULLET * pBullet,STRUCTURE * pStructure,BOOLEAN * pfHit)2249 static INT32 HandleBulletStructureInteraction(BULLET* pBullet, STRUCTURE* pStructure, BOOLEAN* pfHit)
2250 {
2251 	DOOR  *pDoor;
2252 	INT16 sLockDamage;
2253 
2254 	// returns remaining impact amount
2255 
2256 
2257 	INT32 iCurrImpact;
2258 	INT32 iImpactReduction;
2259 
2260 	*pfHit = FALSE;
2261 
2262 	if (pBullet->usFlags & BULLET_FLAG_KNIFE || pBullet->usFlags & BULLET_FLAG_MISSILE || pBullet->usFlags & BULLET_FLAG_TANK_CANNON || pBullet->usFlags & BULLET_FLAG_FLAME )
2263 	{
2264 		// stops!
2265 		*pfHit = TRUE;
2266 		return( 0 );
2267 	}
2268 	else if ( pBullet->usFlags & BULLET_FLAG_SMALL_MISSILE )
2269 	{
2270 		// stops if using HE ammo
2271 		if (pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ].ubGunAmmoType == AMMO_HE)
2272 		{
2273 			*pfHit = TRUE;
2274 			return( 0 );
2275 		}
2276 	}
2277 
2278 	// ATE: Alrighty, check for shooting door locks...
2279 	// First check this is a type of struct that can handle locks...
2280 	if ( pStructure->fFlags & ( STRUCTURE_DOOR | STRUCTURE_OPENABLE ) && PythSpacesAway( (INT16) pBullet->sTargetGridNo, pStructure->sGridNo ) <= 2 )
2281 	{
2282 		// lookup lock table to see if we have a lock,
2283 		// and then remove lock if enough damage done....
2284 		pDoor = FindDoorInfoAtGridNo( pBullet->sGridNo );
2285 
2286 		// Does it have a lock?
2287 		if ( pDoor && LockTable[ pDoor->ubLockID ].ubPickDifficulty < 50 && LockTable[ pDoor->ubLockID ].ubSmashDifficulty < 70 )
2288 		{
2289 			// Yup.....
2290 
2291 			// Chance that it hit the lock....
2292 			if ( PreRandom( 2 ) == 0 )
2293 			{
2294 				// Adjust damage-- CC adjust this based on gun type, etc.....
2295 				//sLockDamage = (INT16)( 35 + Random( 35 ) );
2296 				sLockDamage = (INT16) (pBullet->iImpact - pBullet->iImpactReduction);
2297 				sLockDamage += (INT16) PreRandom( sLockDamage );
2298 
2299 				ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, TacticalStr[ LOCK_HAS_BEEN_HIT ] );
2300 
2301 				pDoor->bLockDamage+= sLockDamage;
2302 
2303 				// Check if it has been shot!
2304 				if ( pDoor->bLockDamage > LockTable[ pDoor->ubLockID ].ubSmashDifficulty )
2305 				{
2306 					// Display message!
2307 					ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, TacticalStr[ LOCK_HAS_BEEN_DESTROYED ] );
2308 
2309 					// succeeded! door can never be locked again, so remove from door list...
2310 					RemoveDoorInfoFromTable( pDoor->sGridNo );
2311 
2312 					// MARKSMANSHIP GAIN (marksPts): Opened/Damaged a door
2313 					StatChange(*pBullet->pFirer, MARKAMT, 10, FROM_SUCCESS);
2314 				}
2315 			}
2316 		}
2317 	}
2318 
2319 	// okay, this seems pretty weird, so here's the comment to explain it:
2320 	// iImpactReduction is the reduction in impact due to the structure
2321 	// pBullet->iImpactReduction is the accumulated reduction in impact
2322 	//   for all bullets encountered thus far
2323 	// iCurrImpact is the original impact value of the bullet reduced due to
2324 	//   range.  To avoid problems involving multiple multiplication
2325 	//   ( (1 - X) * (1 - Y) != (1 - X - Y) ! ), this is calculated from
2326 	//   scratch at each collision with an obstacle
2327 	//   reduction due to range is 25% per "max range"
2328 	if (PreRandom( 100 ) < pStructure->pDBStructureRef->pDBStructure->ubDensity)
2329 	{
2330 		iCurrImpact = pBullet->iImpact;
2331 		//iCurrImpact = BulletImpactReducedByRange( pBullet->iImpact, pBullet->iLoop, pBullet->iRange );
2332 		iImpactReduction = gubMaterialArmour[ pStructure->pDBStructureRef->pDBStructure->ubArmour ];
2333 		iImpactReduction = StructureResistanceIncreasedByRange( iImpactReduction, pBullet->iRange, pBullet->iLoop );
2334 
2335 		switch (pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ].ubGunAmmoType)
2336 		{
2337 			case AMMO_HP:
2338 				iImpactReduction = AMMO_STRUCTURE_ADJUSTMENT_HP( iImpactReduction );
2339 				break;
2340 			case AMMO_AP:
2341 			case AMMO_HEAT:
2342 				iImpactReduction = AMMO_STRUCTURE_ADJUSTMENT_AP( iImpactReduction );
2343 				break;
2344 			case AMMO_SUPER_AP:
2345 				iImpactReduction = AMMO_STRUCTURE_ADJUSTMENT_SAP( iImpactReduction );
2346 				break;
2347 			default:
2348 				break;
2349 		}
2350 
2351 		pBullet->iImpactReduction += iImpactReduction;
2352 
2353 		// really weak stuff like grass should never *stop* a bullet, maybe slow it though
2354 		if ( pStructure->pDBStructureRef->pDBStructure->ubArmour == MATERIAL_LIGHT_VEGETATION )
2355 		{
2356 			// just return a +ve value to indicate the bullet wasn't stopped
2357 			*pfHit = FALSE;
2358 			return( 1 );
2359 		}
2360 
2361 		*pfHit = TRUE;
2362 		return( iCurrImpact - pBullet->iImpactReduction );
2363 	}
2364 	else
2365 	{
2366 		// just return a +ve value to indicate the bullet wasn't stopped
2367 		*pfHit = FALSE;
2368 		return( 1 );
2369 	}
2370 }
2371 
2372 
CTGTHandleBulletStructureInteraction(BULLET * pBullet,STRUCTURE * pStructure)2373 static INT32 CTGTHandleBulletStructureInteraction(BULLET* pBullet, STRUCTURE* pStructure)
2374 {
2375 	// returns reduction in impact for summing in CTGT
2376 
2377 	//INT32 iCurrImpact;
2378 	INT32 iImpactReduction;
2379 
2380 	if (pBullet->usFlags & BULLET_FLAG_KNIFE || pBullet->usFlags & BULLET_FLAG_MISSILE || pBullet->usFlags & BULLET_FLAG_FLAME || pBullet->usFlags & BULLET_FLAG_TANK_CANNON )
2381 	{
2382 		// knife/rocket stops when it hits anything, and people block completely
2383 		return( pBullet->iImpact );
2384 	}
2385 	else if ( pBullet->usFlags & BULLET_FLAG_SMALL_MISSILE )
2386 	{
2387 		// stops if using HE ammo
2388 		if (pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ].ubGunAmmoType == AMMO_HE)
2389 		{
2390 			return( pBullet->iImpact );
2391 		}
2392 	}
2393 
2394 	// okay, this seems pretty weird, so here's the comment to explain it:
2395 	// iImpactReduction is the reduction in impact due to the structure
2396 	// pBullet->iImpactReduction is the accumulated reduction in impact
2397 	//   for all bullets encountered thus far
2398 	// iCurrImpact is the original impact value of the bullet reduced due to
2399 	//   range.  To avoid problems involving multiple multiplication
2400 	//   ( (1 - X) * (1 - Y) != (1 - X - Y) ! ), this is calculated from
2401 	//   scratch at each collision with an obstacle
2402 	//   reduction due to range is 25% per "max range"
2403 	//iCurrImpact = BulletImpactReducedByRange( pBullet->iImpact, pBullet->iLoop, pBullet->iRange );
2404 	//iCurrImpact = pBullet->iImpact;
2405 	// multiply impact reduction by 100 to retain fractions for a bit...
2406 	iImpactReduction = gubMaterialArmour[ pStructure->pDBStructureRef->pDBStructure->ubArmour ] * pStructure->pDBStructureRef->pDBStructure->ubDensity / 100;
2407 	iImpactReduction = StructureResistanceIncreasedByRange( iImpactReduction, pBullet->iRange, pBullet->iLoop );
2408 	switch (pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ].ubGunAmmoType)
2409 	{
2410 		case AMMO_HP:
2411 			iImpactReduction = AMMO_STRUCTURE_ADJUSTMENT_HP( iImpactReduction );
2412 			break;
2413 		case AMMO_AP:
2414 			iImpactReduction = AMMO_STRUCTURE_ADJUSTMENT_AP( iImpactReduction );
2415 			break;
2416 		case AMMO_SUPER_AP:
2417 			iImpactReduction = AMMO_STRUCTURE_ADJUSTMENT_SAP( iImpactReduction );
2418 			break;
2419 		default:
2420 			break;
2421 	}
2422 	return( iImpactReduction );
2423 }
2424 
2425 
CalcChanceToGetThrough(BULLET * pBullet)2426 static UINT8 CalcChanceToGetThrough(BULLET* pBullet)
2427 {
2428 	FIXEDPT qLandHeight;
2429 	INT32 iCurrAboveLevelZ;
2430 	INT32 iCurrCubesAboveLevelZ;
2431 	INT16 sDesiredLevel;
2432 
2433 	INT32 iOldTileX;
2434 	INT32 iOldTileY;
2435 	INT32 iOldCubesZ;
2436 
2437 	MAP_ELEMENT * pMapElement;
2438 	STRUCTURE * pStructure;
2439 	STRUCTURE * pRoofStructure = NULL;
2440 
2441 	FIXEDPT qLastZ;
2442 
2443 	BOOLEAN fIntended;
2444 	INT8 bOldLOSIndexX;
2445 	INT8 bOldLOSIndexY;
2446 
2447 	INT32 iChanceToGetThrough = 100;
2448 
2449 	FIXEDPT qDistToTravelX;
2450 	FIXEDPT qDistToTravelY;
2451 	INT32 iStepsToTravelX;
2452 	INT32 iStepsToTravelY;
2453 	INT32 iStepsToTravel;
2454 	INT32 iNumLocalStructures;
2455 	INT32 iStructureLoop;
2456 	UINT32 uiChanceOfHit;
2457 	INT32 iGridNo;
2458 	INT32 iTotalStructureImpact;
2459 	BOOLEAN fResolveHit;
2460 
2461 	FIXEDPT qWallHeight;
2462 	FIXEDPT qWindowBottomHeight;
2463 	FIXEDPT qWindowTopHeight;
2464 
2465 	SLOGD("Starting CalcChanceToGetThrough" );
2466 
2467 	do
2468 	{
2469 		// check a particular tile
2470 		// retrieve values from world for this particular tile
2471 		iGridNo = pBullet->iCurrTileX + pBullet->iCurrTileY * WORLD_COLS;
2472 		STLOGD("CTGT now at {}", iGridNo);
2473 		pMapElement = &(gpWorldLevelData[ iGridNo ] );
2474 		qLandHeight = INT32_TO_FIXEDPT( CONVERT_PIXELS_TO_HEIGHTUNITS( pMapElement->sHeight ) );
2475 		qWallHeight = gqStandardWallHeight + qLandHeight;
2476 		qWindowBottomHeight = gqStandardWindowBottomHeight + qLandHeight;
2477 		qWindowTopHeight = gqStandardWindowTopHeight + qLandHeight;
2478 
2479 		// Assemble list of structures we might hit!
2480 		iNumLocalStructures = 0;
2481 		pStructure = pMapElement->pStructureHead;
2482 		// calculate chance of hitting each structure
2483 		uiChanceOfHit = ChanceOfBulletHittingStructure( pBullet->iLoop, pBullet->iDistanceLimit, 0 );
2484 
2485 		// reset roof structure pointer each tile
2486 		pRoofStructure = NULL;
2487 
2488 		if (iGridNo == (INT32) pBullet->sTargetGridNo)
2489 		{
2490 			fIntended = TRUE;
2491 			// if in the same tile as our destination, we WANT to hit the structure!
2492 			uiChanceOfHit = 100;
2493 		}
2494 		else
2495 		{
2496 			fIntended = FALSE;
2497 		}
2498 
2499 		iCurrAboveLevelZ = FIXEDPT_TO_INT32( pBullet->qCurrZ - qLandHeight );
2500 		if (iCurrAboveLevelZ < 0)
2501 		{
2502 			// ground is in the way!
2503 			return( 0 );
2504 		}
2505 		iCurrCubesAboveLevelZ = CONVERT_HEIGHTUNITS_TO_INDEX( iCurrAboveLevelZ );
2506 
2507 		while( pStructure )
2508 		{
2509 			if (pStructure->fFlags & ALWAYS_CONSIDER_HIT)
2510 			{
2511 				// ALWAYS add walls
2512 				gpLocalStructure[iNumLocalStructures] = pStructure;
2513 				// fence is special
2514 				//(iCurrCubesAboveLevelZ <= iStartCubesAboveLevelZ)
2515 				if ( pStructure->fFlags & STRUCTURE_ANYFENCE )
2516 				{
2517 					if (pStructure->pDBStructureRef->pDBStructure->ubDensity < 100)
2518 					{
2519 						guiLocalStructureCTH[iNumLocalStructures] = uiChanceOfHit;
2520 					}
2521 					else if ((pBullet->iLoop <= CLOSE_TO_FIRER) && (iCurrCubesAboveLevelZ <= pBullet->bStartCubesAboveLevelZ) &&
2522 						(pBullet->bEndCubesAboveLevelZ >= iCurrCubesAboveLevelZ) &&
2523 						iCurrCubesAboveLevelZ == (StructureHeight(pStructure) - 1))
2524 					{
2525 						guiLocalStructureCTH[iNumLocalStructures] = uiChanceOfHit;
2526 					}
2527 					else if ((pBullet->iDistanceLimit - pBullet->iLoop <= CLOSE_TO_FIRER) &&
2528 						(iCurrCubesAboveLevelZ <= pBullet->bEndCubesAboveLevelZ) &&
2529 						iCurrCubesAboveLevelZ == (StructureHeight( pStructure ) - 1))
2530 					{
2531 						guiLocalStructureCTH[iNumLocalStructures] = uiChanceOfHit;
2532 					}
2533 					else
2534 					{
2535 						guiLocalStructureCTH[iNumLocalStructures] = 100;
2536 					}
2537 				}
2538 				else
2539 				{
2540 					guiLocalStructureCTH[iNumLocalStructures] = 100;
2541 				}
2542 				gubLocalStructureNumTimesHit[iNumLocalStructures] = 0;
2543 				iNumLocalStructures++;
2544 			}
2545 			else if (pStructure->fFlags & STRUCTURE_ROOF)
2546 			{
2547 				// only consider roofs if the flag is set; don't add them to the array since they
2548 				// are a special case
2549 				if (pBullet->fCheckForRoof)
2550 				{
2551 					pRoofStructure = pStructure;
2552 
2553 					if ( pRoofStructure )
2554 					{
2555 						qLastZ = pBullet->qCurrZ - pBullet->qIncrZ;
2556 
2557 						// if just on going to next tile we cross boundary, then roof stops bullet here!
2558 						if ((qLastZ > qWallHeight && pBullet->qCurrZ <= qWallHeight) ||
2559 							(qLastZ < qWallHeight && pBullet->qCurrZ >= qWallHeight))
2560 						{
2561 							// hit a roof
2562 							return( 0 );
2563 						}
2564 					}
2565 
2566 				}
2567 			}
2568 			else if (pStructure->fFlags & STRUCTURE_PERSON)
2569 			{
2570 				SOLDIERTYPE const& person = GetMan(pStructure->usStructureID);
2571 				if (&person != pBullet->pFirer && &person != pBullet->target)
2572 				{
2573 					// ignore intended target since we will get closure upon reaching the center
2574 					// of the destination tile
2575 
2576 					// ignore intervening target if not visible; PCs are always visible so AI will never skip them on that
2577 					// basis
2578 					if (!fIntended && person.bVisible == TRUE)
2579 					{
2580 						// in actually moving the bullet, we consider only count friends as targets if the bullet is unaimed
2581 						// (buckshot), if they are the intended target, or beyond the range of automatic friendly fire hits
2582 						// OR a 1 in 30 chance occurs
2583 						if (gAnimControl[person.usAnimState].ubEndHeight == ANIM_STAND &&
2584 							((pBullet->fAimed && pBullet->iLoop > MIN_DIST_FOR_HIT_FRIENDS) ||
2585 							(!pBullet->fAimed && pBullet->iLoop > MIN_DIST_FOR_HIT_FRIENDS_UNAIMED)))
2586 						{
2587 							// could hit this person!
2588 							gpLocalStructure[iNumLocalStructures] = pStructure;
2589 							// CJC commented this out because of tank trying to shoot through another tank
2590 							//guiLocalStructureCTH[iNumLocalStructures] = uiChanceOfHit;
2591 							guiLocalStructureCTH[iNumLocalStructures] = 100;
2592 							gubLocalStructureNumTimesHit[iNumLocalStructures] = 0;
2593 							iNumLocalStructures++;
2594 						}
2595 						else
2596 						{
2597 							// minimal chance of hitting this person
2598 							gpLocalStructure[iNumLocalStructures] = pStructure;
2599 							guiLocalStructureCTH[iNumLocalStructures] = MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE;
2600 							gubLocalStructureNumTimesHit[iNumLocalStructures] = 0;
2601 							iNumLocalStructures++;
2602 						}
2603 					}
2604 				}
2605 			}
2606 			else if ( pStructure->fFlags & STRUCTURE_CORPSE )
2607 			{
2608 				if ( iGridNo == (INT32) pBullet->sTargetGridNo || (pStructure->pDBStructureRef->pDBStructure->ubNumberOfTiles >= 10) )
2609 				{
2610 					// could hit this corpse!
2611 					// but we should ignore the corpse if there is someone standing there
2612 					if ( FindStructure( (INT16) iGridNo, STRUCTURE_PERSON ) == NULL )
2613 					{
2614 						gpLocalStructure[iNumLocalStructures] = pStructure;
2615 						iNumLocalStructures++;
2616 					}
2617 				}
2618 			}
2619 			else
2620 			{
2621 				if ( pBullet->iLoop > CLOSE_TO_FIRER && !fIntended )
2622 				{
2623 					// could hit it
2624 
2625 					gpLocalStructure[iNumLocalStructures] = pStructure;
2626 					guiLocalStructureCTH[iNumLocalStructures] = uiChanceOfHit;
2627 					gubLocalStructureNumTimesHit[iNumLocalStructures] = 0;
2628 					iNumLocalStructures++;
2629 				}
2630 			}
2631 			pStructure = pStructure->pNext;
2632 		}
2633 
2634 		// record old tile location for loop purposes
2635 		iOldTileX = pBullet->iCurrTileX;
2636 		iOldTileY = pBullet->iCurrTileY;
2637 
2638 		do
2639 		{
2640 			// check a particular location within the tile
2641 
2642 			// check for collision with the ground
2643 			iCurrAboveLevelZ = FIXEDPT_TO_INT32( pBullet->qCurrZ - qLandHeight );
2644 			if (iCurrAboveLevelZ < 0)
2645 			{
2646 				// ground is in the way!
2647 				return( 0 );
2648 			}
2649 			// check for the existence of structures
2650 			pStructure = pMapElement->pStructureHead;
2651 			if (pStructure == NULL)
2652 			{	// no structures in this tile, and THAT INCLUDES ROOFS AND PEOPLE! :-)
2653 				// new system; figure out how many steps until we cross the next edge
2654 				// and then fast forward that many steps.
2655 
2656 				iOldTileX = pBullet->iCurrTileX;
2657 				iOldTileY = pBullet->iCurrTileY;
2658 				iOldCubesZ = pBullet->iCurrCubesZ;
2659 
2660 				if (pBullet->qIncrX > 0)
2661 				{
2662 					qDistToTravelX = INT32_TO_FIXEDPT( CELL_X_SIZE ) - (pBullet->qCurrX % INT32_TO_FIXEDPT( CELL_X_SIZE ));
2663 					iStepsToTravelX = qDistToTravelX / pBullet->qIncrX;
2664 				}
2665 				else if (pBullet->qIncrX < 0)
2666 				{
2667 					qDistToTravelX = pBullet->qCurrX % INT32_TO_FIXEDPT( CELL_X_SIZE );
2668 					iStepsToTravelX = qDistToTravelX / (-pBullet->qIncrX);
2669 				}
2670 				else
2671 				{
2672 					// make sure we don't consider X a limit :-)
2673 					iStepsToTravelX = 1000000;
2674 				}
2675 
2676 				if (pBullet->qIncrY > 0)
2677 				{
2678 					qDistToTravelY = INT32_TO_FIXEDPT( CELL_Y_SIZE ) - (pBullet->qCurrY % INT32_TO_FIXEDPT( CELL_Y_SIZE ));
2679 					iStepsToTravelY = qDistToTravelY / pBullet->qIncrY;
2680 				}
2681 				else if (pBullet->qIncrY < 0)
2682 				{
2683 					qDistToTravelY = pBullet->qCurrY % INT32_TO_FIXEDPT( CELL_Y_SIZE );
2684 					iStepsToTravelY = qDistToTravelY / (-pBullet->qIncrY);
2685 				}
2686 				else
2687 				{
2688 					// make sure we don't consider Y a limit :-)
2689 					iStepsToTravelY = 1000000;
2690 				}
2691 
2692 				// add 1 to the # of steps to travel to go INTO the next tile
2693 				iStepsToTravel = __min( iStepsToTravelX, iStepsToTravelY ) + 1;
2694 
2695 				pBullet->qCurrX += pBullet->qIncrX * iStepsToTravel;
2696 				pBullet->qCurrY += pBullet->qIncrY * iStepsToTravel;
2697 				pBullet->qCurrZ += pBullet->qIncrZ * iStepsToTravel;
2698 				pBullet->iLoop += iStepsToTravel;
2699 
2700 				// check for ground collision
2701 				if (pBullet->qCurrZ < qLandHeight && pBullet->iLoop < pBullet->iDistanceLimit)
2702 				{
2703 					// ground is in the way!
2704 					return( 0 );
2705 				}
2706 
2707 				// figure out the new tile location
2708 				pBullet->iCurrTileX = FIXEDPT_TO_TILE_NUM( pBullet->qCurrX );
2709 				pBullet->iCurrTileY = FIXEDPT_TO_TILE_NUM( pBullet->qCurrY );
2710 				pBullet->iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrZ ) );
2711 				pBullet->bLOSIndexX = FIXEDPT_TO_LOS_INDEX( pBullet->qCurrX );
2712 				pBullet->bLOSIndexY = FIXEDPT_TO_LOS_INDEX( pBullet->qCurrY );
2713 
2714 				SLOGD(ST::format("CTGT at {} {} after traversing empty tile",
2715 					pBullet->bLOSIndexX, pBullet->bLOSIndexY));
2716 			}
2717 			else
2718 			{
2719 				// there are structures in this tile
2720 
2721 				iCurrCubesAboveLevelZ = CONVERT_HEIGHTUNITS_TO_INDEX( iCurrAboveLevelZ );
2722 
2723 				// figure out the LOS cube level of the current point
2724 				if (iCurrCubesAboveLevelZ < STRUCTURE_ON_ROOF_MAX)
2725 				{
2726 					if (iCurrCubesAboveLevelZ < STRUCTURE_ON_GROUND_MAX)
2727 					{
2728 						// check objects on the ground
2729 						sDesiredLevel = STRUCTURE_ON_GROUND;
2730 					}
2731 					else
2732 					{
2733 						// check objects on roofs
2734 						sDesiredLevel = STRUCTURE_ON_ROOF;
2735 						iCurrCubesAboveLevelZ -= STRUCTURE_ON_ROOF;
2736 					}
2737 					// check structures for collision
2738 					for ( iStructureLoop = 0; iStructureLoop < iNumLocalStructures; iStructureLoop++)
2739 					{
2740 						pStructure = gpLocalStructure[iStructureLoop];
2741 						if (pStructure && pStructure->sCubeOffset == sDesiredLevel)
2742 						{
2743 							if (((*(pStructure->pShape))[pBullet->bLOSIndexX][pBullet->bLOSIndexY] & AtHeight[iCurrCubesAboveLevelZ]) > 0)
2744 							{
2745 								if (pStructure->fFlags & STRUCTURE_PERSON)
2746 								{
2747 									// hit someone?
2748 									if (fIntended)
2749 									{	// gotcha! ... return chance to get through
2750 										iChanceToGetThrough = iChanceToGetThrough * (pBullet->iImpact - pBullet->iImpactReduction) / pBullet->iImpact;
2751 										return( (UINT8) iChanceToGetThrough );
2752 									}
2753 									else
2754 									{
2755 										gubLocalStructureNumTimesHit[iStructureLoop]++;
2756 									}
2757 								}
2758 								else if (pStructure->fFlags & STRUCTURE_WALLNWINDOW && pBullet->qCurrZ >= qWindowBottomHeight && pBullet->qCurrZ <= qWindowTopHeight)
2759 								{
2760 									fResolveHit = ResolveHitOnWall( pStructure, iGridNo, pBullet->bLOSIndexX, pBullet->bLOSIndexY, pBullet->ddHorizAngle );
2761 
2762 									if (fResolveHit)
2763 									{
2764 										// the bullet would keep on going!  unless we're considering a knife...
2765 										if (pBullet->usFlags & BULLET_FLAG_KNIFE)
2766 										{
2767 											gubLocalStructureNumTimesHit[iStructureLoop]++;
2768 										}
2769 									}
2770 								}
2771 								else if (pBullet->iLoop > CLOSE_TO_FIRER ||
2772 									(pStructure->fFlags & ALWAYS_CONSIDER_HIT))
2773 								{
2774 									if (pStructure->fFlags & STRUCTURE_WALLSTUFF)
2775 									{
2776 										// possibly shooting at corner in which case we should let it pass
2777 										fResolveHit = ResolveHitOnWall( pStructure, iGridNo, pBullet->bLOSIndexX, pBullet->bLOSIndexY, pBullet->ddHorizAngle );
2778 									}
2779 									else
2780 									{
2781 										fResolveHit = TRUE;
2782 									}
2783 									if (fResolveHit)
2784 									{
2785 										gubLocalStructureNumTimesHit[iStructureLoop]++;
2786 									}
2787 								}
2788 							}
2789 						}
2790 					}
2791 				}
2792 
2793 				// got past everything; go to next LOS location within
2794 				// tile, horizontally or vertically
2795 				bOldLOSIndexX = pBullet->bLOSIndexX;
2796 				bOldLOSIndexY = pBullet->bLOSIndexY;
2797 				iOldCubesZ = pBullet->iCurrCubesZ;
2798 				do
2799 				{
2800 					pBullet->qCurrX += pBullet->qIncrX;
2801 					pBullet->qCurrY += pBullet->qIncrY;
2802 					if (pRoofStructure)
2803 					{
2804 						qLastZ = pBullet->qCurrZ;
2805 						pBullet->qCurrZ += pBullet->qIncrZ;
2806 						if ( (qLastZ > qWallHeight && pBullet->qCurrZ < qWallHeight) || (qLastZ < qWallHeight && pBullet->qCurrZ > qWallHeight))
2807 						{
2808 							// hit roof!
2809 							//pBullet->iImpactReduction += CTGTHandleBulletStructureInteraction( pBullet, pRoofStructure );
2810 							//if (pBullet->iImpactReduction >= pBullet->iImpact)
2811 							{
2812 								return( 0 );
2813 							}
2814 
2815 						}
2816 					}
2817 					else
2818 					{
2819 						pBullet->qCurrZ += pBullet->qIncrZ;
2820 					}
2821 					pBullet->iLoop++;
2822 					pBullet->bLOSIndexX = CONVERT_WITHINTILE_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrX ) % CELL_X_SIZE );
2823 					pBullet->bLOSIndexY = CONVERT_WITHINTILE_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrY ) % CELL_Y_SIZE );
2824 					pBullet->iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrZ ) );
2825 				}
2826 				while( (pBullet->bLOSIndexX == bOldLOSIndexX) && (pBullet->bLOSIndexY == bOldLOSIndexY) && (pBullet->iCurrCubesZ == iOldCubesZ));
2827 
2828 				SLOGD(ST::format("CTGT at {} {} {} after moving in nonempty tile from {} {} {}",
2829 					pBullet->bLOSIndexX, pBullet->bLOSIndexY, pBullet->iCurrCubesZ,
2830 					bOldLOSIndexX, bOldLOSIndexY, iOldCubesZ));
2831 				pBullet->iCurrTileX = FIXEDPT_TO_INT32( pBullet->qCurrX ) / CELL_X_SIZE;
2832 				pBullet->iCurrTileY = FIXEDPT_TO_INT32( pBullet->qCurrY ) / CELL_Y_SIZE;
2833 			}
2834 		} while( (pBullet->iLoop < pBullet->iDistanceLimit) && (pBullet->iCurrTileX == iOldTileX) && (pBullet->iCurrTileY == iOldTileY));
2835 
2836 		if ( pBullet->iCurrTileX < 0 || pBullet->iCurrTileX >= WORLD_COLS || pBullet->iCurrTileY < 0 || pBullet->iCurrTileY >= WORLD_ROWS )
2837 		{
2838 			return( 0 );
2839 		}
2840 
2841 		pBullet->sGridNo = MAPROWCOLTOPOS( pBullet->iCurrTileY , pBullet->iCurrTileX );
2842 
2843 		if (pBullet->iLoop > pBullet->iRange * 2)
2844 		{
2845 			// beyond max effective range, bullet starts to drop!
2846 			// since we're doing an increment based on distance, not time, the
2847 			// decrement is scaled down depending on how fast the bullet is (effective range)
2848 			pBullet->qIncrZ -= INT32_TO_FIXEDPT( 100 ) / (pBullet->iRange * 2);
2849 		}
2850 
2851 		// end of the tile...
2852 		if (iNumLocalStructures > 0)
2853 		{
2854 			for ( iStructureLoop = 0; iStructureLoop < iNumLocalStructures; iStructureLoop++)
2855 			{
2856 				// Calculate the total impact based on the number of points in the structure that were hit
2857 				if (gubLocalStructureNumTimesHit[iStructureLoop] > 0)
2858 				{
2859 					iTotalStructureImpact = CTGTHandleBulletStructureInteraction( pBullet, gpLocalStructure[iStructureLoop] ) * gubLocalStructureNumTimesHit[iStructureLoop];
2860 
2861 					// reduce the impact reduction of a structure tile to that of the bullet, since it can't do MORE than stop it.
2862 					iTotalStructureImpact = __min( iTotalStructureImpact, pBullet->iImpact );
2863 
2864 					// add to "impact reduction" based on strength of structure weighted by probability of hitting it
2865 					pBullet->iImpactReduction += (iTotalStructureImpact * guiLocalStructureCTH[iStructureLoop]) / 100;
2866 				}
2867 			}
2868 			if (pBullet->iImpactReduction >= pBullet->iImpact)
2869 			{
2870 				return( 0 );
2871 			}
2872 		}
2873 	} while( pBullet->iLoop < pBullet->iDistanceLimit );
2874 	// unless the distance is integral, after the loop there will be a
2875 	// fractional amount of distance remaining which is unchecked
2876 	// but we shouldn't(?) need to check it because the target is there!
2877 
2878 	// try simple chance to get through, ignoring range effects
2879 	iChanceToGetThrough = iChanceToGetThrough * (pBullet->iImpact - pBullet->iImpactReduction) / pBullet->iImpact;
2880 
2881 	if (iChanceToGetThrough < 0)
2882 	{
2883 		iChanceToGetThrough = 0;
2884 	}
2885 	return( (UINT8) iChanceToGetThrough );
2886 }
2887 
2888 
2889 static INT8 ChanceToGetThrough(SOLDIERTYPE* pFirer, GridNo end_pos, FLOAT dEndZ);
2890 
2891 
SoldierToSoldierChanceToGetThrough(SOLDIERTYPE * const pStartSoldier,const SOLDIERTYPE * const pEndSoldier)2892 static UINT8 SoldierToSoldierChanceToGetThrough(SOLDIERTYPE* const pStartSoldier, const SOLDIERTYPE* const pEndSoldier)
2893 {
2894 	FLOAT dEndZPos;
2895 	BOOLEAN fOk;
2896 
2897 	if (pStartSoldier == pEndSoldier)
2898 	{
2899 		return( 0 );
2900 	}
2901 	CHECKF( pStartSoldier );
2902 	CHECKF( pEndSoldier );
2903 	fOk = CalculateSoldierZPos( pEndSoldier, TARGET_POS, &dEndZPos );
2904 	if (!fOk)
2905 	{
2906 		return( FALSE );
2907 	}
2908 
2909 	// set startsoldier's target ID ... need an ID stored in case this
2910 	// is the AI calculating cover to a location where he might not be any more
2911 	pStartSoldier->CTGTTarget = pEndSoldier;
2912 	return ChanceToGetThrough(pStartSoldier, pEndSoldier->sGridNo, dEndZPos);
2913 }
2914 
2915 
SoldierToSoldierBodyPartChanceToGetThrough(SOLDIERTYPE * const pStartSoldier,const SOLDIERTYPE * const pEndSoldier,const UINT8 ubAimLocation)2916 UINT8 SoldierToSoldierBodyPartChanceToGetThrough(SOLDIERTYPE* const pStartSoldier, const SOLDIERTYPE* const pEndSoldier, const UINT8 ubAimLocation)
2917 {
2918 	// does like StS-CTGT but with a particular body part in mind
2919 	FLOAT dEndZPos;
2920 	BOOLEAN fOk;
2921 	UINT8 ubPosType;
2922 
2923 	if (pStartSoldier == pEndSoldier)
2924 	{
2925 		return( 0 );
2926 	}
2927 	CHECKF( pStartSoldier );
2928 	CHECKF( pEndSoldier );
2929 	switch( ubAimLocation )
2930 	{
2931 		case AIM_SHOT_HEAD:
2932 			ubPosType = HEAD_TARGET_POS;
2933 			break;
2934 		case AIM_SHOT_TORSO:
2935 			ubPosType = TORSO_TARGET_POS;
2936 			break;
2937 		case AIM_SHOT_LEGS:
2938 			ubPosType = LEGS_TARGET_POS;
2939 			break;
2940 		default:
2941 			ubPosType = TARGET_POS;
2942 			break;
2943 	}
2944 
2945 	fOk = CalculateSoldierZPos( pEndSoldier, ubPosType, &dEndZPos );
2946 	if (!fOk)
2947 	{
2948 		return( FALSE );
2949 	}
2950 
2951 	// set startsoldier's target ID ... need an ID stored in case this
2952 	// is the AI calculating cover to a location where he might not be any more
2953 	pStartSoldier->CTGTTarget = pEndSoldier;
2954 	return ChanceToGetThrough(pStartSoldier, pEndSoldier->sGridNo, dEndZPos);
2955 }
2956 
2957 
SoldierToLocationChanceToGetThrough(SOLDIERTYPE * const pStartSoldier,const INT16 sGridNo,const INT8 bLevel,const INT8 bCubeLevel,const SOLDIERTYPE * const target)2958 UINT8 SoldierToLocationChanceToGetThrough(SOLDIERTYPE* const pStartSoldier, const INT16 sGridNo, const INT8 bLevel, const INT8 bCubeLevel, const SOLDIERTYPE* const target)
2959 {
2960 	FLOAT dEndZPos;
2961 
2962 	if (pStartSoldier->sGridNo == sGridNo)
2963 	{
2964 		return( 0 );
2965 	}
2966 	CHECKF( pStartSoldier );
2967 
2968 	const SOLDIERTYPE* const pEndSoldier = WhoIsThere2(sGridNo, bLevel);
2969 	if (pEndSoldier != NULL)
2970 	{
2971 		return( SoldierToSoldierChanceToGetThrough( pStartSoldier, pEndSoldier ) );
2972 	}
2973 	else
2974 	{
2975 		if (bCubeLevel)
2976 		{
2977 			// fire at the centre of the cube specified
2978 			dEndZPos = ( ( (FLOAT) (bCubeLevel + bLevel * PROFILE_Z_SIZE) ) - 0.5f) * HEIGHT_UNITS_PER_INDEX;
2979 		}
2980 		else
2981 		{
2982 			INT8 const bStructHeight = GetStructureTargetHeight(sGridNo, bLevel == 1);
2983 			if (bStructHeight > 0)
2984 			{
2985 				// fire at the centre of the cube of the tallest structure
2986 				dEndZPos = ((FLOAT) (bStructHeight + bLevel * PROFILE_Z_SIZE) - 0.5f) * HEIGHT_UNITS_PER_INDEX;
2987 			}
2988 			else
2989 			{
2990 				// fire at 1 unit above the level of the ground
2991 				dEndZPos = (FLOAT) ((bLevel * PROFILE_Z_SIZE) * HEIGHT_UNITS_PER_INDEX + 1);
2992 			}
2993 		}
2994 
2995 		dEndZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[sGridNo].sHeight );
2996 
2997 		// set startsoldier's target ID ... need an ID stored in case this
2998 		// is the AI calculating cover to a location where he might not be any more
2999 		pStartSoldier->CTGTTarget = target;
3000 		return ChanceToGetThrough(pStartSoldier, sGridNo, dEndZPos);
3001 	}
3002 }
3003 
3004 
AISoldierToSoldierChanceToGetThrough(SOLDIERTYPE * const pStartSoldier,const SOLDIERTYPE * const pEndSoldier)3005 UINT8 AISoldierToSoldierChanceToGetThrough(SOLDIERTYPE* const pStartSoldier, const SOLDIERTYPE* const pEndSoldier)
3006 {
3007 	// Like a standard CTGT algorithm BUT fakes the start soldier at standing height
3008 	FLOAT dEndZPos;
3009 	BOOLEAN fOk;
3010 	UINT8 ubChance;
3011 	UINT16 usTrueState;
3012 
3013 	if (pStartSoldier == pEndSoldier)
3014 	{
3015 		return( 0 );
3016 	}
3017 	CHECKF( pStartSoldier );
3018 	CHECKF( pEndSoldier );
3019 	fOk = CalculateSoldierZPos( pEndSoldier, TARGET_POS, &dEndZPos );
3020 	if (!fOk)
3021 	{
3022 		return( FALSE );
3023 	}
3024 	usTrueState = pStartSoldier->usAnimState;
3025 	pStartSoldier->usAnimState = STANDING;
3026 
3027 	// set startsoldier's target ID ... need an ID stored in case this
3028 	// is the AI calculating cover to a location where he might not be any more
3029 	pStartSoldier->CTGTTarget = NULL;
3030 
3031 	ubChance = ChanceToGetThrough(pStartSoldier, pEndSoldier->sGridNo, dEndZPos);
3032 	pStartSoldier->usAnimState = usTrueState;
3033 	return( ubChance );
3034 }
3035 
AISoldierToLocationChanceToGetThrough(SOLDIERTYPE * pStartSoldier,INT16 sGridNo,INT8 bLevel,INT8 bCubeLevel)3036 UINT8 AISoldierToLocationChanceToGetThrough( SOLDIERTYPE * pStartSoldier, INT16 sGridNo, INT8 bLevel, INT8 bCubeLevel )
3037 {
3038 	FLOAT dEndZPos;
3039 
3040 	UINT16 usTrueState;
3041 	UINT8 ubChance;
3042 
3043 	if (pStartSoldier->sGridNo == sGridNo)
3044 	{
3045 		return( 0 );
3046 	}
3047 	CHECKF( pStartSoldier );
3048 
3049 	const SOLDIERTYPE* const pEndSoldier = WhoIsThere2(sGridNo, bLevel);
3050 	if (pEndSoldier != NULL)
3051 	{
3052 		return( AISoldierToSoldierChanceToGetThrough( pStartSoldier, pEndSoldier ) );
3053 	}
3054 	else
3055 	{
3056 		if (bCubeLevel)
3057 		{
3058 			// fire at the centre of the cube specified
3059 			dEndZPos = ( (FLOAT) (bCubeLevel + bLevel * PROFILE_Z_SIZE) - 0.5f) * HEIGHT_UNITS_PER_INDEX;
3060 		}
3061 		else
3062 		{
3063 			INT8 const bStructHeight = GetStructureTargetHeight(sGridNo, bLevel == 1);
3064 			if (bStructHeight > 0)
3065 			{
3066 				// fire at the centre of the cube of the tallest structure
3067 				dEndZPos = ((FLOAT) (bStructHeight + bLevel * PROFILE_Z_SIZE) - 0.5f) * HEIGHT_UNITS_PER_INDEX;
3068 			}
3069 			else
3070 			{
3071 				// fire at 1 unit above the level of the ground
3072 				dEndZPos = (FLOAT) ((bLevel * PROFILE_Z_SIZE) * HEIGHT_UNITS_PER_INDEX + 1);
3073 			}
3074 		}
3075 
3076 		dEndZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[sGridNo].sHeight );
3077 
3078 		// set startsoldier's target ID ... need an ID stored in case this
3079 		// is the AI calculating cover to a location where he might not be any more
3080 		pStartSoldier->CTGTTarget = NULL;
3081 
3082 		usTrueState = pStartSoldier->usAnimState;
3083 		pStartSoldier->usAnimState = STANDING;
3084 
3085 		ubChance = ChanceToGetThrough(pStartSoldier, sGridNo, dEndZPos);
3086 
3087 		pStartSoldier->usAnimState = usTrueState;
3088 
3089 		return( ubChance );
3090 	}
3091 }
3092 
3093 
CalculateFiringIncrements(DOUBLE ddHorizAngle,DOUBLE ddVerticAngle,DOUBLE dd2DDistance,BULLET * pBullet,DOUBLE * pddNewHorizAngle,DOUBLE * pddNewVerticAngle)3094 static void CalculateFiringIncrements(DOUBLE ddHorizAngle, DOUBLE ddVerticAngle, DOUBLE dd2DDistance, BULLET* pBullet, DOUBLE* pddNewHorizAngle, DOUBLE* pddNewVerticAngle)
3095 {
3096 	INT32 iMissedBy = - pBullet->sHitBy;
3097 	DOUBLE ddVerticPercentOfMiss;
3098 	DOUBLE ddAbsVerticAngle;
3099 	DOUBLE ddScrewupAdjustmentLimit;
3100 	UINT32 uiChanceOfMissAbove;
3101 	DOUBLE ddMinimumMiss;
3102 	DOUBLE ddMaximumMiss;
3103 	DOUBLE ddAmountOfMiss;
3104 
3105 	if (iMissedBy > 0)
3106 	{
3107 		ddVerticPercentOfMiss = PreRandom( 50 );
3108 
3109 		ddAbsVerticAngle = ddVerticAngle;
3110 		if (ddAbsVerticAngle < 0)
3111 		{
3112 			ddAbsVerticAngle *= -1.0;
3113 		}
3114 
3115 		// chance of shooting over target is 60 for horizontal shots, up to 80% for shots at 22.5 degrees,
3116 		// and then down again to 50% for shots at 45+%.
3117 		if (ddAbsVerticAngle < DEGREES_22_5)
3118 		{
3119 			uiChanceOfMissAbove = 60 + (INT32) (20 * (ddAbsVerticAngle) / DEGREES_22_5);
3120 		}
3121 		else if (ddAbsVerticAngle < DEGREES_45)
3122 		{
3123 			uiChanceOfMissAbove = 80 - (INT32) (30.0 * (ddAbsVerticAngle - DEGREES_22_5) / DEGREES_22_5);
3124 		}
3125 		else
3126 		{
3127 			uiChanceOfMissAbove = 50;
3128 		}
3129 		// figure out change in horizontal and vertical angle due to shooter screwup
3130 		// the more the screwup, the greater the angle;
3131 		// for the look of things, the further away, reduce the angle a bit.
3132 		ddScrewupAdjustmentLimit = (dd2DDistance / CELL_X_SIZE) / 200;
3133 		if (ddScrewupAdjustmentLimit > MAX_AIMING_SCREWUP / 2)
3134 		{
3135 			ddScrewupAdjustmentLimit = MAX_AIMING_SCREWUP / 2;
3136 		}
3137 		ddMaximumMiss = MAX_AIMING_SCREWUP - ddScrewupAdjustmentLimit;
3138 
3139 		// Want to make sure that not too many misses actually hit the target after all
3140 		// to miss a target at 1 tile is about 30 degrees off, at 5 tiles, 6 degrees off
3141 		// at 15 tiles, 2 degrees off.  Thus 30 degrees divided by the # of tiles distance.
3142 		ddMinimumMiss = MIN_AIMING_SCREWUP / (dd2DDistance / CELL_X_SIZE );
3143 
3144 		if (ddMinimumMiss > ddMaximumMiss)
3145 		{
3146 			ddMinimumMiss = ddMaximumMiss;
3147 		}
3148 
3149 		ddAmountOfMiss = ( (ddMaximumMiss - ddMinimumMiss) * ((DOUBLE) iMissedBy)) / 100.0 + ddMinimumMiss;
3150 
3151 		// miss to the left or right
3152 		if (PreRandom( 2 ) )
3153 		{
3154 			ddHorizAngle += ddAmountOfMiss * (100.0 - ddVerticPercentOfMiss) / 100.0;
3155 		}
3156 		else
3157 		{
3158 			ddHorizAngle -= ddAmountOfMiss * (100.0 - ddVerticPercentOfMiss) / 100.0;
3159 		}
3160 
3161 		// miss up or down
3162 		if (PreRandom( 100 ) < uiChanceOfMissAbove)
3163 		{
3164 			ddVerticAngle += ddAmountOfMiss * ddVerticPercentOfMiss / 100.0;
3165 		}
3166 		else
3167 		{
3168 			ddVerticAngle -= ddAmountOfMiss * ddVerticPercentOfMiss / 100.0;
3169 		}
3170 	}
3171 
3172 	*pddNewHorizAngle = ddHorizAngle;
3173 	*pddNewVerticAngle = ddVerticAngle;
3174 
3175 	pBullet->qIncrX = FloatToFixed( (FLOAT) cos( ddHorizAngle ) );
3176 	pBullet->qIncrY = FloatToFixed( (FLOAT) sin( ddHorizAngle ) );
3177 
3178 	// this is the same as multiplying the X and Y increments by the projection of the line in
3179 	// 3-space onto the horizontal plane, without reducing the X/Y increments and thus slowing
3180 	// the LOS code
3181 	pBullet->qIncrZ = FloatToFixed( (FLOAT) ( sin( ddVerticAngle ) / sin( (PI/2) - ddVerticAngle ) * 2.56 ) );
3182 }
3183 
3184 
FireBullet(BULLET * pBullet,BOOLEAN fFake)3185 static INT8 FireBullet(BULLET* pBullet, BOOLEAN fFake)
3186 {
3187 	pBullet->iCurrTileX = FIXEDPT_TO_INT32( pBullet->qCurrX ) / CELL_X_SIZE;
3188 	pBullet->iCurrTileY = FIXEDPT_TO_INT32( pBullet->qCurrY ) / CELL_Y_SIZE;
3189 	pBullet->bLOSIndexX = CONVERT_WITHINTILE_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrX ) % CELL_X_SIZE );
3190 	pBullet->bLOSIndexY = CONVERT_WITHINTILE_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrY ) % CELL_Y_SIZE );
3191 	pBullet->iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrZ ) );
3192 	pBullet->iLoop = 1;
3193 	pBullet->iImpactReduction = 0;
3194 	pBullet->sGridNo = MAPROWCOLTOPOS( pBullet->iCurrTileY, pBullet->iCurrTileX );
3195 	SOLDIERTYPE* const pFirer = pBullet->pFirer;
3196 	if (fFake)
3197 	{
3198 		pBullet->target = pFirer->CTGTTarget;
3199 		return( CalcChanceToGetThrough( pBullet ) );
3200 	}
3201 	else
3202 	{
3203 		pBullet->target = pFirer->target;
3204 		//if ( gGameSettings.fOptions[ TOPTION_HIDE_BULLETS ] )
3205 		//{
3206 		//	pBullet->uiLastUpdate = 0;
3207 		//	pBullet->ubTilesPerUpdate	= 4;
3208 		//}
3209 		//else
3210 		//{
3211 			pBullet->uiLastUpdate = 0;
3212 			pBullet->ubTilesPerUpdate	= 1;
3213 		//}
3214 
3215 		// increment shots fired if shooter has a merc profile
3216 		if( ( pFirer->ubProfile != NO_PROFILE ) && ( pFirer->bTeam == 0 ) )
3217 		{
3218 			if (pFirer->target)
3219 			{
3220 				// Do not count shots fired at nothing in particular because it skews hit %. It would always counts as a miss
3221 				if (pFirer->target->bLife > 0) gMercProfiles[pFirer->ubProfile].usShotsFired++;
3222 			}
3223 		}
3224 
3225 		if ( GCM->getItem(pFirer->usAttackingWeapon)->getItemClass() == IC_THROWING_KNIFE )
3226 		{
3227 			pBullet->usClockTicksPerUpdate = 30;
3228 		}
3229 		else
3230 		{
3231 			pBullet->usClockTicksPerUpdate = GCM->getWeapon( pFirer->usAttackingWeapon )->ubBulletSpeed / 10;
3232 		}
3233 
3234 		HandleBulletSpecialFlags(pBullet);
3235 
3236 		MoveBullet(pBullet);
3237 
3238 		return( TRUE );
3239 	}
3240 }
3241 
3242 
FireBulletGivenTarget(SOLDIERTYPE * const pFirer,const FLOAT dEndX,const FLOAT dEndY,const FLOAT dEndZ,const UINT16 usHandItem,INT16 sHitBy,const BOOLEAN fBuckshot,const BOOLEAN fFake)3243 INT8 FireBulletGivenTarget(SOLDIERTYPE* const pFirer, const FLOAT dEndX, const FLOAT dEndY, const FLOAT dEndZ, const UINT16 usHandItem, INT16 sHitBy, const BOOLEAN fBuckshot, const BOOLEAN fFake)
3244 {
3245 	// fFake indicates that we should set things up for a call to ChanceToGetThrough
3246 	FLOAT dStartZ;
3247 
3248 	FLOAT d2DDistance;
3249 	FLOAT dDeltaX;
3250 	FLOAT dDeltaY;
3251 	FLOAT dDeltaZ;
3252 
3253 	FLOAT dStartX;
3254 	FLOAT dStartY;
3255 
3256 	DOUBLE ddOrigHorizAngle;
3257 	DOUBLE ddOrigVerticAngle;
3258 	DOUBLE ddHorizAngle;
3259 	DOUBLE ddVerticAngle;
3260 	DOUBLE ddAdjustedHorizAngle;
3261 	DOUBLE ddAdjustedVerticAngle;
3262 	DOUBLE ddDummyHorizAngle;
3263 	DOUBLE ddDummyVerticAngle;
3264 
3265 	INT32 iDistance;
3266 
3267 	UINT8 ubLoop;
3268 	UINT8 ubShots;
3269 	UINT8 ubImpact;
3270 	INT8 bCTGT;
3271 	UINT8 ubSpreadIndex = 0;
3272 	UINT16 usBulletFlags = 0;
3273 
3274 	CalculateSoldierZPos( pFirer, FIRING_POS, &dStartZ );
3275 
3276 	dStartX = (FLOAT) CenterX( pFirer->sGridNo );
3277 	dStartY = (FLOAT) CenterY( pFirer->sGridNo );
3278 
3279 	dDeltaX = dEndX - dStartX;
3280 	dDeltaY = dEndY - dStartY;
3281 	dDeltaZ = dEndZ - dStartZ;
3282 
3283 	d2DDistance = Distance2D( dDeltaX, dDeltaY );
3284 	iDistance = (INT32) d2DDistance;
3285 
3286 	if ( d2DDistance != iDistance )
3287 	{
3288 		iDistance += 1;
3289 		d2DDistance = (FLOAT) ( iDistance);
3290 	}
3291 
3292 	ddOrigHorizAngle = atan2( dDeltaY, dDeltaX );
3293 	ddOrigVerticAngle = atan2( dDeltaZ, (d2DDistance * 2.56f) );
3294 
3295 	ubShots = 1;
3296 
3297 	// Check if we have spit as a weapon!
3298 	if (GCM->getWeapon( usHandItem )->calibre->monsterWeapon)
3299 	{
3300 		usBulletFlags |= BULLET_FLAG_CREATURE_SPIT;
3301 	}
3302 	else if (GCM->getItem(usHandItem)->getItemClass() == IC_THROWING_KNIFE)
3303 	{
3304 		usBulletFlags |= BULLET_FLAG_KNIFE;
3305 		if (GCM->getItem(usHandItem)->getItemIndex() == BLOODY_THROWING_KNIFE)
3306 			usBulletFlags |= BULLET_FLAG_BLOODY;
3307 	}
3308 	else if (usHandItem == ROCKET_LAUNCHER)
3309 	{
3310 		usBulletFlags |= BULLET_FLAG_MISSILE;
3311 	}
3312 	else if (usHandItem == TANK_CANNON)
3313 	{
3314 		usBulletFlags |= BULLET_FLAG_TANK_CANNON;
3315 	}
3316 	else if (usHandItem == ROCKET_RIFLE || usHandItem == AUTO_ROCKET_RIFLE)
3317 	{
3318 		usBulletFlags |= BULLET_FLAG_SMALL_MISSILE;
3319 	}
3320 	else if (usHandItem == FLAMETHROWER)
3321 	{
3322 		usBulletFlags |= BULLET_FLAG_FLAME;
3323 		ubSpreadIndex = 2;
3324 	}
3325 
3326 	ubImpact = GCM->getWeapon(usHandItem)->ubImpact;
3327 	//if (!fFake)
3328 	{
3329 		if (fBuckshot)
3330 		{
3331 			// shotgun pellets fire 9 bullets doing 1/4 damage each
3332 			if (!fFake)
3333 			{
3334 				ubShots = BUCKSHOT_SHOTS;
3335 				// but you can't really aim the damn things very well!
3336 				if (sHitBy > 0)
3337 				{
3338 					sHitBy = sHitBy / 2;
3339 				}
3340 				if (FindAttachment( &(pFirer->inv[pFirer->ubAttackingHand]), DUCKBILL ) != NO_SLOT)
3341 				{
3342 					ubSpreadIndex = 1;
3343 				}
3344 				if (pFirer->target != NULL) pFirer->target->bNumPelletsHitBy = 0;
3345 				usBulletFlags |= BULLET_FLAG_BUCKSHOT;
3346 			}
3347 			ubImpact = AMMO_DAMAGE_ADJUSTMENT_BUCKSHOT( ubImpact );
3348 		}
3349 	}
3350 
3351 	// GET BULLET
3352 	for (ubLoop = 0; ubLoop < ubShots; ubLoop++)
3353 	{
3354 		BULLET* const pBullet = CreateBullet(pFirer, fFake, usBulletFlags);
3355 		if (pBullet == NULL)
3356 		{
3357 			SLOGW("Failed to create bullet");
3358 			return FALSE;
3359 		}
3360 		pBullet->sHitBy	= sHitBy;
3361 
3362 		if (dStartZ < WALL_HEIGHT_UNITS)
3363 		{
3364 			if (dEndZ > WALL_HEIGHT_UNITS)
3365 			{
3366 				pBullet->fCheckForRoof = TRUE;
3367 			}
3368 			else
3369 			{
3370 				pBullet->fCheckForRoof = FALSE;
3371 			}
3372 		}
3373 		else // dStartZ >= WALL_HEIGHT_UNITS; presumably >
3374 		{
3375 			if (dEndZ < WALL_HEIGHT_UNITS)
3376 			{
3377 				pBullet->fCheckForRoof = TRUE;
3378 			}
3379 			else
3380 			{
3381 				pBullet->fCheckForRoof = FALSE;
3382 			}
3383 		}
3384 
3385 		if ( ubLoop == 0 )
3386 		{
3387 			ddHorizAngle = ddOrigHorizAngle;
3388 			ddVerticAngle = ddOrigVerticAngle;
3389 
3390 			// first bullet, roll to hit...
3391 			if ( sHitBy >= 0 )
3392 			{
3393 				// calculate by hand (well, without angles) to match LOS
3394 				pBullet->qIncrX = FloatToFixed( dDeltaX / (FLOAT)iDistance );
3395 				pBullet->qIncrY = FloatToFixed( dDeltaY / (FLOAT)iDistance );
3396 				pBullet->qIncrZ = FloatToFixed( dDeltaZ / (FLOAT)iDistance );
3397 				ddAdjustedHorizAngle = ddHorizAngle;
3398 				ddAdjustedVerticAngle = ddVerticAngle;
3399 			}
3400 			else
3401 			{
3402 				CalculateFiringIncrements( ddHorizAngle, ddVerticAngle, d2DDistance, pBullet, &ddAdjustedHorizAngle, &ddAdjustedVerticAngle );
3403 			}
3404 		}
3405 		else
3406 		{
3407 			// temporarily set bullet's sHitBy value to 0 to get unadjusted angles
3408 			pBullet->sHitBy = 0;
3409 
3410 			ddHorizAngle = ddAdjustedHorizAngle + ddShotgunSpread[ubSpreadIndex][ubLoop][0];
3411 			ddVerticAngle = ddAdjustedVerticAngle + ddShotgunSpread[ubSpreadIndex][ubLoop][1];
3412 
3413 			CalculateFiringIncrements( ddHorizAngle, ddVerticAngle, d2DDistance, pBullet, &ddDummyHorizAngle, &ddDummyVerticAngle );
3414 			pBullet->sHitBy = sHitBy;
3415 		}
3416 
3417 
3418 		pBullet->ddHorizAngle = ddHorizAngle;
3419 
3420 		if (ubLoop == 0 && pFirer->bDoBurst < 2)
3421 		{
3422 			pBullet->fAimed = TRUE;
3423 		}
3424 		else
3425 		{
3426 			// buckshot pellets after the first can hit friendlies even at close range
3427 			pBullet->fAimed = FALSE;
3428 		}
3429 
3430 		if ( pBullet->usFlags & BULLET_FLAG_KNIFE )
3431 		{
3432 			pBullet->ubItemStatus = pFirer->inv[pFirer->ubAttackingHand].bStatus[0];
3433 		}
3434 
3435 		// apply increments for first move
3436 
3437 		pBullet->qCurrX = FloatToFixed( dStartX ) + pBullet->qIncrX;
3438 		pBullet->qCurrY = FloatToFixed( dStartY ) + pBullet->qIncrY;
3439 		pBullet->qCurrZ = FloatToFixed( dStartZ ) + pBullet->qIncrZ;
3440 
3441 		// NB we can only apply correction for leftovers if the bullet is going to hit
3442 		// because otherwise the increments are not right for the calculations!
3443 		if ( pBullet->sHitBy >= 0 )
3444 		{
3445 			pBullet->qCurrX += ( FloatToFixed( dDeltaX ) - pBullet->qIncrX * iDistance ) / 2;
3446 			pBullet->qCurrY += ( FloatToFixed( dDeltaY ) - pBullet->qIncrY * iDistance ) / 2;
3447 			pBullet->qCurrZ += ( FloatToFixed( dDeltaZ ) - pBullet->qIncrZ * iDistance ) / 2;
3448 		}
3449 
3450 		pBullet->iImpact = ubImpact;
3451 
3452 		pBullet->iRange = GunRange(pFirer->inv[pFirer->ubAttackingHand]);
3453 		pBullet->sTargetGridNo = ((INT32)dEndX) / CELL_X_SIZE + ((INT32)dEndY) / CELL_Y_SIZE * WORLD_COLS;
3454 
3455 		pBullet->bStartCubesAboveLevelZ = (INT8) CONVERT_HEIGHTUNITS_TO_INDEX( (INT32)dStartZ - CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ pFirer->sGridNo ].sHeight ) );
3456 		pBullet->bEndCubesAboveLevelZ = (INT8) CONVERT_HEIGHTUNITS_TO_INDEX( (INT32)dEndZ - CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ pBullet->sTargetGridNo ].sHeight ) );
3457 
3458 		// this distance limit only applies in a "hard" sense to fake bullets for chance-to-get-through,
3459 		// but is used for determining structure hits by the regular code
3460 		pBullet->iDistanceLimit = iDistance;
3461 		if (fFake)
3462 		{
3463 			bCTGT = FireBullet(pBullet, TRUE);
3464 			RemoveBullet(pBullet);
3465 			return( bCTGT );
3466 		}
3467 		else
3468 		{
3469 			FireBullet(pBullet, FALSE);
3470 		}
3471 	}
3472 
3473 	return( TRUE );
3474 }
3475 
3476 
ChanceToGetThrough(SOLDIERTYPE * const pFirer,const GridNo end_pos,const FLOAT dEndZ)3477 static INT8 ChanceToGetThrough(SOLDIERTYPE* const pFirer, const GridNo end_pos, const FLOAT dEndZ)
3478 {
3479 	UINT16  weapon = pFirer->usAttackingWeapon;
3480 	BOOLEAN buck_shot;
3481 	if (GCM->getItem(weapon)->getItemClass() == IC_GUN ||
3482 		GCM->getItem(weapon)->getItemClass() == IC_THROWING_KNIFE)
3483 	{
3484 		// if shotgun, shotgun would have to be in main hand
3485 		buck_shot = pFirer->inv[HANDPOS].usItem        == weapon &&
3486 				pFirer->inv[HANDPOS].ubGunAmmoType == AMMO_BUCKSHOT;
3487 	}
3488 	else
3489 	{
3490 		// fake it
3491 		weapon    = GLOCK_17;
3492 		buck_shot = FALSE;
3493 	}
3494 
3495 	INT16 end_x;
3496 	INT16 end_y;
3497 	ConvertGridNoToCenterCellXY(end_pos, &end_x, &end_y);
3498 	return FireBulletGivenTarget(pFirer, end_x, end_y, dEndZ, weapon, 0, buck_shot, TRUE);
3499 }
3500 
3501 
MoveBullet(BULLET * const pBullet)3502 void MoveBullet(BULLET* const pBullet)
3503 {
3504 	FIXEDPT qLandHeight;
3505 	INT32 iCurrAboveLevelZ;
3506 	INT32 iCurrCubesAboveLevelZ;
3507 	INT16 sDesiredLevel;
3508 
3509 	INT32 iOldTileX;
3510 	INT32 iOldTileY;
3511 	INT32 iOldCubesZ;
3512 
3513 	MAP_ELEMENT * pMapElement;
3514 	STRUCTURE * pStructure;
3515 	STRUCTURE * pRoofStructure = NULL;
3516 
3517 	FIXEDPT qLastZ;
3518 
3519 	BOOLEAN fIntended;
3520 	BOOLEAN fStopped;
3521 	INT8 bOldLOSIndexX;
3522 	INT8 bOldLOSIndexY;
3523 
3524 	UINT32 uiTileInc = 0;
3525 	UINT32 uiTime;
3526 
3527 	INT8 bDir;
3528 	INT32 iGridNo, iAdjGridNo;
3529 
3530 	INT32 iRemainingImpact;
3531 
3532 	FIXEDPT qDistToTravelX;
3533 	FIXEDPT qDistToTravelY;
3534 	INT32 iStepsToTravelX;
3535 	INT32 iStepsToTravelY;
3536 	INT32 iStepsToTravel;
3537 
3538 	INT32 iNumLocalStructures;
3539 	INT32 iStructureLoop;
3540 	UINT32 uiChanceOfHit;
3541 
3542 	BOOLEAN fResolveHit;
3543 
3544 	INT32 i;
3545 	BOOLEAN fGoingOver = FALSE;
3546 	BOOLEAN fHitStructure;
3547 
3548 	FIXEDPT qWallHeight;
3549 	FIXEDPT qWindowBottomHeight;
3550 	FIXEDPT qWindowTopHeight;
3551 
3552 	// CHECK MIN TIME ELAPSED
3553 	uiTime = GetJA2Clock( );
3554 
3555 	if ( ( uiTime - pBullet->uiLastUpdate ) < pBullet->usClockTicksPerUpdate )
3556 	{
3557 		return;
3558 	}
3559 
3560 	pBullet->uiLastUpdate = uiTime;
3561 
3562 	do
3563 	{
3564 		// check a particular tile
3565 		// retrieve values from world for this particular tile
3566 		iGridNo = pBullet->iCurrTileX + pBullet->iCurrTileY * WORLD_COLS;
3567 		if (!GridNoOnVisibleWorldTile( (INT16) iGridNo ) || (pBullet->iCurrCubesZ > PROFILE_Z_SIZE * 2 && FIXEDPT_TO_INT32( pBullet->qIncrZ ) > 0 ) )
3568 		{
3569 			// bullet outside of world!
3570 			// NB remove bullet only flags a bullet for deletion; we still have access to the
3571 			// information in the structure
3572 			RemoveBullet(pBullet);
3573 			ShotMiss(pBullet);
3574 			return;
3575 		}
3576 
3577 		pMapElement = &(gpWorldLevelData[ iGridNo ]);
3578 		qLandHeight = INT32_TO_FIXEDPT( CONVERT_PIXELS_TO_HEIGHTUNITS( pMapElement->sHeight ) );
3579 		qWallHeight = gqStandardWallHeight + qLandHeight;
3580 		qWindowBottomHeight = gqStandardWindowBottomHeight + qLandHeight;
3581 		qWindowTopHeight = gqStandardWindowTopHeight + qLandHeight;
3582 
3583 
3584 		// calculate which level bullet is on for suppression and close call purposes
3585 		// figure out the LOS cube level of the current point
3586 		iCurrCubesAboveLevelZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrZ - qLandHeight) );
3587 		// figure out the level
3588 		if (iCurrCubesAboveLevelZ < STRUCTURE_ON_GROUND_MAX)
3589 		{
3590 			// check objects on the ground
3591 			sDesiredLevel = 0;
3592 		}
3593 		else
3594 		{
3595 			// check objects on roofs
3596 			sDesiredLevel = 1;
3597 		}
3598 
3599 		// assemble list of structures we might hit!
3600 		iNumLocalStructures = 0;
3601 		pStructure = pMapElement->pStructureHead;
3602 		// calculate chance of hitting each structure
3603 		uiChanceOfHit = ChanceOfBulletHittingStructure( pBullet->iLoop, pBullet->iDistanceLimit, pBullet->sHitBy );
3604 		if (iGridNo == (INT32) pBullet->sTargetGridNo)
3605 		{
3606 			fIntended = TRUE;
3607 			// if in the same tile as our destination, we WANT to hit the structure!
3608 			if ( fIntended )
3609 			{
3610 				uiChanceOfHit = 100;
3611 			}
3612 		}
3613 		else
3614 		{
3615 			fIntended = FALSE;
3616 		}
3617 
3618 		while( pStructure )
3619 		{
3620 			if (pStructure->fFlags & ALWAYS_CONSIDER_HIT)
3621 			{
3622 				// ALWAYS add walls
3623 				// fence is special
3624 				if ( pStructure->fFlags & STRUCTURE_ANYFENCE )
3625 				{
3626 					// If the density of the fence is less than 100%, or this is the top of the fence, then roll the dice
3627 					// NB cubes are 0 based, heights 1 based
3628 					if ( pStructure->pDBStructureRef->pDBStructure->ubDensity < 100 )
3629 					{
3630 						// requires roll
3631 						if ( PreRandom( 100 ) < uiChanceOfHit )
3632 						{
3633 							gpLocalStructure[iNumLocalStructures] = pStructure;
3634 							gubLocalStructureNumTimesHit[iNumLocalStructures] = 0;
3635 							iNumLocalStructures++;
3636 						}
3637 					}
3638 					else if ( (pBullet->iLoop <= CLOSE_TO_FIRER) && (iCurrCubesAboveLevelZ <= pBullet->bStartCubesAboveLevelZ) && (pBullet->bEndCubesAboveLevelZ >= iCurrCubesAboveLevelZ) && iCurrCubesAboveLevelZ == (StructureHeight( pStructure ) - 1) )
3639 					{
3640 						// near firer and at top of structure and at same level as bullet's start
3641 						// requires roll
3642 						if ( PreRandom( 100 ) < uiChanceOfHit )
3643 						{
3644 							gpLocalStructure[iNumLocalStructures] = pStructure;
3645 							gubLocalStructureNumTimesHit[iNumLocalStructures] = 0;
3646 							iNumLocalStructures++;
3647 						}
3648 					}
3649 					else if ( (pBullet->iDistanceLimit - pBullet->iLoop <= CLOSE_TO_FIRER) && (iCurrCubesAboveLevelZ <= pBullet->bEndCubesAboveLevelZ) && iCurrCubesAboveLevelZ == (StructureHeight( pStructure ) - 1) )
3650 					{
3651 						// near target and at top of structure and at same level as bullet's end
3652 						// requires roll
3653 						if ( PreRandom( 100 ) < uiChanceOfHit )
3654 						{
3655 							gpLocalStructure[iNumLocalStructures] = pStructure;
3656 							gubLocalStructureNumTimesHit[iNumLocalStructures] = 0;
3657 							iNumLocalStructures++;
3658 						}
3659 					}
3660 					else
3661 					{
3662 						// always add
3663 						gpLocalStructure[iNumLocalStructures] = pStructure;
3664 						gubLocalStructureNumTimesHit[iNumLocalStructures] = 0;
3665 						iNumLocalStructures++;
3666 					}
3667 
3668 					/*
3669 					if ( !( (pStructure->pDBStructureRef->pDBStructure->ubDensity < 100 || iCurrCubesAboveLevelZ == (StructureHeight( pStructure ) - 1) )	) && (PreRandom( 100 ) >= uiChanceOfHit) )
3670 					{
3671 						gpLocalStructure[iNumLocalStructures] = pStructure;
3672 						gubLocalStructureNumTimesHit[iNumLocalStructures] = 0;
3673 						iNumLocalStructures++;
3674 					}*/
3675 				}
3676 				else
3677 				{
3678 					gpLocalStructure[iNumLocalStructures] = pStructure;
3679 					gubLocalStructureNumTimesHit[iNumLocalStructures] = 0;
3680 					iNumLocalStructures++;
3681 				}
3682 			}
3683 			else if (pStructure->fFlags & STRUCTURE_ROOF)
3684 			{
3685 				// only consider roofs if the flag is set; don't add them to the array since they
3686 				// are a special case
3687 				if (pBullet->fCheckForRoof)
3688 				{
3689 					pRoofStructure = pStructure;
3690 
3691 					qLastZ = pBullet->qCurrZ - pBullet->qIncrZ;
3692 
3693 					// if just on going to next tile we cross boundary, then roof stops bullet here!
3694 					if ((qLastZ > qWallHeight && pBullet->qCurrZ <= qWallHeight) ||
3695 						(qLastZ < qWallHeight && pBullet->qCurrZ >= qWallHeight))
3696 					{
3697 						// hit a roof
3698 						StopBullet(pBullet);
3699 						BulletHitStructure(pBullet, 0, 0, TRUE);
3700 						return;
3701 					}
3702 
3703 				}
3704 			}
3705 			else if (pStructure->fFlags & STRUCTURE_PERSON)
3706 			{
3707 				SOLDIERTYPE& tgt = GetMan(pStructure->usStructureID);
3708 				if (&tgt != pBullet->pFirer)
3709 				{
3710 					// in actually moving the bullet, we consider only count friends as targets if the bullet is unaimed
3711 					// (buckshot), if they are the intended target, or beyond the range of automatic friendly fire hits
3712 					// OR a 1 in 30 chance occurs
3713 
3714 
3715 					// ignore *intervening* target if not visible; PCs are always visible so AI will never skip them on that
3716 					// basis
3717 					if (fIntended)
3718 					{
3719 						// could hit this person!
3720 						gpLocalStructure[iNumLocalStructures] = pStructure;
3721 						iNumLocalStructures++;
3722 					}
3723 					else if ( pBullet->pFirer->uiStatusFlags & SOLDIER_MONSTER )
3724 					{
3725 						// monsters firing will always accidentally hit people but never accidentally hit each other.
3726 						if (!(tgt.uiStatusFlags & SOLDIER_MONSTER))
3727 						{
3728 							gpLocalStructure[iNumLocalStructures] = pStructure;
3729 							iNumLocalStructures++;
3730 						}
3731 					}
3732 					else if (tgt.bVisible == TRUE &&
3733 						gAnimControl[tgt.usAnimState].ubEndHeight == ANIM_STAND &&
3734 						((pBullet->fAimed && pBullet->iLoop > MIN_DIST_FOR_HIT_FRIENDS) ||
3735 						(!pBullet->fAimed && pBullet->iLoop > MIN_DIST_FOR_HIT_FRIENDS_UNAIMED) ||
3736 						PreRandom( 100 ) < MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE))
3737 					{
3738 						// could hit this person!
3739 						gpLocalStructure[iNumLocalStructures] = pStructure;
3740 						iNumLocalStructures++;
3741 					}
3742 
3743 					// this might be a close call
3744 					if (tgt.bTeam == OUR_TEAM && pBullet->pFirer->bTeam != OUR_TEAM && sDesiredLevel == tgt.bLevel)
3745 					{
3746 						tgt.fCloseCall = TRUE;
3747 					}
3748 
3749 					if (IS_MERC_BODY_TYPE(&tgt))
3750 					{
3751 						// apply suppression, regardless of friendly or enemy
3752 						// except if friendly, not within a few tiles of shooter
3753 						if (tgt.bSide != pBullet->pFirer->bSide || pBullet->iLoop > MIN_DIST_FOR_HIT_FRIENDS)
3754 						{
3755 							// buckshot has only a 1 in 2 chance of applying a suppression point
3756 							if ( !(pBullet->usFlags & BULLET_FLAG_BUCKSHOT) || Random( 2 ) )
3757 							{
3758 								// bullet goes whizzing by this guy!
3759 								switch (gAnimControl[tgt.usAnimState].ubEndHeight)
3760 								{
3761 									case ANIM_PRONE:
3762 										// two 1/4 chances of avoiding suppression pt - one below
3763 										if (PreRandom( 4 ) == 0)
3764 										{
3765 											break;
3766 										}
3767 										// else fall through
3768 									case ANIM_CROUCH:
3769 										// 1/4 chance of avoiding suppression pt
3770 										if (PreRandom( 4 ) == 0)
3771 										{
3772 											break;
3773 										}
3774 										// else fall through
3775 									default:
3776 										tgt.ubSuppressionPoints++;
3777 										tgt.suppressor = pBullet->pFirer;
3778 										break;
3779 								}
3780 							}
3781 						}
3782 					}
3783 				}
3784 
3785 			}
3786 			else if ( pStructure->fFlags & STRUCTURE_CORPSE )
3787 			{
3788 				if (iGridNo == (INT32) pBullet->sTargetGridNo ||
3789 					(pStructure->pDBStructureRef->pDBStructure->ubNumberOfTiles >= 10))
3790 				{
3791 					// could hit this corpse!
3792 					// but ignore if someone is here
3793 					if ( FindStructure( (INT16) iGridNo, STRUCTURE_PERSON ) == NULL )
3794 					{
3795 						gpLocalStructure[iNumLocalStructures] = pStructure;
3796 						iNumLocalStructures++;
3797 					}
3798 				}
3799 			}
3800 			else
3801 			{
3802 				if (pBullet->iLoop > CLOSE_TO_FIRER || ( fIntended ) )
3803 				{
3804 					// calculate chance of hitting structure
3805 					if (PreRandom( 100 ) < uiChanceOfHit)
3806 					{
3807 						// could hit it
3808 						gpLocalStructure[iNumLocalStructures] = pStructure;
3809 						gubLocalStructureNumTimesHit[iNumLocalStructures] = 0;
3810 						iNumLocalStructures++;
3811 					}
3812 				}
3813 			}
3814 			pStructure = pStructure->pNext;
3815 		}
3816 
3817 		// check to see if any soldiers are nearby; those soldiers
3818 		// have their near-miss value incremented
3819 		if (pMapElement->ubAdjacentSoldierCnt > 0)
3820 		{
3821 			// cube level now calculated above!
3822 			// figure out the LOS cube level of the current point
3823 			//iCurrCubesAboveLevelZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrZ - qLandHeight) );
3824 			// figure out what level to affect...
3825 			if (iCurrCubesAboveLevelZ < STRUCTURE_ON_ROOF_MAX)
3826 			{
3827 				/*
3828 				if (iCurrCubesAboveLevelZ < STRUCTURE_ON_GROUND_MAX)
3829 				{
3830 					// check objects on the ground
3831 					sDesiredLevel = 0;
3832 				}
3833 				else
3834 				{
3835 					// check objects on roofs
3836 					sDesiredLevel = 1;
3837 				}*/
3838 
3839 				for( bDir = 0; bDir < NUM_WORLD_DIRECTIONS; bDir++)
3840 				{
3841 					iAdjGridNo = iGridNo + DirIncrementer[bDir];
3842 
3843 					if ( gubWorldMovementCosts[ iAdjGridNo ][ bDir ][ sDesiredLevel ] < TRAVELCOST_BLOCKED)
3844 					{
3845 						SOLDIERTYPE* const pTarget = WhoIsThere2(iAdjGridNo, sDesiredLevel);
3846 						if (pTarget != NULL)
3847 						{
3848 							if ( IS_MERC_BODY_TYPE( pTarget ) && pBullet->pFirer->bSide != pTarget->bSide )
3849 							{
3850 								if ( !(pBullet->usFlags & BULLET_FLAG_BUCKSHOT) || Random( 2 ) )
3851 								{
3852 									// bullet goes whizzing by this guy!
3853 									switch ( gAnimControl[ pTarget->usAnimState ].ubEndHeight )
3854 									{
3855 										case ANIM_PRONE:
3856 											// two 1/4 chances of avoiding suppression pt - one below
3857 											if (PreRandom( 4 ) == 0)
3858 											{
3859 												break;
3860 											}
3861 											// else fall through
3862 										case ANIM_CROUCH:
3863 											// 1/4 chance of avoiding suppression pt
3864 											if (PreRandom( 4 ) == 0)
3865 											{
3866 												break;
3867 											}
3868 											// else fall through
3869 										default:
3870 											pTarget->ubSuppressionPoints++;
3871 											pTarget->suppressor = pBullet->pFirer;
3872 											break;
3873 									}
3874 								}
3875 
3876 								/*
3877 								// this could be a close call
3878 								if ( pTarget->bTeam == OUR_TEAM && pBullet->pFirer->bTeam != OUR_TEAM )
3879 								{
3880 									pTarget->fCloseCall = TRUE;
3881 								}*/
3882 
3883 							}
3884 						}
3885 					}
3886 				}
3887 			}
3888 		}
3889 
3890 		// record old tile location for loop purposes
3891 		iOldTileX = pBullet->iCurrTileX;
3892 		iOldTileY = pBullet->iCurrTileY;
3893 
3894 		do
3895 		{
3896 			// check a particular location within the tile
3897 
3898 			// check for collision with the ground
3899 			iCurrAboveLevelZ = FIXEDPT_TO_INT32( pBullet->qCurrZ - qLandHeight );
3900 			if (iCurrAboveLevelZ < 0)
3901 			{
3902 				// ground is in the way!
3903 				StopBullet(pBullet);
3904 				BulletHitStructure(pBullet, INVALID_STRUCTURE_ID, 0, TRUE);
3905 				return;
3906 			}
3907 			// check for the existence of structures
3908 			if (iNumLocalStructures == 0 && !pRoofStructure)
3909 			{
3910 				// no structures in this tile, AND THAT INCLUDES ROOFS! :-)
3911 				// new system; figure out how many steps until we cross the next edge
3912 				// and then fast forward that many steps.
3913 
3914 				iOldTileX = pBullet->iCurrTileX;
3915 				iOldTileY = pBullet->iCurrTileY;
3916 				iOldCubesZ = pBullet->iCurrCubesZ;
3917 
3918 				if (pBullet->qIncrX > 0)
3919 				{
3920 					qDistToTravelX = INT32_TO_FIXEDPT( CELL_X_SIZE ) - (pBullet->qCurrX % INT32_TO_FIXEDPT( CELL_X_SIZE ));
3921 					iStepsToTravelX = qDistToTravelX / pBullet->qIncrX;
3922 				}
3923 				else if (pBullet->qIncrX < 0)
3924 				{
3925 					qDistToTravelX = pBullet->qCurrX % INT32_TO_FIXEDPT( CELL_X_SIZE );
3926 					iStepsToTravelX = qDistToTravelX / (-pBullet->qIncrX);
3927 				}
3928 				else
3929 				{
3930 					// make sure we don't consider X a limit :-)
3931 					iStepsToTravelX = 1000000;
3932 				}
3933 
3934 				if (pBullet->qIncrY > 0)
3935 				{
3936 					qDistToTravelY = INT32_TO_FIXEDPT( CELL_Y_SIZE ) - (pBullet->qCurrY % INT32_TO_FIXEDPT( CELL_Y_SIZE ));
3937 					iStepsToTravelY = qDistToTravelY / pBullet->qIncrY;
3938 				}
3939 				else if (pBullet->qIncrY < 0)
3940 				{
3941 					qDistToTravelY = pBullet->qCurrY % INT32_TO_FIXEDPT( CELL_Y_SIZE );
3942 					iStepsToTravelY = qDistToTravelY / (-pBullet->qIncrY);
3943 				}
3944 				else
3945 				{
3946 					// make sure we don't consider Y a limit :-)
3947 					iStepsToTravelY = 1000000;
3948 				}
3949 
3950 				// add 1 to the # of steps to travel to go INTO the next tile
3951 				iStepsToTravel = __min( iStepsToTravelX, iStepsToTravelY ) + 1;
3952 
3953 				// special coding (compared with other versions above) to deal with
3954 				// bullets hitting the ground
3955 				if (pBullet->qCurrZ + pBullet->qIncrZ * iStepsToTravel < qLandHeight)
3956 				{
3957 					iStepsToTravel = __min( iStepsToTravel, ABS( (pBullet->qCurrZ - qLandHeight) / pBullet->qIncrZ ) );
3958 					pBullet->qCurrX += pBullet->qIncrX * iStepsToTravel;
3959 					pBullet->qCurrY += pBullet->qIncrY * iStepsToTravel;
3960 					pBullet->qCurrZ += pBullet->qIncrZ * iStepsToTravel;
3961 
3962 					StopBullet(pBullet);
3963 					BulletHitStructure(pBullet, INVALID_STRUCTURE_ID, 0, TRUE);
3964 					return;
3965 				}
3966 
3967 				if (pBullet->usFlags & (BULLET_FLAG_MISSILE | BULLET_FLAG_SMALL_MISSILE | BULLET_FLAG_TANK_CANNON |
3968 							BULLET_FLAG_FLAME | BULLET_FLAG_CREATURE_SPIT))
3969 				{
3970 					INT8 bStepsPerMove = STEPS_FOR_BULLET_MOVE_TRAILS;
3971 
3972 					if ( pBullet->usFlags & ( BULLET_FLAG_SMALL_MISSILE ) )
3973 					{
3974 						bStepsPerMove = STEPS_FOR_BULLET_MOVE_SMALL_TRAILS;
3975 					}
3976 					else if ( pBullet->usFlags & ( BULLET_FLAG_FLAME ) )
3977 					{
3978 						bStepsPerMove = STEPS_FOR_BULLET_MOVE_FIRE_TRAILS;
3979 					}
3980 
3981 					for ( i = 0; i < iStepsToTravel; i++ )
3982 					{
3983 						if ( ( ( pBullet->iLoop + i ) % bStepsPerMove ) == 0 )
3984 						{
3985 							fGoingOver = TRUE;
3986 							break;
3987 						}
3988 					}
3989 
3990 					if ( fGoingOver )
3991 					{
3992 						FIXEDPT	qCurrX, qCurrY, qCurrZ;
3993 
3994 						qCurrX = pBullet->qCurrX + pBullet->qIncrX * i;
3995 						qCurrY = pBullet->qCurrY + pBullet->qIncrY * i;
3996 						qCurrZ = pBullet->qCurrZ + pBullet->qIncrZ * i;
3997 
3998 						AddMissileTrail( pBullet, qCurrX, qCurrY, qCurrZ );
3999 					}
4000 				}
4001 
4002 				pBullet->qCurrX += pBullet->qIncrX * iStepsToTravel;
4003 				pBullet->qCurrY += pBullet->qIncrY * iStepsToTravel;
4004 				pBullet->qCurrZ += pBullet->qIncrZ * iStepsToTravel;
4005 				pBullet->iLoop += iStepsToTravel;
4006 
4007 
4008 				// figure out the new tile location
4009 				pBullet->iCurrTileX = FIXEDPT_TO_TILE_NUM( pBullet->qCurrX );
4010 				pBullet->iCurrTileY = FIXEDPT_TO_TILE_NUM( pBullet->qCurrY );
4011 				pBullet->iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrZ ) );
4012 				pBullet->bLOSIndexX = FIXEDPT_TO_LOS_INDEX( pBullet->qCurrX );
4013 				pBullet->bLOSIndexY = FIXEDPT_TO_LOS_INDEX( pBullet->qCurrY );
4014 			}
4015 			else
4016 			{
4017 				// there are structures in this tile
4018 				iCurrCubesAboveLevelZ = CONVERT_HEIGHTUNITS_TO_INDEX( iCurrAboveLevelZ );
4019 				// figure out the LOS cube level of the current point
4020 
4021 				if (iCurrCubesAboveLevelZ < STRUCTURE_ON_ROOF_MAX)
4022 				{
4023 					if (iCurrCubesAboveLevelZ < STRUCTURE_ON_GROUND_MAX)
4024 					{
4025 						// check objects on the ground
4026 						sDesiredLevel = STRUCTURE_ON_GROUND;
4027 					}
4028 					else
4029 					{
4030 						// check objects on roofs
4031 						sDesiredLevel = STRUCTURE_ON_ROOF;
4032 						iCurrCubesAboveLevelZ -= STRUCTURE_ON_ROOF;
4033 					}
4034 					// check structures for collision
4035 					for ( iStructureLoop = 0; iStructureLoop < iNumLocalStructures; iStructureLoop++)
4036 					{
4037 						pStructure = gpLocalStructure[iStructureLoop];
4038 						if (pStructure && pStructure->sCubeOffset == sDesiredLevel)
4039 						{
4040 							if (((*(pStructure->pShape))[pBullet->bLOSIndexX][pBullet->bLOSIndexY] & AtHeight[iCurrCubesAboveLevelZ]) > 0)
4041 							{
4042 								if (pStructure->fFlags & STRUCTURE_PERSON)
4043 								{
4044 									// hit someone!
4045 									fStopped = BulletHitMerc( pBullet, pStructure, fIntended );
4046 									if (fStopped)
4047 									{
4048 										// remove bullet function now called from within BulletHitMerc, so just quit
4049 										return;
4050 									}
4051 									else
4052 									{
4053 										// set pointer to null so that we don't consider hitting this person again
4054 										gpLocalStructure[iStructureLoop] = NULL;
4055 									}
4056 								}
4057 								else if (pStructure->fFlags & STRUCTURE_WALLNWINDOW && pBullet->qCurrZ >= qWindowBottomHeight && pBullet->qCurrZ <= qWindowTopHeight)
4058 								{
4059 									fResolveHit = ResolveHitOnWall( pStructure, iGridNo, pBullet->bLOSIndexX, pBullet->bLOSIndexY, pBullet->ddHorizAngle );
4060 
4061 									if (fResolveHit)
4062 									{
4063 
4064 										if (pBullet->usFlags & BULLET_FLAG_KNIFE)
4065 										{
4066 											// knives do get stopped by windows!
4067 
4068 											iRemainingImpact = HandleBulletStructureInteraction( pBullet, pStructure, &fHitStructure );
4069 											if ( iRemainingImpact <= 0 )
4070 											{
4071 												// check angle of knife and place on ground appropriately
4072 												OBJECTTYPE Object;
4073 												INT32 iKnifeGridNo;
4074 
4075 												// Add item
4076 												if ((pBullet->usFlags & BULLET_FLAG_BLOODY))
4077 													CreateItem(BLOODY_THROWING_KNIFE, (INT8) pBullet->ubItemStatus, &Object);
4078 												else
4079 													CreateItem(THROWING_KNIFE, (INT8) pBullet->ubItemStatus, &Object);
4080 
4081 												// by default knife at same tile as window
4082 												iKnifeGridNo = (INT16) iGridNo;
4083 
4084 												if (pStructure->ubWallOrientation == INSIDE_TOP_RIGHT || pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT)
4085 												{
4086 													if ( pBullet->qIncrX > 0)
4087 													{
4088 														// heading east so place knife on west, in same tile
4089 													}
4090 													else
4091 													{
4092 														// place to east of window
4093 														iKnifeGridNo += 1;
4094 													}
4095 												}
4096 												else
4097 												{
4098 													if (pBullet->qIncrY > 0)
4099 													{
4100 														// heading south so place wall to north, in same tile of window
4101 													}
4102 													else
4103 													{
4104 														iKnifeGridNo += WORLD_ROWS;
4105 													}
4106 												}
4107 
4108 												if ( sDesiredLevel == STRUCTURE_ON_GROUND )
4109 												{
4110 													AddItemToPool(iKnifeGridNo, &Object, INVISIBLE, 0, 0, 0);
4111 												}
4112 												else
4113 												{
4114 													AddItemToPool(iKnifeGridNo, &Object, INVISIBLE, 0, 1, 0);
4115 												}
4116 
4117 												// Make team look for items
4118 												NotifySoldiersToLookforItems( );
4119 
4120 												// bullet must end here!
4121 												StopBullet(pBullet);
4122 												BulletHitStructure(pBullet, pStructure->usStructureID, 1, TRUE);
4123 												return;
4124 											}
4125 										}
4126 										else
4127 										{
4128 											BOOLEAN blow_south;
4129 											if (pStructure->ubWallOrientation == INSIDE_TOP_RIGHT ||
4130 													pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT)
4131 											{
4132 												blow_south = pBullet->qIncrX > 0;
4133 											}
4134 											else
4135 											{
4136 												blow_south = pBullet->qIncrY > 0;
4137 											}
4138 											BulletHitWindow(pBullet, pBullet->iCurrTileX + pBullet->iCurrTileY * WORLD_COLS, pStructure->usStructureID, blow_south);
4139 											LocateBullet(pBullet);
4140 											// have to remove this window from future hit considerations so the deleted structure data can't be referenced!
4141 											gpLocalStructure[iStructureLoop] = NULL;
4142 											// but the bullet keeps on going!!!
4143 										}
4144 
4145 									}
4146 								}
4147 								else if ( pBullet->iLoop > CLOSE_TO_FIRER || (pStructure->fFlags & ALWAYS_CONSIDER_HIT) || (pBullet->iLoop > CLOSE_TO_FIRER) || (fIntended) )
4148 								{
4149 									if (pStructure->fFlags & STRUCTURE_WALLSTUFF)
4150 									{
4151 										// possibly shooting at corner in which case we should let it pass
4152 										fResolveHit = ResolveHitOnWall( pStructure, iGridNo, pBullet->bLOSIndexX, pBullet->bLOSIndexY, pBullet->ddHorizAngle );
4153 									}
4154 									else
4155 									{
4156 										fResolveHit = TRUE;
4157 									}
4158 
4159 									if (fResolveHit)
4160 									{
4161 
4162 										iRemainingImpact = HandleBulletStructureInteraction( pBullet, pStructure, &fHitStructure );
4163 										if (fHitStructure)
4164 										{
4165 											// ATE: NOT if we are a special bullet like a LAW trail...
4166 											if (pStructure->fFlags & STRUCTURE_CORPSE && !( pBullet->usFlags & ( BULLET_FLAG_MISSILE | BULLET_FLAG_SMALL_MISSILE | BULLET_FLAG_TANK_CANNON | BULLET_FLAG_FLAME | BULLET_FLAG_CREATURE_SPIT ) ) )
4167 											{
4168 												// ATE: In enemy territory here... ;)
4169 												// Now that we have hit a corpse, make the bugger twich!
4170 												RemoveBullet(pBullet);
4171 
4172 												CorpseHit( (INT16)pBullet->sGridNo, pStructure->usStructureID );
4173 												SLOGD("Reducing attacker busy count..., CORPSE HIT");
4174 
4175 												FreeUpAttacker(pBullet->pFirer);
4176 												return;
4177 											}
4178 											else if ( iRemainingImpact <= 0 )
4179 											{
4180 												StopBullet(pBullet);
4181 												BulletHitStructure(pBullet, pStructure->usStructureID, 1, TRUE);
4182 												return;
4183 											}
4184 											else if (fHitStructure && (gubLocalStructureNumTimesHit[iStructureLoop] == 0) )
4185 											{
4186 												// play animation to indicate structure being hit
4187 												BulletHitStructure(pBullet, pStructure->usStructureID, 1, FALSE);
4188 												gubLocalStructureNumTimesHit[iStructureLoop] = 1;
4189 											}
4190 										}
4191 									}
4192 								}
4193 							}
4194 						}
4195 					}
4196 				}
4197 				// got past everything; go to next LOS location within
4198 				// tile, horizontally or vertically
4199 				bOldLOSIndexX = pBullet->bLOSIndexX;
4200 				bOldLOSIndexY = pBullet->bLOSIndexY;
4201 				iOldCubesZ = pBullet->iCurrCubesZ;
4202 				do
4203 				{
4204 					pBullet->qCurrX += pBullet->qIncrX;
4205 					pBullet->qCurrY += pBullet->qIncrY;
4206 					if (pRoofStructure)
4207 					{
4208 						qLastZ = pBullet->qCurrZ;
4209 						pBullet->qCurrZ += pBullet->qIncrZ;
4210 						if ( (qLastZ > qWallHeight && pBullet->qCurrZ <= qWallHeight) || (qLastZ < qWallHeight && pBullet->qCurrZ >= qWallHeight))
4211 						{
4212 							// generate roof-hitting event
4213 							// always stop with roofs
4214 
4215 							if ( 1 /*HandleBulletStructureInteraction( pBullet, pRoofStructure, &fHitStructure ) <= 0 */)
4216 							{
4217 								StopBullet(pBullet);
4218 								BulletHitStructure(pBullet, 0, 0, TRUE);
4219 								return;
4220 							}
4221 							/*
4222 							else
4223 							{
4224 								// ATE: Found this: Should we be calling this because if we do, it will
4225 								// delete a bullet that was not supposed to be deleted....
4226 								//BulletHitStructure(pBullet, 0, 0);
4227 							}*/
4228 						}
4229 					}
4230 					else
4231 					{
4232 						pBullet->qCurrZ += pBullet->qIncrZ;
4233 					}
4234 					pBullet->bLOSIndexX = FIXEDPT_TO_LOS_INDEX( pBullet->qCurrX );
4235 					pBullet->bLOSIndexY = FIXEDPT_TO_LOS_INDEX( pBullet->qCurrY );
4236 					pBullet->iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrZ ) );
4237 					pBullet->iLoop++;
4238 
4239 					if (pBullet->usFlags & (BULLET_FLAG_MISSILE | BULLET_FLAG_SMALL_MISSILE | BULLET_FLAG_TANK_CANNON |
4240 								BULLET_FLAG_FLAME | BULLET_FLAG_CREATURE_SPIT))
4241 					{
4242 						INT8 bStepsPerMove = STEPS_FOR_BULLET_MOVE_TRAILS;
4243 
4244 						if ( pBullet->usFlags & ( BULLET_FLAG_SMALL_MISSILE ) )
4245 						{
4246 							bStepsPerMove = STEPS_FOR_BULLET_MOVE_SMALL_TRAILS;
4247 						}
4248 						else if ( pBullet->usFlags & ( BULLET_FLAG_FLAME ) )
4249 						{
4250 							bStepsPerMove = STEPS_FOR_BULLET_MOVE_FIRE_TRAILS;
4251 						}
4252 
4253 						if ( pBullet->iLoop % bStepsPerMove == 0 )
4254 						{
4255 							// add smoke trail
4256 							AddMissileTrail( pBullet, pBullet->qCurrX, pBullet->qCurrY, pBullet->qCurrZ );
4257 						}
4258 					}
4259 
4260 				}
4261 				while( (pBullet->bLOSIndexX == bOldLOSIndexX) && (pBullet->bLOSIndexY == bOldLOSIndexY) && (pBullet->iCurrCubesZ == iOldCubesZ));
4262 				pBullet->iCurrTileX = FIXEDPT_TO_INT32( pBullet->qCurrX ) / CELL_X_SIZE;
4263 				pBullet->iCurrTileY = FIXEDPT_TO_INT32( pBullet->qCurrY ) / CELL_Y_SIZE;
4264 			}
4265 		} while( (pBullet->iCurrTileX == iOldTileX) && (pBullet->iCurrTileY == iOldTileY));
4266 
4267 		if ( !GridNoOnVisibleWorldTile( (INT16) (pBullet->iCurrTileX + pBullet->iCurrTileY * WORLD_COLS) ) || (pBullet->iCurrCubesZ > PROFILE_Z_SIZE * 2 && FIXEDPT_TO_INT32( pBullet->qIncrZ ) > 0 ) )
4268 		{
4269 			// bullet outside of world!
4270 			RemoveBullet(pBullet);
4271 			ShotMiss(pBullet);
4272 			return;
4273 		}
4274 
4275 		pBullet->sGridNo = MAPROWCOLTOPOS( pBullet->iCurrTileY , pBullet->iCurrTileX );
4276 		uiTileInc++;
4277 
4278 		if ( (pBullet->iLoop > pBullet->iRange * 2) )
4279 		{
4280 			// beyond max effective range, bullet starts to drop!
4281 			// since we're doing an increment based on distance, not time, the
4282 			// decrement is scaled down depending on how fast the bullet is (effective range)
4283 			pBullet->qIncrZ -= INT32_TO_FIXEDPT( 100 ) / (pBullet->iRange * 2);
4284 		}
4285 		else if ( (pBullet->usFlags & BULLET_FLAG_FLAME) && ( pBullet->iLoop > pBullet->iRange ) )
4286 		{
4287 			pBullet->qIncrZ -= INT32_TO_FIXEDPT( 100 ) / (pBullet->iRange * 2);
4288 		}
4289 
4290 		// check to see if bullet is close to target
4291 		if (pBullet->pFirer->target != NULL &&
4292 				!(pBullet->pFirer->uiStatusFlags & SOLDIER_ATTACK_NOTICED) &&
4293 				PythSpacesAway(pBullet->sGridNo, pBullet->sTargetGridNo) <= 3)
4294 		{
4295 			pBullet->pFirer->uiStatusFlags |= SOLDIER_ATTACK_NOTICED;
4296 		}
4297 	} while( uiTileInc < pBullet->ubTilesPerUpdate );
4298 	// unless the distance is integral, after the loop there will be a
4299 	// fractional amount of distance remaining which is unchecked
4300 	// but we shouldn't(?) need to check it because the target is there!
4301 
4302 }
4303 
4304 
CheckForCollision(FLOAT dX,FLOAT dY,FLOAT dZ,FLOAT dDeltaX,FLOAT dDeltaY,FLOAT dDeltaZ,UINT16 * pusStructureID,FLOAT * pdNormalX,FLOAT * pdNormalY,FLOAT * pdNormalZ)4305 INT32 CheckForCollision(FLOAT dX, FLOAT dY, FLOAT dZ, FLOAT dDeltaX, FLOAT dDeltaY, FLOAT dDeltaZ, UINT16* pusStructureID, FLOAT* pdNormalX, FLOAT* pdNormalY, FLOAT* pdNormalZ)
4306 {
4307 	INT32 iLandHeight;
4308 	INT32 iCurrAboveLevelZ;
4309 	INT32 iCurrCubesAboveLevelZ;
4310 	INT16 sDesiredLevel;
4311 
4312 	MAP_ELEMENT * pMapElement;
4313 	STRUCTURE * pStructure, *pTempStructure;
4314 
4315 	SOLDIERTYPE * pTarget;
4316 	//FLOAT dTargetX;
4317 	//FLOAT dTargetY;
4318 	FLOAT dTargetZMin;
4319 	FLOAT dTargetZMax;
4320 
4321 	//INT8 iImpactReduction;
4322 
4323 	INT16 sX, sY;
4324 
4325 	FLOAT dOldZUnits, dZUnits;
4326 
4327 	INT8 bLOSIndexX, bLOSIndexY;
4328 
4329 
4330 	sX = (INT16)( dX / CELL_X_SIZE );
4331 	sY = (INT16)( dY / CELL_Y_SIZE );
4332 
4333 	// Check if gridno is in bounds....
4334 	if ( !GridNoOnVisibleWorldTile( (INT16) (sX + sY * WORLD_COLS) ) )
4335 	{
4336 		//return( COLLISION_NONE );
4337 	}
4338 
4339 	if ( sX < 0 || sX > WORLD_COLS || sY < 0 || sY > WORLD_COLS )
4340 	{
4341 		//return( COLLISION_NONE );
4342 	}
4343 
4344 	// check a particular tile
4345 	// retrieve values from world for this particular tile
4346 	pMapElement = &(gpWorldLevelData[ sX + sY * WORLD_COLS] );
4347 	iLandHeight = CONVERT_PIXELS_TO_HEIGHTUNITS( pMapElement->sHeight );
4348 
4349 	// Calculate old height and new hieght in pixels
4350 	dOldZUnits = (dZ - dDeltaZ );
4351 	dZUnits = dZ;
4352 
4353 	//BOOLEAN fRoofPresent = FALSE;
4354 	//if (pBullet->fCheckForRoof)
4355 	//{
4356 	//	if (pMapElement->pRoofHead != NULL)
4357 	//	{
4358 	//		fRoofPresent = TRUE;
4359 	//	}
4360 	//	else
4361 	//	{
4362 	//		fRoofPresent = FALSE;
4363 	//	}
4364 	//}
4365 
4366 	//if (pMapElement->pMercHead != NULL && pBullet->iLoop != 1)
4367 	if (pMapElement->pMercHead != NULL )
4368 	{
4369 		// a merc! that isn't us :-)
4370 		pTarget = pMapElement->pMercHead->pSoldier;
4371 		//dTargetX = pTarget->dXPos;
4372 		//dTargetY = pTarget->dYPos;
4373 		dTargetZMin = 0.0f;
4374 		CalculateSoldierZPos( pTarget, HEIGHT, &dTargetZMax );
4375 		if (pTarget->bLevel > 0)
4376 		{
4377 			// on roof
4378 			dTargetZMin += WALL_HEIGHT_UNITS;
4379 		}
4380 	}
4381 	else
4382 	{
4383 		pTarget = NULL;
4384 	}
4385 
4386 	// record old tile location for loop purposes
4387 
4388 	// check for collision with the ground
4389 	iCurrAboveLevelZ = (INT32) dZ - iLandHeight;
4390 	if (iCurrAboveLevelZ < 0)
4391 	{
4392 		// ground is in the way!
4393 		if ( pMapElement->ubTerrainID == DEEP_WATER || pMapElement->ubTerrainID == LOW_WATER || pMapElement->ubTerrainID == MED_WATER )
4394 		{
4395 			return ( COLLISION_WATER );
4396 		}
4397 		else
4398 		{
4399 			return ( COLLISION_GROUND );
4400 		}
4401 	}
4402 	// check for the existence of structures
4403 	pStructure = pMapElement->pStructureHead;
4404 	if (pStructure == NULL)
4405 	{	// no structures in this tile
4406 
4407 		// we can go as far as we like vertically (so long as we don't hit
4408 		// the ground), but want to stop when we get to the next tile or
4409 		// the end of the LOS path
4410 
4411 		// move 1 unit along the bullet path
4412 		//if (fRoofPresent)
4413 		//{
4414 		//	dLastZ = pBullet->dCurrZ;
4415 		//	(pBullet->dCurrZ) += pBullet->dIncrZ;
4416 		//	if ( (dLastZ > WALL_HEIGHT && pBullet->dCurrZ < WALL_HEIGHT) || (dLastZ < WALL_HEIGHT && pBullet->dCurrZ > WALL_HEIGHT))
4417 		//	{
4418 		//		// generate roof-hitting event
4419 		//		BulletHitStructure(pBullet);
4420 		//		RemoveBullet(pBullet);
4421 		//		return;
4422 		//	}
4423 		//}
4424 		//else
4425 		//{
4426 		//	(pBullet->dCurrZ) += pBullet->dIncrZ;
4427 		//}
4428 
4429 		// check for ground collision
4430 		if ( dZ < iLandHeight)
4431 		{
4432 			// ground is in the way!
4433 			if (pMapElement->ubTerrainID == DEEP_WATER || pMapElement->ubTerrainID == LOW_WATER ||
4434 				pMapElement->ubTerrainID == MED_WATER )
4435 			{
4436 				return ( COLLISION_WATER );
4437 			}
4438 			else
4439 			{
4440 				return ( COLLISION_GROUND );
4441 			}
4442 		}
4443 
4444 		if ( gfCaves || gfBasement )
4445 		{
4446 			if ( dOldZUnits > HEIGHT_UNITS && dZUnits  < HEIGHT_UNITS )
4447 			{
4448 				return( COLLISION_ROOF );
4449 			}
4450 			if ( dOldZUnits < HEIGHT_UNITS && dZUnits  > HEIGHT_UNITS )
4451 			{
4452 				return( COLLISION_INTERIOR_ROOF );
4453 			}
4454 		}
4455 
4456 		// check to see if we hit someone
4457 		//if (pTarget && Distance2D( dX - dTargetX, dY - dTargetY ) < HIT_DISTANCE )
4458 		//{
4459 		//	// well, we're in the right area; it's possible that
4460 		//	// we're firing over or under them though
4461 		//	if ( dZ < dTargetZMax && dZ > dTargetZMin)
4462 		//	{
4463 		//		return( COLLISION_MERC );
4464 		//	}
4465 		//}
4466 
4467 	}
4468 	else
4469 	{
4470 		// there are structures in this tile
4471 		iCurrCubesAboveLevelZ = CONVERT_HEIGHTUNITS_TO_INDEX( iCurrAboveLevelZ );
4472 		// figure out the LOS cube level of the current point
4473 
4474 		// CALCULAT LOS INDEX
4475 		bLOSIndexX = CONVERT_WITHINTILE_TO_INDEX( ((INT32)dX) % CELL_X_SIZE );
4476 		bLOSIndexY = CONVERT_WITHINTILE_TO_INDEX( ((INT32)dY) % CELL_Y_SIZE );
4477 
4478 		if (iCurrCubesAboveLevelZ < STRUCTURE_ON_ROOF_MAX)
4479 		{
4480 			if (iCurrCubesAboveLevelZ < STRUCTURE_ON_GROUND_MAX)
4481 			{
4482 				// check objects on the ground
4483 				sDesiredLevel = STRUCTURE_ON_GROUND;
4484 			}
4485 			else
4486 			{
4487 				// check objects on roofs
4488 				sDesiredLevel = STRUCTURE_ON_ROOF;
4489 				iCurrCubesAboveLevelZ -= STRUCTURE_ON_ROOF;
4490 			}
4491 
4492 			// check structures for collision
4493 			while (pStructure != NULL)
4494 			{
4495 
4496 				if (pStructure->fFlags & STRUCTURE_ROOF || gfCaves || gfBasement )
4497 				{
4498 					if ( dOldZUnits > HEIGHT_UNITS && dZUnits  < HEIGHT_UNITS )
4499 					{
4500 						return( COLLISION_ROOF );
4501 					}
4502 					if ( dOldZUnits < HEIGHT_UNITS && dZUnits  > HEIGHT_UNITS )
4503 					{
4504 						return( COLLISION_INTERIOR_ROOF );
4505 					}
4506 				}
4507 
4508 				if (pStructure->sCubeOffset == sDesiredLevel)
4509 				{
4510 
4511 					if (((*(pStructure->pShape))[bLOSIndexX][bLOSIndexY] & AtHeight[iCurrCubesAboveLevelZ]) > 0)
4512 					{
4513 						*pusStructureID = pStructure->usStructureID;
4514 
4515 						if (pStructure->fFlags & STRUCTURE_WALLNWINDOW && dZ >= WINDOW_BOTTOM_HEIGHT_UNITS &&
4516 							dZ <= WINDOW_TOP_HEIGHT_UNITS)
4517 						{
4518 							if (pStructure->ubWallOrientation == INSIDE_TOP_RIGHT ||
4519 								pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT)
4520 							{
4521 
4522 								if (dDeltaX > 0)
4523 								{
4524 									return( COLLISION_WINDOW_SOUTHWEST );
4525 								}
4526 								else
4527 								{
4528 									return( COLLISION_WINDOW_NORTHWEST );
4529 								}
4530 							}
4531 							else
4532 							{
4533 								if ( dDeltaY > 0)
4534 								{
4535 									return( COLLISION_WINDOW_SOUTHEAST );
4536 								}
4537 								else
4538 								{
4539 									return( COLLISION_WINDOW_NORTHEAST );
4540 								}
4541 							}
4542 						}
4543 
4544 						if (pStructure->fFlags & STRUCTURE_WALLSTUFF )
4545 						{
4546 							*pdNormalX = 0;
4547 							*pdNormalY = 0;
4548 							*pdNormalZ = 0;
4549 
4550 							if (pStructure->ubWallOrientation == INSIDE_TOP_RIGHT ||
4551 								pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT)
4552 							{
4553 
4554 								if (dDeltaX > 0)
4555 								{
4556 									*pdNormalX = -1;
4557 									return( COLLISION_WALL_SOUTHEAST );
4558 								}
4559 								else
4560 								{
4561 									*pdNormalX = 1;
4562 									return( COLLISION_WALL_NORTHEAST );
4563 								}
4564 							}
4565 							else
4566 							{
4567 								if ( dDeltaY > 0)
4568 								{
4569 									*pdNormalY = -1;
4570 									return( COLLISION_WALL_SOUTHWEST );
4571 								}
4572 								else
4573 								{
4574 									*pdNormalY = 1;
4575 									return( COLLISION_WALL_NORTHWEST );
4576 								}
4577 							}
4578 
4579 						}
4580 						else
4581 						{
4582 							// Determine if we are on top of this struct
4583 							// If we are a tree, not dense enough to stay!
4584 							if (!(pStructure->fFlags & STRUCTURE_TREE) && !(pStructure->fFlags & STRUCTURE_CORPSE))
4585 							{
4586 								if ( iCurrCubesAboveLevelZ < PROFILE_Z_SIZE-1 )
4587 								{
4588 									if (!((*(pStructure->pShape))[bLOSIndexX][bLOSIndexY] & AtHeight[ iCurrCubesAboveLevelZ + 1 ]))
4589 									{
4590 										if ( ( pStructure->fFlags & STRUCTURE_ROOF ) )
4591 										{
4592 											return( COLLISION_ROOF );
4593 										}
4594 										else
4595 										{
4596 											return( COLLISION_STRUCTURE_Z );
4597 										}
4598 									}
4599 								}
4600 								else
4601 								{
4602 									// Search next level ( if we are ground )
4603 									if ( sDesiredLevel == STRUCTURE_ON_GROUND )
4604 									{
4605 										pTempStructure = pMapElement->pStructureHead;
4606 
4607 										// LOOK at ALL structs on roof
4608 										while ( pTempStructure != NULL )
4609 										{
4610 											if (pTempStructure->sCubeOffset == STRUCTURE_ON_ROOF )
4611 											{
4612 												if ( !((*(pTempStructure->pShape))[bLOSIndexX][bLOSIndexY] & AtHeight[ 0 ]) )
4613 												{
4614 													return( COLLISION_STRUCTURE_Z );
4615 												}
4616 
4617 											}
4618 
4619 											pTempStructure = pTempStructure->pNext;
4620 										}
4621 									}
4622 									else
4623 									{
4624 										// We are very high!
4625 										return( COLLISION_STRUCTURE_Z );
4626 									}
4627 								}
4628 							}
4629 
4630 							// Check armour rating.....
4631 							// ATE; not if small vegitation....
4632 							if ( pStructure->pDBStructureRef->pDBStructure->ubArmour != MATERIAL_LIGHT_VEGETATION )
4633 							{
4634 								if ( !(pStructure->fFlags & STRUCTURE_CORPSE ) )
4635 								{
4636 									return( COLLISION_STRUCTURE );
4637 								}
4638 							}
4639 						}
4640 					}
4641 				}
4642 				pStructure = pStructure->pNext;
4643 			}
4644 
4645 		}
4646 
4647 		// check to see if we hit someone
4648 		//if (pTarget && Distance2D( dX - dTargetX, dY - dTargetY ) < HIT_DISTANCE )
4649 		//{
4650 		//	// well, we're in the right area; it's possible that
4651 		//	// we're firing over or under them though
4652 		//	if ( dZ < dTargetZMax && dZ > dTargetZMin)
4653 		//	{
4654 		//		return( COLLISION_MERC );
4655 		//	}
4656 		//}
4657 
4658 	}
4659 
4660 	return( COLLISION_NONE );
4661 }
4662