/*****************************************************************************/
/* D2K: A Doom Source Port for the 21st Century                              */
/*                                                                           */
/* Copyright (C) 2014: See COPYRIGHT file                                    */
/*                                                                           */
/* This file is part of D2K.                                                 */
/*                                                                           */
/* D2K is free software: you can redistribute it and/or modify it under the  */
/* terms of the GNU General Public License as published by the Free Software */
/* Foundation, either version 2 of the License, or (at your option) any      */
/* later version.                                                            */
/*                                                                           */
/* D2K is distributed in the hope that it will be useful, but WITHOUT ANY    */
/* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS */
/* FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more    */
/* details.                                                                  */
/*                                                                           */
/* You should have received a copy of the GNU General Public License along   */
/* with D2K.  If not, see <http://www.gnu.org/licenses/>.                    */
/*                                                                           */
/*****************************************************************************/


#include "z_zone.h"

#include "r_defs.h"
#include "r_main.h"
#include "r_state.h"
#include "p_map.h"
#include "p_maputl.h"
#include "p_setup.h"
#include "m_bbox.h"
#include "g_overflow.h"
#include "e6y.h"
#include "g_game.h"
#include "p_mobj.h"

/*
==============================================================================

P_CheckSight
This uses specialized forms of the maputils routines for optimized performance

==============================================================================
*/

fixed_t sightzstart;            // eye z of looker
fixed_t topslope, bottomslope;  // slopes to top and bottom of target
int sightcounts[3];

CrossSubsectorFunc P_CrossSubsector;

/*
==============
=
= PTR_SightTraverse
=
==============
*/

bool PTR_SightTraverse(intercept_t *in)
{
  line_t *li;
  fixed_t slope;

  li = in->d.line;

  //
  // crosses a two sided line
  //
  P_LineOpening(li);

  if (openbottom >= opentop)  // quick test for totally closed doors
    return false;  // stop

  if (li->frontsector->floorheight != li->backsector->floorheight)
  {
    slope = FixedDiv(openbottom - sightzstart , in->frac);
    if (slope > bottomslope)
      bottomslope = slope;
  }

  if (li->frontsector->ceilingheight != li->backsector->ceilingheight)
  {
    slope = FixedDiv(opentop - sightzstart, in->frac);
    if (slope < topslope)
      topslope = slope;
  }

  if (topslope <= bottomslope)
    return false;  // stop

  return true;  // keep going
}



/*
==================
=
= P_SightBlockLinesIterator
=
===================
*/

bool P_SightBlockLinesIterator(int x, int y)
{
  int offset;
  int *list;
  line_t *ld;
  int s1, s2;
  divline_t dl;

  offset = y*bmapwidth+x;

  offset = *(blockmap+offset);

  for (list = blockmaplump+offset; *list != -1; list++)
  {
    ld = &lines[*list];
    if (ld->validcount == validcount)
      continue;    // line has already been checked
    ld->validcount = validcount;

    s1 = P_PointOnDivlineSide(ld->v1->x, ld->v1->y, &trace);
    s2 = P_PointOnDivlineSide(ld->v2->x, ld->v2->y, &trace);
    if (s1 == s2)
      continue;    // line isn't crossed
    P_MakeDivline (ld, &dl);
    s1 = P_PointOnDivlineSide(trace.x, trace.y, &dl);
    s2 = P_PointOnDivlineSide(trace.x+trace.dx, trace.y+trace.dy, &dl);
    if (s1 == s2)
      continue; // line isn't crossed

    // try to early out the check
    if (!ld->backsector)
      return false; // stop checking

    check_intercept();    // killough

    // store the line for later intersection testing
    intercept_p->d.line = ld;
    intercept_p++;

  }

  return true; // everything was checked
}

/*
====================
=
= P_SightTraverseIntercepts
=
= Returns true if the traverser function returns true for all lines
====================
*/

bool P_SightTraverseIntercepts(void)
{
  int count;
  fixed_t dist;
  intercept_t *scan, *in;
  divline_t dl;

  count = intercept_p - intercepts;
  //
  // calculate intercept distance
  //
  for (scan = intercepts; scan<intercept_p; scan++)
  {
    P_MakeDivline(scan->d.line, &dl);
    scan->frac = P_InterceptVector(&trace, &dl);    
  }

  //
  // go through in order
  //  
  in = 0; // shut up compiler warning

  while (count--)
  {
    dist = INT_MAX;
    for (scan = intercepts ; scan<intercept_p ; scan++)
      if (scan->frac < dist)
      {
        dist = scan->frac;
        in = scan;
      }

      if (!PTR_SightTraverse(in))
        return false;      // don't bother going farther
      in->frac = INT_MAX;
  }

  return true;    // everything was traversed
}



/*
==================
=
= P_SightPathTraverse
=
= Traces a line from x1,y1 to x2,y2, calling the traverser function for each
= Returns true if the traverser function returns true for all lines
==================
*/

bool P_SightPathTraverse(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2)
{
  fixed_t xt1,yt1,xt2,yt2;
  fixed_t xstep,ystep;
  fixed_t partial;
  fixed_t xintercept, yintercept;
  int mapx, mapy, mapxstep, mapystep;
  int count;

  validcount++;
  intercept_p = intercepts;

  if (((x1-bmaporgx)&(MAPBLOCKSIZE-1)) == 0)
    x1 += FRACUNIT;        // don't side exactly on a line
  if (((y1-bmaporgy)&(MAPBLOCKSIZE-1)) == 0)
    y1 += FRACUNIT;        // don't side exactly on a line
  trace.x = x1;
  trace.y = y1;
  trace.dx = x2 - x1;
  trace.dy = y2 - y1;

  x1 -= bmaporgx;
  y1 -= bmaporgy;
  xt1 = P_GetSafeBlockX(x1);
  yt1 = P_GetSafeBlockY(y1);

  x2 -= bmaporgx;
  y2 -= bmaporgy;
  xt2 = P_GetSafeBlockX(x2);
  yt2 = P_GetSafeBlockY(y2);

  // points should never be out of bounds, but check once instead of
  // each block
  if (xt1<0 || yt1<0 || xt1>=bmapwidth || yt1>=bmapheight
    || xt2<0 || yt2<0 || xt2>=bmapwidth || yt2>=bmapheight)
    return false;

  if (xt2 > xt1)
  {
    mapxstep = 1;
    partial = FRACUNIT - ((x1>>MAPBTOFRAC)&(FRACUNIT-1));
    ystep = FixedDiv (y2-y1,abs(x2-x1));
  }
  else if (xt2 < xt1)
  {
    mapxstep = -1;
    partial = (x1>>MAPBTOFRAC)&(FRACUNIT-1);
    ystep = FixedDiv (y2-y1,abs(x2-x1));
  }
  else
  {
    mapxstep = 0;
    partial = FRACUNIT;
    ystep = 256*FRACUNIT;
  }  
  yintercept = (y1>>MAPBTOFRAC) + FixedMul (partial, ystep);


  if (yt2 > yt1)
  {
    mapystep = 1;
    partial = FRACUNIT - ((y1>>MAPBTOFRAC)&(FRACUNIT-1));
    xstep = FixedDiv (x2-x1,abs(y2-y1));
  }
  else if (yt2 < yt1)
  {
    mapystep = -1;
    partial = (y1>>MAPBTOFRAC)&(FRACUNIT-1);
    xstep = FixedDiv (x2-x1,abs(y2-y1));
  }
  else
  {
    mapystep = 0;
    partial = FRACUNIT;
    xstep = 256*FRACUNIT;
  }  
  xintercept = (x1>>MAPBTOFRAC) + FixedMul (partial, xstep);


  //
  // step through map blocks
  // Count is present to prevent a round off error from skipping the break
  mapx = xt1;
  mapy = yt1;


  for (count = 0; count < 64; count++)
  {
    if (!P_SightBlockLinesIterator(mapx, mapy))
    {
      sightcounts[1]++;
      return false;  // early out
    }

    if (mapx == xt2 && mapy == yt2)
      break;

    if ((yintercept >> FRACBITS) == mapy)
    {
      yintercept += ystep;
      mapx += mapxstep;
    }
    else if ((xintercept >> FRACBITS) == mapx)
    {
      xintercept += xstep;
      mapy += mapystep;
    }

  }


  //
  // couldn't early out, so go through the sorted list
  //
  sightcounts[2]++;

  return P_SightTraverseIntercepts();
}



/*
=====================
=
= P_CheckSight
=
= Returns true if a straight line between t1 and t2 is unobstructed
= look from eyes of t1 to any part of t2
=
=====================
*/

bool P_CheckSight_12(mobj_t *t1, mobj_t *t2)
{
  int s1, s2;
  int pnum, bytenum, bitnum;

  //
  // check for trivial rejection
  //
  s1 = (t1->subsector->sector->iSectorID);
  s2 = (t2->subsector->sector->iSectorID);
  pnum = s1*numsectors + s2;
  bytenum = pnum>>3;
  bitnum = 1 << (pnum&7);

  if (rejectmatrix[bytenum]&bitnum)
  {
    sightcounts[0]++;
    return false;    // can't possibly be connected
  }

  //
  // check precisely
  //    
  sightzstart = t1->z + t1->height - (t1->height>>2);
  topslope = (t2->z+t2->height) - sightzstart;
  bottomslope = (t2->z) - sightzstart;

  return P_SightPathTraverse (t1->x, t1->y, t2->x, t2->y);
}

//
// P_CheckSight
//
// killough 4/19/98:
// Convert LOS info to struct for reentrancy and efficiency of data locality

typedef struct {
  fixed_t sightzstart, t2x, t2y;   // eye z of looker
  divline_t strace;                // from t1 to t2
  fixed_t topslope, bottomslope;   // slopes to top and bottom of target
  fixed_t bbox[4];
  fixed_t maxz,minz;               // cph - z optimisations for 2sided lines
} los_t;

static los_t los; // cph - made static

//
// P_DivlineSide
// Returns side 0 (front), 1 (back), or 2 (on).
//
// killough 4/19/98: made static, cleaned up

INLINE static int P_DivlineSide(fixed_t x, fixed_t y, const divline_t *node)
{
  fixed_t left, right;
  return
    !node->dx ? x == node->x ? 2 : x <= node->x ? node->dy > 0 : node->dy < 0 :
    !node->dy ? ( compatibility_level < prboom_4_compatibility ? x : y) == node->y ? 2 : y <= node->y ? node->dx < 0 : node->dx > 0 :
    (right = ((y - node->y) >> FRACBITS) * (node->dx >> FRACBITS)) <
    (left  = ((x - node->x) >> FRACBITS) * (node->dy >> FRACBITS)) ? 0 :
    right == left ? 2 : 1;
}

INLINE static int P_DivlineCrossed(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2, divline_t *node)
{
#if 1
   return (P_DivlineSide(x1, y1, node) == P_DivlineSide(x2, y2, node));
#else
  if (!node->dx)
  {
    if (x1 == node->x)
      return (x2 == node->x);
    if (x2 == node->x)
      return (x1 == node->x);
    if (x1 < node->x)
      return (x2 < node->x);
    return (x2 > node->x);
  }

  if (!node->dy)
  {
    fixed_t _y1, _y2;
    if (compatibility_level < prboom_4_compatibility)
    {
      _y1 = x1;
      _y2 = x2;
    }
    else
    {
      _y1 = y1;
      _y2 = y2;
    }
    //if ((compatibility_level < prboom_4_compatibility ? x1 : y1) == node->y)
    //  return (x2 == node->y);
    if (_y1 == node->y)
      return (_y2 == node->y);
    if (_y2 == node->y)
      return (_y1 == node->y);
    if (y1 <= node->y)
      return (y2 < node->y);
    return (y2 > node->y);
  }

  {
    fixed_t node_dx = (node->dx >> FRACBITS);
    fixed_t node_dy = (node->dy >> FRACBITS);

    fixed_t left1  = node_dy * ((x1 - node->x) >> FRACBITS);
    fixed_t right1 = ((y1 - node->y) >> FRACBITS) * node_dx;
    fixed_t left2  = node_dy * ((x2 - node->x) >> FRACBITS);
    fixed_t right2 = ((y2 - node->y) >> FRACBITS) * node_dx;

    if (right1 < left1)
      return (right2 < left2);

    if (left1 == right1)
      return (left2 == right2);

    return (right2 > left2);
  }
#endif
}


//
// P_CrossSubsector
// Returns true
//  if strace crosses the given subsector successfully.
//
// killough 4/19/98: made static and cleaned up

bool P_CrossSubsector_PrBoom(int num)
{
  ssline_t *ssline = &sslines[sslines_indexes[num]];
  const ssline_t *ssline_last = &sslines[sslines_indexes[num + 1]];
  fixed_t opentop = 0, openbottom = 0;
  const sector_t *front = NULL, *back = NULL;

#ifdef RANGECHECK
  if (num >= numsubsectors)
    I_Error("P_CrossSubsector: ss %i with numss = %i", num, numsubsectors);
#endif

  // check lines
  for (; ssline < ssline_last; ssline++)
  {
    divline_t divl;

    /* OPTIMIZE: killough 4/20/98: Added quick bounding-box rejection test
     * cph - this is causing demo desyncs on original Doom demos.
     *  Who knows why. Exclude test for those.
     */
    if (ssline->bbox[BOXLEFT  ] > los.bbox[BOXRIGHT ] ||
        ssline->bbox[BOXRIGHT ] < los.bbox[BOXLEFT  ] ||
        ssline->bbox[BOXBOTTOM] > los.bbox[BOXTOP   ] ||
        ssline->bbox[BOXTOP]    < los.bbox[BOXBOTTOM])
    {
      ssline->linedef->validcount = validcount;
      continue;
    }

    // Forget this line if it doesn't cross the line of sight
    if (P_DivlineCrossed(ssline->x1, ssline->y1, ssline->x2, ssline->y2, &los.strace))
    {
      ssline->linedef->validcount = validcount;
      continue;
    }

    divl.dx = ssline->x2 - (divl.x = ssline->x1);
    divl.dy = ssline->y2 - (divl.y = ssline->y1);

    // line isn't crossed?
    if (P_DivlineCrossed(los.strace.x, los.strace.y, los.t2x, los.t2y, &divl))
    {
      ssline->linedef->validcount = validcount;
      continue;
    }

    // allready checked other side?
    if (ssline->linedef->validcount == validcount)
      continue;

    ssline->linedef->validcount = validcount;

    // cph - do what we can before forced to check intersection
    if (ssline->linedef->flags & ML_TWOSIDED)
    {
      // crosses a two sided line
      front = ssline->seg->frontsector;
      back = ssline->seg->backsector;

      // no wall to block sight with?
      if (front->floorheight == back->floorheight
        && front->ceilingheight == back->ceilingheight)
        continue;	

      // possible occluder
      // because of ceiling height differences
      opentop = MIN(front->ceilingheight, back->ceilingheight);

      // because of floor height differences
      openbottom = MAX(front->floorheight, back->floorheight);

      // cph - reject if does not intrude in the z-space of the possible LOS
      if ((opentop >= los.maxz) && (openbottom <= los.minz))
        continue;
    }

    // cph - if bottom >= top or top < minz or bottom > maxz then it must be
    // solid wrt this LOS
    if (!(ssline->linedef->flags & ML_TWOSIDED) || (openbottom >= opentop) ||
  (prboom_comp[PC_FORCE_LXDOOM_DEMO_COMPATIBILITY].state ?
  (opentop <= los.minz) || (openbottom >= los.maxz) :
  (opentop < los.minz) || (openbottom > los.maxz)))
  return false;

    { // crosses a two sided line
      /* cph 2006/07/15 - oops, we missed this in 2.4.0 & .1;
       *  use P_InterceptVector2 for those compat levels only. */ 
      fixed_t frac = (compatibility_level == prboom_5_compatibility || compatibility_level == prboom_6_compatibility) ?
		      P_InterceptVector2(&los.strace, &divl) : 
		      P_InterceptVector(&los.strace, &divl);

      if (front->floorheight != back->floorheight)
      {
        fixed_t slope = FixedDiv(openbottom - los.sightzstart, frac);
        if (slope > los.bottomslope)
          los.bottomslope = slope;
      }

      if (front->ceilingheight != back->ceilingheight)
      {
        fixed_t slope = FixedDiv(opentop - los.sightzstart, frac);
        if (slope < los.topslope)
          los.topslope = slope;
      }

      if (los.topslope <= los.bottomslope)
        return false;               // stop
    }
  }
  // passed the subsector ok
  return true;
}

bool P_CrossSubsector_Doom(int num)
{
  ssline_t *ssline = &sslines[sslines_indexes[num]];
  const ssline_t *ssline_last = &sslines[sslines_indexes[num + 1]];

#ifdef RANGECHECK
  if (num >= numsubsectors)
    I_Error("P_CrossSubsector: ss %i with numss = %i", num, numsubsectors);
#endif

  for (; ssline < ssline_last; ssline++)
  {
    divline_t divl;
    fixed_t opentop, openbottom;
    const sector_t *front, *back;
    fixed_t frac;

    // line isn't crossed?
    if (P_DivlineCrossed(ssline->x1, ssline->y1, ssline->x2, ssline->y2, &los.strace))
    {
      ssline->linedef->validcount = validcount;
      continue;
    }

    divl.dx = ssline->x2 - (divl.x = ssline->x1);
    divl.dy = ssline->y2 - (divl.y = ssline->y1);

    // line isn't crossed?
    if (P_DivlineCrossed(los.strace.x, los.strace.y, los.t2x, los.t2y, &divl))
    {
      ssline->linedef->validcount = validcount;
      continue;
    }

    // allready checked other side?
    if (ssline->linedef->validcount == validcount)
      continue;

    ssline->linedef->validcount = validcount;

    // stop because it is not two sided anyway
    if (!(ssline->linedef->flags & ML_TWOSIDED))
      return false;

    // crosses a two sided line
    front = ssline->seg->frontsector;
    back = ssline->seg->backsector;

    // missed back side on two-sided lines.
    if (!back)
    {
      back = GetSectorAtNullAddress();
    }

    // no wall to block sight with?
    if (front->floorheight == back->floorheight
      && front->ceilingheight == back->ceilingheight)
      continue;	

    // possible occluder
    // because of ceiling height differences
    opentop = MIN(front->ceilingheight, back->ceilingheight);

    // because of floor height differences
    openbottom = MAX(front->floorheight, back->floorheight);

    // quick test for totally closed doors
    if (openbottom >= opentop)
      return false;               // stop

    frac = P_InterceptVector2(&los.strace, &divl);

    if (front->floorheight != back->floorheight)
    {
      fixed_t slope = FixedDiv(openbottom - los.sightzstart, frac);
      if (slope > los.bottomslope)
        los.bottomslope = slope;
    }

    if (front->ceilingheight != back->ceilingheight)
    {
      fixed_t slope = FixedDiv(opentop - los.sightzstart, frac);
      if (slope < los.topslope)
        los.topslope = slope;
    }

    if (los.topslope <= los.bottomslope)
      return false;               // stop
  }
  // passed the subsector ok
  return true;
}

bool P_CrossSubsector_Boom(int num)
{
  ssline_t *ssline = &sslines[sslines_indexes[num]];
  const ssline_t *ssline_last = &sslines[sslines_indexes[num + 1]];

#ifdef RANGECHECK
  if (num >= numsubsectors)
    I_Error("P_CrossSubsector: ss %i with numss = %i", num, numsubsectors);
#endif

  for (; ssline < ssline_last; ssline++)
  {
    divline_t divl;
    fixed_t opentop, openbottom;
    const sector_t *front, *back;
    fixed_t frac;

    // OPTIMIZE: killough 4/20/98: Added quick bounding-box rejection test

    if (ssline->bbox[BOXLEFT  ] > los.bbox[BOXRIGHT ] ||
        ssline->bbox[BOXRIGHT ] < los.bbox[BOXLEFT  ] ||
        ssline->bbox[BOXBOTTOM] > los.bbox[BOXTOP   ] ||
        ssline->bbox[BOXTOP]    < los.bbox[BOXBOTTOM])
    {
      ssline->linedef->validcount = validcount;
      continue;
    }

    // line isn't crossed?
    if (P_DivlineCrossed(ssline->x1, ssline->y1, ssline->x2, ssline->y2, &los.strace))
    {
      ssline->linedef->validcount = validcount;
      continue;
    }

    divl.dx = ssline->x2 - (divl.x = ssline->x1);
    divl.dy = ssline->y2 - (divl.y = ssline->y1);

    // line isn't crossed?
    if (P_DivlineCrossed(los.strace.x, los.strace.y, los.t2x, los.t2y, &divl))
    {
      ssline->linedef->validcount = validcount;
      continue;
    }

    // allready checked other side?
    if (ssline->linedef->validcount == validcount)
      continue;

    ssline->linedef->validcount = validcount;

    // stop because it is not two sided anyway
    if (!(ssline->linedef->flags & ML_TWOSIDED))
      return false;

    // crosses a two sided line
    front = ssline->seg->frontsector;
    back = ssline->seg->backsector;

    // no wall to block sight with?
    if (front->floorheight == back->floorheight
      && front->ceilingheight == back->ceilingheight)
      continue;

    // possible occluder
    // because of ceiling height differences
    opentop = MIN(front->ceilingheight, back->ceilingheight);

    // because of floor height differences
    openbottom = MAX(front->floorheight, back->floorheight);

    // quick test for totally closed doors
    if (openbottom >= opentop)
      return false;               // stop

    frac = P_InterceptVector2(&los.strace, &divl);

    if (front->floorheight != back->floorheight)
    {
      fixed_t slope = FixedDiv(openbottom - los.sightzstart, frac);
      if (slope > los.bottomslope)
        los.bottomslope = slope;
    }

    if (front->ceilingheight != back->ceilingheight)
    {
      fixed_t slope = FixedDiv(opentop - los.sightzstart, frac);
      if (slope < los.topslope)
        los.topslope = slope;
    }

    if (los.topslope <= los.bottomslope)
      return false;               // stop
  }
  // passed the subsector ok
  return true;
}

//
// P_CrossBSPNode
// Returns true
//  if strace crosses the given node successfully.
//
// killough 4/20/98: rewritten to remove tail recursion, clean up, and optimize
// cph - Made to use R_PointOnSide instead of P_DivlineSide, since the latter
//  could return 2 which was ambigous, and the former is
//  better optimised; also removes two casts :-)

static bool P_CrossBSPNode_LxDoom(int bspnum)
{
  while (!(bspnum & NF_SUBSECTOR))
    {
      register const node_t *bsp = nodes + bspnum;
      int side,side2;
      side = R_PointOnSide(los.strace.x, los.strace.y, bsp);
      side2 = R_PointOnSide(los.t2x, los.t2y, bsp);
      if (side == side2)
         bspnum = bsp->children[side]; // doesn't touch the other side
      else         // the partition plane is crossed here
        if (!P_CrossBSPNode_LxDoom(bsp->children[side]))
          return 0;  // cross the starting side
        else
          bspnum = bsp->children[side^1];  // cross the ending side
    }
  return P_CrossSubsector(bspnum == -1 ? 0 : bspnum & ~NF_SUBSECTOR);
}

static bool P_CrossBSPNode_PrBoom(int bspnum)
{
  while (!(bspnum & NF_SUBSECTOR))
    {
      register const node_t *bsp = nodes + bspnum;
      int side,side2;
      side = P_DivlineSide(los.strace.x,los.strace.y,(const divline_t *)bsp)&1;
      side2= P_DivlineSide(los.t2x, los.t2y, (const divline_t *) bsp);
      if (side == side2)
         bspnum = bsp->children[side]; // doesn't touch the other side
      else         // the partition plane is crossed here
        if (!P_CrossBSPNode_PrBoom(bsp->children[side]))
          return 0;  // cross the starting side
        else
          bspnum = bsp->children[side^1];  // cross the ending side
    }
  return P_CrossSubsector(bspnum == -1 ? 0 : bspnum & ~NF_SUBSECTOR);
}

/* proff - Moved the compatibility check outside the functions
 * this gives a slight speedup
 */
static bool P_CrossBSPNode(int bspnum)
{
  /* cph - LxDoom used some R_* funcs here */
  if (compatibility_level == lxdoom_1_compatibility || prboom_comp[PC_FORCE_LXDOOM_DEMO_COMPATIBILITY].state)
    return P_CrossBSPNode_LxDoom(bspnum);
  else
    return P_CrossBSPNode_PrBoom(bspnum);
}

//
// P_CheckSight
// Returns true
//  if a straight line between t1 and t2 is unobstructed.
// Uses REJECT.
//
// killough 4/20/98: cleaned up, made to use new LOS struct

bool P_CheckSight(mobj_t *t1, mobj_t *t2) {
  const sector_t *s1, *s2;
  int pnum;

  if (compatibility_level == doom_12_compatibility)
    return P_CheckSight_12(t1, t2);

  s1 = t1->subsector->sector;
  s2 = t2->subsector->sector;
  pnum = (s1->iSectorID)*numsectors + (s2->iSectorID);

  // First check for trivial rejection.
  // Determine subsector entries in REJECT table.
  //
  // Check in REJECT table.

  if (rejectmatrix[pnum>>3] & (1 << (pnum & 7))) // can't possibly be connected
    return false;

  // killough 4/19/98: make fake floors and ceilings block monster view

  if ((s1->heightsec != -1 &&
       ((t1->z + t1->height <= sectors[s1->heightsec].floorheight &&
         t2->z >= sectors[s1->heightsec].floorheight) ||
        (t1->z >= sectors[s1->heightsec].ceilingheight &&
         t2->z + t1->height <= sectors[s1->heightsec].ceilingheight)))
      ||
      (s2->heightsec != -1 &&
       ((t2->z + t2->height <= sectors[s2->heightsec].floorheight &&
         t1->z >= sectors[s2->heightsec].floorheight) ||
        (t2->z >= sectors[s2->heightsec].ceilingheight &&
         t1->z + t2->height <= sectors[s2->heightsec].ceilingheight)))) {
    return false;
  }

  /* killough 11/98: shortcut for melee situations
   * same subsector? obviously visible
   * cph - compatibility optioned for demo sync, cf HR06-UV.LMP */
  if ((t1->subsector == t2->subsector) &&
      (compatibility_level >= mbf_compatibility)) {
    return true;
  }

  // An unobstructed LOS is possible.
  // Now look from eyes of t1 to any part of t2.

  validcount++;

  los.topslope = (los.bottomslope = t2->z - (los.sightzstart =
                                             t1->z + t1->height -
                                             (t1->height>>2))) + t2->height;
  los.strace.dx = (los.t2x = t2->x) - (los.strace.x = t1->x);
  los.strace.dy = (los.t2y = t2->y) - (los.strace.y = t1->y);

  if (t1->x > t2->x)
    los.bbox[BOXRIGHT] = t1->x, los.bbox[BOXLEFT] = t2->x;
  else
    los.bbox[BOXRIGHT] = t2->x, los.bbox[BOXLEFT] = t1->x;

  if (t1->y > t2->y)
    los.bbox[BOXTOP] = t1->y, los.bbox[BOXBOTTOM] = t2->y;
  else
    los.bbox[BOXTOP] = t2->y, los.bbox[BOXBOTTOM] = t1->y;

  /* cph - calculate min and max z of the potential line of sight
   * For old demos, we disable this optimisation by setting them to
   * the extremes */
  if (compatibility_level == lxdoom_1_compatibility ||
      prboom_comp[PC_FORCE_LXDOOM_DEMO_COMPATIBILITY].state) {
    if (los.sightzstart < t2->z) {
      los.maxz = t2->z + t2->height;
      los.minz = los.sightzstart;
    }
    else if (los.sightzstart > t2->z + t2->height) {
      los.maxz = los.sightzstart;
      los.minz = t2->z;
    }
    else {
      los.maxz = t2->z + t2->height;
      los.minz = t2->z;
    }
  }
  else {
    los.maxz = INT_MAX;
    los.minz = INT_MIN;
  }

  // the head node is the last node output
  return P_CrossBSPNode(numnodes-1);
}

/* vi: set et ts=2 sw=2: */

