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