197 changes: 193 additions & 4 deletions src/map.c
Original file line number Diff line number Diff line change
Expand Up @@ -1775,6 +1775,195 @@ BOOL mapShutdown(void)
return true;
}

/**
* Intersect a tile with a line and report the points of intersection
* line is gives as point plus 2d directional vector
* returned are two coordinates at the edge
* true if the intersection also crosses the tile split line
* (which has to be taken into account)
**/
BOOL map_Intersect(int* Cx, int* Cy, int* Vx, int* Vy, int* Sx, int* Sy)
{
int x, y, ox, oy, Dx, Dy, tileX, tileY;
int ily,iry,itx,ibx;

// dereference pointers
x = *Cx;
y = *Cy;
Dx = *Vx;
Dy = *Vy;

// Turn into tile coordinates
tileX = map_coord(x);
tileY = map_coord(y);

// Inter tile comp
ox = map_round(x);
oy = map_round(y);

// allow backwards tracing
if (ox == 0 && Dx < 0)
{
tileX--;
ox = TILE_UNITS;
}
if (oy == 0 && Dy < 0)
{
tileY--;
oy = TILE_UNITS;
}

*Cx =-4*TILE_UNITS; // to trigger assertion
*Cy =-4*TILE_UNITS;
*Vx =-4*TILE_UNITS;
*Vy =-4*TILE_UNITS;

// calculate intersection point on the left and right (if any)
ily = y - 4 * TILE_UNITS; // make sure initial value is way outside of tile
iry = y - 4 * TILE_UNITS;
if ( Dx != 0)
{
ily = y - ox*Dy/Dx;
iry = y + (TILE_UNITS-ox)*Dy/Dx;
}
// calculate intersection point on top and bottom (if any)
itx = x - 4 * TILE_UNITS; // make sure initial value is way outside of tile
ibx = x - 4 * TILE_UNITS;
if (Dy != 0)
{
itx = x - oy * Dx / Dy;
ibx = x + (TILE_UNITS-oy) * Dx / Dy;
}

// line comes from the left?
if (Dx >= 0)
{
if (map_coord(ily)==tileY || map_coord(ily-1) == tileY)
{
*Cx = world_coord(tileX);
*Cy = ily;
}
if (map_coord(iry)==tileY || map_coord(iry-1) == tileY)
{
*Vx = world_coord(tileX + 1);
*Vy = iry;
}
}
else
{
if (map_coord(ily) == tileY || map_coord(ily-1) == tileY)
{
*Vx = world_coord(tileX);
*Vy = ily;
}
if (map_coord(iry) == tileY || map_coord(iry - 1) == tileY)
{
*Cx = world_coord(tileX + 1);
*Cy = iry;
}
}
// line comes from the top?
if (Dy >= 0)
{
if (map_coord(itx) == tileX || map_coord(itx-1) == tileX)
{
*Cx = itx;
*Cy = world_coord(tileY);
}
if (map_coord(ibx) == tileX || map_coord(ibx-1) == tileX)
{
*Vx = ibx;
*Vy = world_coord(tileY + 1);
}
}
else
{
if (map_coord(itx) == tileX || map_coord(itx-1) == tileX)
{
*Vx = itx;
*Vy = world_coord(tileY);
}
if (map_coord(ibx) == tileX || map_coord(ibx-1) == tileX)
{
*Cx = ibx;
*Cy = world_coord(tileY + 1);
}
}
// assertions, no intersections outside of tile
ASSERT(*Cx >= world_coord(tileX) && *Cx <= world_coord(tileX + 1), "map_Intersect(): tile Bounds %i %i, %i %i -> %i,%i,%i,%i", x, y, Dx, Dy, *Cx, *Cy, *Vx, *Vy);
ASSERT(*Cy >= world_coord(tileY) && *Cy <= world_coord(tileY + 1), "map_Intersect(): tile Bounds %i %i, %i %i -> %i,%i,%i,%i", x, y, Dx, Dy, *Cx, *Cy, *Vx, *Vy);
ASSERT(*Vx >= world_coord(tileX) && *Vx <= world_coord(tileX + 1), "map_Intersect(): tile Bounds %i %i, %i %i -> %i,%i,%i,%i", x, y, Dx, Dy, *Cx, *Cy, *Vx, *Vy);
ASSERT(*Vy >= world_coord(tileY) && *Vy <= world_coord(tileY + 1), "map_Intersect(): tile Bounds %i %i, %i %i -> %i,%i,%i,%i", x, y, Dx, Dy, *Cx, *Cy, *Vx, *Vy);
ASSERT(tileX >= 0 && tileY >= 0 && tileX < mapWidth && tileY < mapHeight,"map_Intersect(): map Bounds %i %i, %i %i -> %i,%i,%i,%i", x, y, Dx, Dy, *Cx, *Cy, *Vx, *Vy);

//calculate midway line intersection points
if (((map_coord(itx) == tileX ) == (map_coord(ily) == tileY)) && ((map_coord(ibx) == tileX ) == (map_coord(iry) == tileY)))
{
// line crosses diagnonal only
if (Dx - Dy == 0)
{
return false;
}
*Sx = world_coord(tileX) + (Dx * oy - Dy * ox) / (Dx - Dy);
*Sy = world_coord(tileY) + (Dx * oy - Dy * ox) / (Dx - Dy);
if (map_coord(*Sx) != tileX || map_coord(*Sy) != tileY)
{
return false;
}
return true;
}
else if (((map_coord(ibx) == tileX) == (map_coord(ily) == tileY)) && ((map_coord(itx) == tileX) == (map_coord(iry) == tileY)))
{
//line crosses anti-diagonal only
if (Dx + Dy == 0) return false;
*Sx = world_coord(tileX) + (Dx * (TILE_UNITS - oy) + Dy * ox) / (Dx + Dy);
*Sy = world_coord(tileY) + (Dy * (TILE_UNITS - ox) + Dx * oy) / (Dx + Dy);
if (map_coord(*Sx) != tileX || map_coord(*Sy) != tileY)
{
return false;
}
return true;
}
else
{
//line crosses both tile diagonals - check texture which one matters more
if (tileX >= 0 && tileY >= 0 && tileX < mapWidth && tileY < mapHeight)
{
if (psMapTiles[tileX + (tileY * mapWidth)].texture & TILE_TRIFLIP)
{
// tile is split 0,1 to 1,0 - check anti-diagonal
if (Dx + Dy == 0)
{
return false;
}
*Sx = world_coord(tileX) + (Dx * (TILE_UNITS - oy) + Dy * ox)/(Dx + Dy);
*Sy = world_coord(tileY) + (Dy * (TILE_UNITS - ox) + Dx * oy)/(Dx + Dy);
if (map_coord(*Sx)!=tileX || map_coord(*Sy)!=tileY)
{
return false;
}
return true;
}
else
{
// tile is split 0,0 to 1,1 - check diagonal
if (Dx - Dy == 0)
{
return false;
}
*Sx = world_coord(tileX) + (Dx * oy - Dy * ox)/(Dx - Dy);
*Sy = world_coord(tileY) + (Dx * oy - Dy * ox)/(Dx - Dy);
if (map_coord(*Sx)!=tileX || map_coord(*Sy) != tileY)
{
return false;
}
return true;
}
}
}
return false;
}

/* Return linear interpolated height of x,y */
extern SWORD map_Height(int x, int y)
{
Expand Down Expand Up @@ -1931,10 +2120,10 @@ extern BOOL mapObjIsAboveGround( BASE_OBJECT *psObj )
tileY = map_coord(psObj->pos.y),
tileYOffset1 = (tileY * mapWidth),
tileYOffset2 = ((tileY+1) * mapWidth),
h1 = psMapTiles[MIN(mapWidth * mapHeight, tileYOffset1 + tileX) ].height,
h2 = psMapTiles[MIN(mapWidth * mapHeight, tileYOffset1 + tileX + 1)].height,
h3 = psMapTiles[MIN(mapWidth * mapHeight, tileYOffset2 + tileX) ].height,
h4 = psMapTiles[MIN(mapWidth * mapHeight, tileYOffset2 + tileX + 1)].height;
h1 = psMapTiles[MIN(mapWidth * mapHeight, tileYOffset1 + tileX) ].height * ELEVATION_SCALE,
h2 = psMapTiles[MIN(mapWidth * mapHeight, tileYOffset1 + tileX + 1)].height * ELEVATION_SCALE,
h3 = psMapTiles[MIN(mapWidth * mapHeight, tileYOffset2 + tileX) ].height * ELEVATION_SCALE,
h4 = psMapTiles[MIN(mapWidth * mapHeight, tileYOffset2 + tileX + 1)].height * ELEVATION_SCALE;

/* trivial test above */
if ( (psObj->pos.z > h1) && (psObj->pos.z > h2) &&
Expand Down
4 changes: 4 additions & 0 deletions src/map.h
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,9 @@ typedef struct _tile_coord
MAPTILE *psTile;
} TILE_COORD;

/* Intersect a line with the map and report tile intersection points */
extern BOOL map_Intersect(int *Cx, int *Cy, int *Vx, int* Vy, int *Sx, int *Sy);

/* Return height of x,y */
extern SWORD map_Height(int x, int y);

Expand All @@ -421,6 +424,7 @@ void mapFloodFillContinents(void);
extern void mapTest(void);

extern bool fireOnLocation(unsigned int x, unsigned int y);
extern BOOL map_Intersect(int* Cx, int* Cy, int* Vx, int* Vy, int* Sx, int* Sy);

/**
* Transitive sensor check for tile. Has to be here rather than
Expand Down
44 changes: 35 additions & 9 deletions src/projectile.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@
#include "multistat.h"
#include "mapgrid.h"

#define PROJ_MAX_PITCH 30
#define DIRECT_PROJ_SPEED 500
#define VTOL_HITBOX_MODIFICATOR 100

Expand All @@ -74,7 +73,7 @@ typedef struct _interval
#define PROJ_NAYBOR_RANGE (TILE_UNITS*4)
// used to create a specific ID for projectile objects to facilitate tracking them.
static const UDWORD ProjectileTrackerID = 0xdead0000;
// Watermelon:neighbour global info ripped from droid.c
// neighbour global info ripped from droid.c
static PROJ_NAYBOR_INFO asProjNaybors[MAX_NAYBORS];
static UDWORD numProjNaybors = 0;

Expand All @@ -95,7 +94,6 @@ BASE_OBJECT *g_pProjLastAttacker;
/***************************************************************************/

static UDWORD establishTargetRadius( BASE_OBJECT *psTarget );
static UDWORD establishTargetHeight( BASE_OBJECT *psTarget );
static void proj_ImpactFunc( PROJECTILE *psObj );
static void proj_PostImpactFunc( PROJECTILE *psObj );
static void proj_checkBurnDamage( BASE_OBJECT *apsList, PROJECTILE *psProj);
Expand Down Expand Up @@ -325,6 +323,13 @@ static void proj_UpdateKills(PROJECTILE *psObj, float experienceInc)
/***************************************************************************/

BOOL proj_SendProjectile(WEAPON *psWeap, BASE_OBJECT *psAttacker, int player, Vector3i target, BASE_OBJECT *psTarget, BOOL bVisible, int weapon_slot)
{
return proj_SendProjectileAngled(psWeap, psAttacker, player, target, psTarget, bVisible, weapon_slot, 0);
}

/***************************************************************************/

BOOL proj_SendProjectileAngled(WEAPON *psWeap, BASE_OBJECT *psAttacker, int player, Vector3i target, BASE_OBJECT *psTarget, BOOL bVisible, int weapon_slot, int min_angle)
{
PROJECTILE *psProj = malloc(sizeof(PROJECTILE));
SDWORD tarHeight, srcHeight, iMinSq;
Expand Down Expand Up @@ -405,7 +410,10 @@ BOOL proj_SendProjectile(WEAPON *psWeap, BASE_OBJECT *psAttacker, int player, Ve
if (psTarget)
{
const float maxHeight = establishTargetHeight(psTarget);
unsigned int heightVariance = frandom() * maxHeight;
const float minHight = MAX(0, MIN(maxHeight,maxHeight + 2 * LINE_OF_FIRE_MINIMUM - areaOfFire(psAttacker, psTarget, weapon_slot, true)));
unsigned int heightVariance = minHight + frandom() * (maxHeight - minHight);
// store visible part (LOCK ON this part for homing :)
psProj->partVisible = (maxHeight - minHight);

scoreUpdateVar(WD_SHOTS_ON_TARGET);

Expand Down Expand Up @@ -522,8 +530,23 @@ BOOL proj_SendProjectile(WEAPON *psWeap, BASE_OBJECT *psAttacker, int player, Ve
}
}

// Check against min_angle
if (psProj->pitch<min_angle)
{
// set pitch to pass terrain
psProj->pitch = min_angle;

fS = trigSin(min_angle);
fC = trigCos(min_angle);
fT = fS / fC;
fS = ACC_GRAVITY * (1. + fT * fT);
fS = fS / (2.0 * (fR * fT - dz));

iVel = trigIntSqrt(fS * (fR * fR));
}

/* if droid set muzzle pitch */
//Watermelon:fix turret pitch for more turrets
// fix turret pitch for more turrets
if (psAttacker != NULL && weapon_slot >= 0)
{
if (psAttacker->type == OBJ_DROID)
Expand Down Expand Up @@ -751,7 +774,8 @@ static void proj_InFlightFunc(PROJECTILE *psProj, bool bIndirect)
/* If it's homing and it has a target (not a miss)... */
move.x = psProj->psDest->pos.x - psProj->startX;
move.y = psProj->psDest->pos.y - psProj->startY;
move.z = psProj->psDest->pos.z + establishTargetHeight(psProj->psDest)/2 - psProj->srcHeight;
// Home at the center of the part that was visible when firing
move.z = psProj->psDest->pos.z + (establishTargetHeight(psProj->psDest) - psProj->partVisible/2) - psProj->srcHeight;
}
else
{
Expand Down Expand Up @@ -1967,7 +1991,7 @@ static void projGetNaybors(PROJECTILE *psObj)
#define BULLET_FLIGHT_HEIGHT 16


static UDWORD establishTargetHeight(BASE_OBJECT *psTarget)
UDWORD establishTargetHeight(BASE_OBJECT *psTarget)
{
if (psTarget == NULL)
{
Expand Down Expand Up @@ -2038,18 +2062,19 @@ static UDWORD establishTargetHeight(BASE_OBJECT *psTarget)
return height;
}

// TODO: check the /2 - does this really make sense? why + ?
utilityHeight = (yMax + yMin)/2;

return height + utilityHeight;
}
case OBJ_STRUCTURE:
{
STRUCTURE_STATS * psStructureStats = ((STRUCTURE *)psTarget)->pStructureType;
return (psStructureStats->pIMD->max.y + psStructureStats->pIMD->min.y) / 2;
return (psStructureStats->pIMD->max.y + psStructureStats->pIMD->min.y);
}
case OBJ_FEATURE:
// Just use imd ymax+ymin
return (psTarget->sDisplay.imd->max.y + psTarget->sDisplay.imd->min.y) / 2;
return (psTarget->sDisplay.imd->max.y + psTarget->sDisplay.imd->min.y);
case OBJ_PROJECTILE:
return BULLET_FLIGHT_HEIGHT;
default:
Expand Down Expand Up @@ -2082,3 +2107,4 @@ void checkProjectile(const PROJECTILE* psProjectile, const char * const location
for (n = 0; n != psProjectile->psNumDamaged; ++n)
checkObject(psProjectile->psDamaged[n], location_description, function, recurse - 1);
}

9 changes: 9 additions & 0 deletions src/projectile.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@

extern BASE_OBJECT *g_pProjLastAttacker; ///< The last unit that did damage - used by script functions

#define PROJ_MAX_PITCH 30
#define PROJ_ULTIMATE_PITCH 80

#define IN_FIRE 0x01 ///< Whether an object is in a fire.
#define BURNING 0x02 ///< Whether an object has just left the fire, but is still burning.
#define BURN_TIME 10000 ///< How long an object burns for after leaving a fire.
Expand All @@ -59,6 +62,10 @@ void proj_FreeAllProjectiles(void); ///< Free all projectiles in the list.
/** Send a single projectile against the given target. */
BOOL proj_SendProjectile(WEAPON *psWeap, BASE_OBJECT *psAttacker, int player, Vector3i target, BASE_OBJECT *psTarget, BOOL bVisible, int weapon_slot);

/** Send a single projectile against the given target
* with a minimum shot angle. */
BOOL proj_SendProjectileAngled(WEAPON *psWeap, BASE_OBJECT *psAttacker, int player, Vector3i target, BASE_OBJECT *psTarget, BOOL bVisible, int weapon_slot,int min_angle);

/** Return whether a weapon is direct or indirect. */
bool proj_Direct(const WEAPON_STATS* psStats);

Expand Down Expand Up @@ -115,6 +122,8 @@ static inline void setProjectileDamaged(PROJECTILE *psProj, BASE_OBJECT *psObj)
psProj->psDamaged[psProj->psNumDamaged - 1] = psObj;
}

UDWORD establishTargetHeight(BASE_OBJECT *psTarget);

/* @} */

void checkProjectile(const PROJECTILE* psProjectile, const char * const location_description, const char * function, const int recurse);
Expand Down
1 change: 1 addition & 0 deletions src/projectiledef.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ typedef struct PROJECTILE
SDWORD altChange; ///< Change in altitude
UDWORD born;
UDWORD died;
int partVisible; ///< how much of target was visible on shooting (important for homing)
} PROJECTILE;

#endif // __INCLUDED_PROJECTILEDEF_H__
39 changes: 39 additions & 0 deletions src/structure.c
Original file line number Diff line number Diff line change
Expand Up @@ -5526,6 +5526,45 @@ BOOL getLasSatExists(UDWORD player)
return lasSatExists[player];
}

/* calculate muzzle base location in 3d world */
BOOL calcStructureMuzzleBaseLocation(STRUCTURE *psStructure, Vector3f *muzzle, int weapon_slot)
{
iIMDShape *psShape = psStructure->pStructureType->pIMD, *psWeaponImd = NULL;

CHECK_STRUCTURE(psStructure);

if (psStructure->asWeaps[weapon_slot].nStat > 0)
{
psWeaponImd = asWeaponStats[psStructure->asWeaps[weapon_slot].nStat].pIMD;
}

if(psShape && psShape->nconnectors)
{
Vector3f barrel = {0.0f, 0.0f, 0.0f};

pie_MatBegin();

pie_TRANSLATE(psStructure->pos.x, -psStructure->pos.z, psStructure->pos.y);

//matrix = the center of droid
pie_MatRotY( DEG( psStructure->direction ) );
pie_MatRotX( DEG( psStructure->pitch ) );
pie_MatRotZ( DEG( -psStructure->roll ) );
pie_TRANSLATE( psShape->connectors[weapon_slot].x, -psShape->connectors[weapon_slot].z,
-psShape->connectors[weapon_slot].y); //note y and z flipped

pie_RotateTranslate3f(&barrel, muzzle);
muzzle->z = -muzzle->z;

pie_MatEnd();
}
else
{
*muzzle = Vector3f_Init(psStructure->pos.x, psStructure->pos.y, psStructure->pos.z + psStructure->sDisplay.imd->max.y);
}

return true;
}

/* calculate muzzle tip location in 3d world */
BOOL calcStructureMuzzleLocation(STRUCTURE *psStructure, Vector3f *muzzle, int weapon_slot)
Expand Down
1 change: 1 addition & 0 deletions src/structure.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ extern BOOL getLasSatExists(UDWORD player);

/* added int weapon_slot to fix the alway slot 0 hack */
extern BOOL calcStructureMuzzleLocation(STRUCTURE *psStructure, Vector3f *muzzle, int weapon_slot);
extern BOOL calcStructureMuzzleBaseLocation(STRUCTURE *psStructure, Vector3f *muzzle, int weapon_slot);

/*this is called whenever a structure has finished building*/
extern void buildingComplete(STRUCTURE *psBuilding);
Expand Down
232 changes: 211 additions & 21 deletions src/visibility.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include "cluster.h"
#include "scriptextern.h"
#include "structure.h"
#include "projectile.h"

#include "display.h"
#include "multiplay.h"
Expand Down Expand Up @@ -842,13 +843,16 @@ bool scrTileIsVisible(SDWORD player, SDWORD x, SDWORD y)
return scrTileVisible[player][x][y];
}

/* Check whether psViewer can fire directly at psTarget.
//forward declaration
static int checkFireLine( const BASE_OBJECT* psViewer, const BASE_OBJECT* psTarget, int weapon_slot, bool wallsBlock, bool direct);

/**
* Check whether psViewer can fire directly at psTarget.
* psTarget can be any type of BASE_OBJECT (e.g. a tree).
*/
bool lineOfFire(const BASE_OBJECT* psViewer, const BASE_OBJECT* psTarget, bool wallsBlock)
bool lineOfFire(const BASE_OBJECT* psViewer, const BASE_OBJECT* psTarget, int weapon_slot, bool wallsBlock)
{
Vector3i pos, dest, diff;
int range, distSq;
WEAPON_STATS *psStats;

ASSERT(psViewer != NULL, "Invalid shooter pointer!");
ASSERT(psTarget != NULL, "Invalid target pointer!");
Expand All @@ -857,37 +861,223 @@ bool lineOfFire(const BASE_OBJECT* psViewer, const BASE_OBJECT* psTarget, bool w
return false;
}

if (psViewer->type == OBJ_DROID)
{
psStats = asWeaponStats + ((DROID*)psViewer)->asWeaps[weapon_slot].nStat;
}
else if (psViewer->type == OBJ_STRUCTURE)
{
psStats = asWeaponStats + ((STRUCTURE*)psViewer)->asWeaps[weapon_slot].nStat;
}
if (proj_Direct(psStats))
{
/** direct shots could collide with ground **/
int distance = sqrt( (psTarget->pos.x - psViewer->pos.x) * (psTarget->pos.x - psViewer->pos.x) +
(psTarget->pos.y - psViewer->pos.y) * (psTarget->pos.y - psViewer->pos.y) +
(psTarget->pos.z - psViewer->pos.z) * (psTarget->pos.z - psViewer->pos.z));
int range = proj_GetLongRange(psStats);
if (range < distance)
{
return false;
}
else
{
return (LINE_OF_FIRE_MINIMUM <= checkFireLine(psViewer, psTarget, weapon_slot, wallsBlock, true));
}
}
else
{
/**
* indirect shots always have a line of fire, IF the forced
* minimum angle doesn't move it out of range
**/
int min_angle = checkFireLine(psViewer, psTarget, weapon_slot, wallsBlock, false);
/** 2d distance **/
int distance = sqrt( (psTarget->pos.x - psViewer->pos.x)*(psTarget->pos.x - psViewer->pos.x) +
(psTarget->pos.y - psViewer->pos.y)*(psTarget->pos.y - psViewer->pos.y) +
MAX(0,(psTarget->pos.z - psViewer->pos.z))*MAX(0,(psTarget->pos.z - psViewer->pos.z))
);
int range = proj_GetLongRange(psStats);
if (min_angle > PROJ_MAX_PITCH)
{
if (trigSin(2 * min_angle) < trigSin(2 * PROJ_MAX_PITCH))
{
range = (range * trigSin(2 * min_angle)) / trigSin(2 * PROJ_MAX_PITCH);
}
}
if (range < distance)
{
return false;
}
else
{
return true;
}
}
}

/* Check how much of psTarget is hitable from psViewer's gun position */
int areaOfFire(const BASE_OBJECT* psViewer, const BASE_OBJECT* psTarget, int weapon_slot, bool wallsBlock)
{
return checkFireLine(psViewer, psTarget, weapon_slot, wallsBlock, true);
}

/* Check the minimum angle to hitpsTarget from psViewer via indirect shots */
int arcOfFire(const BASE_OBJECT* psViewer, const BASE_OBJECT* psTarget, int weapon_slot, bool wallsBlock)
{
return checkFireLine(psViewer, psTarget, weapon_slot, wallsBlock, false);
}

/* helper function for checkFireLine */
static inline void angle_check(double* angletan, int positionSq, int height, int distanceSq, int targetHeight, bool direct) {
double current;
if (direct)
{
current = (double)height / (double)sqrt(positionSq);
}
else
{
int dist = sqrt(distanceSq);
int pos = sqrt(positionSq);
current = pos*targetHeight / dist;
if (current<height && pos>TILE_UNITS / 2 && pos < dist - TILE_UNITS / 2)
{
// solve the following trajectory parabolic equation
// ( targetHeight ) = a * distance^2 + factor * distance
// ( height ) = a * position^2 + factor * position
// "a" depends on angle, gravity and shooting speed.
// luckily we dont need it for this at all, since
// factor = tan(firing_angle)
current = ( (double)distanceSq * (double)height - (double)positionSq * (double)targetHeight )
/ ( (double)distanceSq * (double)pos - (double)dist * (double)positionSq );
}
else
{
current = 0;
}
}
if (current > *angletan)
{
*angletan = current;
}
}


/**
* Check fire line from psViewer to psTarget
* psTarget can be any type of BASE_OBJECT (e.g. a tree).
*/
static int checkFireLine(const BASE_OBJECT* psViewer, const BASE_OBJECT* psTarget, int weapon_slot, bool wallsBlock, bool direct)
{
Vector3i pos, dest;
Vector2i start,diff, current, halfway, next, part;
Vector3f muzzle;
int distSq, partSq, oldPartSq;
double angletan;

ASSERT_OR_RETURN(-1, psViewer || psTarget, "Invalid shooter pointer!");

// get muzzle offset (code from projectile.c)
if (psViewer->type == OBJ_DROID && weapon_slot >= 0)
{
calcDroidMuzzleBaseLocation( (DROID *) psViewer, &muzzle, weapon_slot);
}
else if (psViewer->type == OBJ_STRUCTURE && weapon_slot >= 0)
{
calcStructureMuzzleBaseLocation( (STRUCTURE *) psViewer, &muzzle, weapon_slot);
}
else // incase anything wants a projectile
{
// FIXME HACK Needed since we got those ugly Vector3uw floating around in BASE_OBJECT...
muzzle = Vector3uw_To3f(psViewer->pos);
}

// FIXME HACK Needed since we got those ugly Vector3uw floating around in BASE_OBJECT...
pos = Vector3uw_To3i(psViewer->pos);
pos = Vector3f_To3i(muzzle);
dest = Vector3uw_To3i(psTarget->pos);
diff = Vector3i_Sub(dest, pos);
range = objSensorRange(psViewer);
diff.x = dest.x - pos.x; diff.y = dest.y - pos.y;

distSq = Vector3i_ScalarP(diff, diff);
distSq = Vector2i_ScalarP(diff, diff);
if (distSq == 0)
{
// Should never be on top of each other, but ...
return true;
return 1000;
}

// initialise the callback variables
current.x = pos.x;
current.y = pos.y;
start = current;
angletan = -1000;
partSq = 0;
// run a manual trace along the line of fire until target is reached
while (partSq<distSq)
{
VisibleObjectHelp_t help = { true, wallsBlock, distSq, pos.z + visObjHeight(psViewer), { map_coord(dest.x), map_coord(dest.y) }, 0, 0, -UBYTE_MAX * GRAD_MUL * ELEVATION_SCALE, 0, { 0, 0 } };
int targetGrad, top;
BOOL hasSplitIntersection;

// Cast a ray from the viewer to the target
rayCast(pos, diff, range, rayLOSCallback, &help);
oldPartSq = partSq;

if (partSq > 0) angle_check(&angletan, partSq, map_Height(current.x, current.y) - pos.z, distSq, dest.z - pos.z, direct);

if (gWall != NULL && gNumWalls != NULL) // Out globals are set
// intersect current tile with line of fire
next = diff;
hasSplitIntersection=map_Intersect(&current.x, &current.y, &next.x, &next.y, &halfway.x, &halfway.y);

if (hasSplitIntersection)
{
*gWall = help.wall;
*gNumWalls = help.numWalls;
// check whether target was reached before tile split line:
part = Vector2i_Sub(halfway, start);
partSq = Vector2i_ScalarP(part, part);

if (partSq >= distSq)
{
break;
}

if (partSq > 0) angle_check(&angletan, partSq, map_Height(halfway.x, halfway.y) - pos.z, distSq, dest.z - pos.z, direct);
}

// See if the target can be seen
top = dest.z + visObjHeight(psTarget) - help.startHeight;
targetGrad = top * GRAD_MUL / MAX(1, help.lastDist);

return targetGrad >= help.currGrad;
// check for walls and other structures
// TODO: if there is a structure on the same tile as the shooter (and the shooter is not that structure) check if LOF is blocked by it.
if (wallsBlock && (oldPartSq>0))
{
const MAPTILE *psTile;
halfway.x = (next.x - current.x) / 2; halfway.y = (next.y - current.y) / 2;
halfway = Vector2i_Add(current,halfway);
psTile = mapTile(map_coord(halfway.x), map_coord(halfway.y));
if (TileHasStructure(psTile) && psTile->psObject != psTarget)
{
// check whether target was reached before tile's "half way" line
part = Vector2i_Sub(halfway, start);
partSq = Vector2i_ScalarP(part, part);

if (partSq >= distSq)
{
break;
}

// allowed to shoot over enemy structures if they are NOT the target
if (partSq > 0)
{
angle_check(&angletan, oldPartSq, psTile->psObject->pos.z + establishTargetHeight(psTile->psObject) - pos.z, distSq, dest.z-pos.z, direct);
}
}
}

// next
current = next;
part = Vector2i_Sub(current, start);
partSq = Vector2i_ScalarP(part, part);
ASSERT(partSq > oldPartSq,"areaOfFire(): no progress in tile-walk! From: %i,%i to %i,%i stuck in %i,%i", map_coord(pos.x), map_coord(pos.y), map_coord(dest.x), map_coord(dest.y), map_coord(current.x), map_coord(current.y));

}
if (direct)
{
return (establishTargetHeight((BASE_OBJECT*)psTarget) - (pos.z + (int)(angletan * sqrt(distSq)) - dest.z));
}
else
{
return (RAD_TO_DEG(atan(angletan)) + 1);
}
}

10 changes: 9 additions & 1 deletion src/visibility.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#include "objectdef.h"
#include "raycast.h"

#define LINE_OF_FIRE_MINIMUM 5

// initialise the visibility stuff
extern BOOL visInitialise(void);

Expand All @@ -46,7 +48,13 @@ extern void visTilesUpdate(BASE_OBJECT *psObj, RAY_CALLBACK callback);
extern bool visibleObject(const BASE_OBJECT* psViewer, const BASE_OBJECT* psTarget, bool wallsBlock);

/** Can shooter hit target with direct fire weapon? */
bool lineOfFire(const BASE_OBJECT* psViewer, const BASE_OBJECT* psTarget, bool wallsBlock);
bool lineOfFire(const BASE_OBJECT* psViewer, const BASE_OBJECT* psTarget, int weapon_slot, bool wallsBlock);

/** How much of target can the player hit with direct fire weapon? */
int areaOfFire(const BASE_OBJECT* psViewer, const BASE_OBJECT* psTarget, int weapon_slot, bool wallsBlock);

/** How much of target can the player hit with direct fire weapon? */
int arcOfFire(const BASE_OBJECT* psViewer, const BASE_OBJECT* psTarget, int weapon_slot, bool wallsBlock);

// Find the wall that is blocking LOS to a target (if any)
extern STRUCTURE* visGetBlockingWall(const BASE_OBJECT* psViewer, const BASE_OBJECT* psTarget);
Expand Down