Logo Search packages:      
Sourcecode: warzone2100 version File versions

visibility.c

Go to the documentation of this file.
/*
      This file is part of Warzone 2100.
      Copyright (C) 1999-2004  Eidos Interactive
      Copyright (C) 2005-2007  Warzone Resurrection Project

      Warzone 2100 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.

      Warzone 2100 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 Warzone 2100; if not, write to the Free Software
      Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
 * @file visibility.c
 * Handles object visibility.
 * Pumpkin Studios, Eidos Interactive 1996.
 */
#include "lib/framework/frame.h"

#include "lib/gamelib/gtime.h"
#include "lib/sound/audio.h"
#include "lib/sound/audio_id.h"

#include "visibility.h"

#include "objects.h"
#include "map.h"
#include "loop.h"
#include "raycast.h"
#include "geometry.h"
#include "hci.h"
#include "mapgrid.h"
#include "cluster.h"
#include "scriptextern.h"
#include "structure.h"

#include "multiplay.h"
#include "advvis.h"


// accuracy for the height gradient
#define GRAD_MUL  10000

// which object is being considered by the callback
static SDWORD                 currObj;

// rate to change visibility level
const static float VIS_LEVEL_INC = 255 * 2;
const static float VIS_LEVEL_DEC = 50;

// fractional accumulator of how much to change visibility this frame
static float                  visLevelIncAcc, visLevelDecAcc;

// integer amount to change visiblility this turn
static SDWORD                 visLevelInc, visLevelDec;

// alexl's sensor range.
BOOL bDisplaySensorRange;

/* Variables for the visibility callback */
static SDWORD           rayPlayer;                    // The player the ray is being cast for
static SDWORD           startH;                             // The height at the view point
static SDWORD           currG;                              // The current obscuring gradient
static SDWORD           lastH, lastD;                 // The last height and distance
static BOOL             rayStart;                     // Whether this is the first point on the ray
static SDWORD           tarDist;                      // The distance to the ray target
static BOOL             blockingWall;                 // Whether walls block line of sight
static SDWORD           finalX,finalY;                // The final tile of the ray cast
static SDWORD           numWalls;                     // Whether the LOS has hit a wall
static SDWORD           wallX,wallY;                  // the position of a wall if it is on the LOS

// initialise the visibility stuff
BOOL visInitialise(void)
{
      visLevelIncAcc = 0;
      visLevelDecAcc = 0;
      visLevelInc = 0;
      visLevelDec = 0;

      return true;
}

// update the visibility change levels
void visUpdateLevel(void)
{
      visLevelIncAcc += timeAdjustedIncrement(VIS_LEVEL_INC, true);
      visLevelInc = visLevelIncAcc;
      visLevelIncAcc -= visLevelInc;
      visLevelDecAcc += timeAdjustedIncrement(VIS_LEVEL_DEC, true);
      visLevelDec = visLevelDecAcc;
      visLevelDecAcc -= visLevelDec;
}

static SDWORD visObjHeight(const BASE_OBJECT * const psObject)
{
      SDWORD      height;

      switch (psObject->type)
      {
      case OBJ_DROID:
            height = 80;
//          height = psObject->sDisplay.imd->pos.max.y;
            break;
      case OBJ_STRUCTURE:
            height = psObject->sDisplay.imd->max.y;
            break;
      case OBJ_FEATURE:
            height = psObject->sDisplay.imd->max.y;
            break;
      default:
            ASSERT( false,"visObjHeight: unknown object type" );
            height = 0;
            break;
      }

      return height;
}

/* The terrain revealing ray callback */
static BOOL rayTerrainCallback(SDWORD x, SDWORD y, SDWORD dist)
{
      SDWORD            newH, newG;       // The new gradient
      MAPTILE           *psTile;

      ASSERT(x >= 0
          && x < world_coord(mapWidth)
          && y >= 0
          && y < world_coord(mapHeight),
                  "rayTerrainCallback: coords off map" );

      psTile = mapTile(map_coord(x), map_coord(y));

      /* Not true visibility - done on sensor range */

      if (dist == 0)
      {
            debug(LOG_ERROR, "rayTerrainCallback: dist is 0, which is not a valid distance");
            dist = 1;
      }

      newH = psTile->height * ELEVATION_SCALE;
      newG = (newH - startH) * GRAD_MUL / (SDWORD)dist;
      if (newG >= currG)
      {
            currG = newG;

            SET_TILE_VISIBLE(rayPlayer, psTile);

            if (selectedPlayer != rayPlayer && bMultiPlayer && game.alliance == ALLIANCES_TEAMS
                && aiCheckAlliances(selectedPlayer, rayPlayer))
            {
                  SET_TILE_VISIBLE(selectedPlayer,psTile);        //reveal radar
            }

            /* Not true visibility - done on sensor range */
            if (rayPlayer == selectedPlayer
                || (bMultiPlayer && game.alliance == ALLIANCES_TEAMS
                  && aiCheckAlliances(selectedPlayer, rayPlayer)))
            {
                  // can see opponent moving
                  if(getRevealStatus())
                  {
                        avInformOfChange(map_coord(x), map_coord(y));         //reveal map
                  }
                  psTile->activeSensor = true;
            }
      }

      return true;
}

/* The los ray callback */
static BOOL rayLOSCallback(SDWORD x, SDWORD y, SDWORD dist)
{
      SDWORD            newG;       // The new gradient
      SDWORD            distSq;
      SDWORD            tileX,tileY;
      MAPTILE           *psTile;

      ASSERT(x >= 0
          && x < world_coord(mapWidth)
          && y >= 0
          && y < world_coord(mapHeight),
                  "rayLOSCallback: coords off map" );

      distSq = dist*dist;

      if (rayStart)
      {
            rayStart = false;
      }
      else
      {
            // Calculate the current LOS gradient
            newG = (lastH - startH) * GRAD_MUL / lastD;
            if (newG >= currG)
            {
                  currG = newG;
            }
      }

      // See if the ray has reached the target
      if (distSq >= tarDist)
      {
            lastD = dist;
            return false;
      }
      else
      {
            // Store the height at this tile for next time round
            tileX = map_coord(x);
            tileY = map_coord(y);

            if (blockingWall && !((tileX == finalX) && (tileY == finalY)))
            {
                  psTile = mapTile(tileX, tileY);
                  if (TileHasWall(psTile) && !TileHasSmallStructure(psTile))
                  {
                        lastH = 2*UBYTE_MAX * ELEVATION_SCALE;
      //                currG = UBYTE_MAX * ELEVATION_SCALE * GRAD_MUL / lastD;
                        numWalls += 1;
                        wallX = x;
                        wallY = y;
      //                return false;
                  }
                  else
                  {
                        lastH = map_Height((UDWORD)x, (UDWORD)y);
                  }
            }
            else
            {
                  lastH = map_Height((UDWORD)x, (UDWORD)y);
            }
            lastD = dist;
      }

      return true;
}

#define VTRAYSTEP (NUM_RAYS/120)
#define     DUPF_SCANTERRAIN 0x01

BOOL visTilesPending(BASE_OBJECT *psObj)
{
      ASSERT( psObj->type == OBJ_DROID,"visTilesPending : Only implemented for droids" );

      return (((DROID*)psObj)->updateFlags & DUPF_SCANTERRAIN);
}

/* Check which tiles can be seen by an object */
void visTilesUpdate(BASE_OBJECT *psObj)
{
      SDWORD      range = objSensorRange(psObj);
      SDWORD      ray;

      ASSERT(psObj->type != OBJ_FEATURE, "visTilesUpdate: visibility updates are not for features!");

      rayPlayer = psObj->player;

      // Do the whole circle.
      for(ray = 0; ray < NUM_RAYS; ray += NUM_RAYS / 80)
      {
            // initialise the callback variables
            startH = psObj->pos.z + visObjHeight(psObj);
            currG = -UBYTE_MAX * GRAD_MUL;

            // Cast the rays from the viewer
            rayCast(psObj->pos.x, psObj->pos.y,ray, range, rayTerrainCallback);
      }
}

/* Check whether psViewer can see psTarget.
 * psViewer should be an object that has some form of sensor,
 * currently droids and structures.
 * psTarget can be any type of BASE_OBJECT (e.g. a tree).
 * struckBlock controls whether structures block LOS
 */
BOOL visibleObject(const BASE_OBJECT* psViewer, const BASE_OBJECT* psTarget)
{
      SDWORD            x,y, ray;
      SDWORD            xdiff,ydiff, rangeSquared;
      SDWORD            range = objSensorRange(psViewer);
      SDWORD            tarG, top;

      ASSERT(psViewer != NULL, "Invalid viewer pointer!");
      ASSERT(psTarget != NULL, "Invalid viewed pointer!");
      if (!psViewer || !psTarget)
      {
            return false;
      }

      /* Get the sensor Range and power */
      switch (psViewer->type)
      {
      case OBJ_DROID:
      {
            const DROID * const psDroid = (const DROID *)psViewer;

            if (psDroid->droidType == DROID_COMMAND)
            {
                  range = 3 * range / 2;
            }
            break;
      }
      case OBJ_STRUCTURE:
      {
            const STRUCTURE * const psStruct = (const STRUCTURE *)psViewer;

            // a structure that is being built cannot see anything
            if (psStruct->status != SS_BUILT)
            {
                  return false;
            }

            if (psStruct->pStructureType->type == REF_WALL
             || psStruct->pStructureType->type == REF_WALLCORNER)
            {
                  return false;
            }

            if ((structCBSensor(psStruct)
              || structVTOLCBSensor(psStruct))
             && psStruct->psTarget[0] == psTarget)
            {
                  // if a unit is targetted by a counter battery sensor
                  // it is automatically seen
                  return true;
            }

            // increase the sensor range for AA sites
            // AA sites are defensive structures that can only shoot in the air
            if (psStruct->pStructureType->type == REF_DEFENSE
             && asWeaponStats[psStruct->asWeaps[0].nStat].surfaceToAir == SHOOT_IN_AIR)
            {
                  range = 3 * range / 2;
            }

            break;
      }
      default:
            ASSERT( false,
                  "visibleObject: visibility checking is only implemented for"
                  "units and structures" );
            return false;
            break;
      }

      // Structures can be seen from further away
      if (psTarget->type == OBJ_STRUCTURE)
      {
            range = 4 * range / 3;
      }

      /* First see if the target is in sensor range */
      x = psViewer->pos.x;
      xdiff = x - (SDWORD)psTarget->pos.x;
      if (xdiff < 0)
      {
            xdiff = -xdiff;
      }
      if (xdiff > range)
      {
            // too far away, reject
            return false;
      }

      y = psViewer->pos.y;
      ydiff = y - (SDWORD)psTarget->pos.y;
      if (ydiff < 0)
      {
            ydiff = -ydiff;
      }
      if (ydiff > range)
      {
            // too far away, reject
            return false;
      }

      rangeSquared = xdiff*xdiff + ydiff*ydiff;
      if (rangeSquared > (range*range))
      {
            /* Out of sensor range */
            return false;
      }

      if (rangeSquared == 0)
      {
            // Should never be on top of each other, but ...
            return true;
      }

      // initialise the callback variables
      startH = psViewer->pos.z;
      startH += visObjHeight(psViewer);
      currG = -UBYTE_MAX * GRAD_MUL * ELEVATION_SCALE;
      tarDist = rangeSquared;
      rayStart = true;
      currObj = 0;
      ray = NUM_RAYS-1 - calcDirection(psViewer->pos.x,psViewer->pos.y, psTarget->pos.x,psTarget->pos.y);
      finalX = map_coord(psTarget->pos.x);
      finalY = map_coord(psTarget->pos.y);

      // Cast a ray from the viewer to the target
      rayCast(x,y, ray, range, rayLOSCallback);

      // See if the target can be seen
      top = ((SDWORD)psTarget->pos.z + visObjHeight(psTarget) - startH);
      tarG = top * GRAD_MUL / lastD;

      return tarG >= currG;
}

// Do visibility check, but with walls completely blocking LOS.
BOOL visibleObjWallBlock(BASE_OBJECT *psViewer, BASE_OBJECT *psTarget)
{
      BOOL  result;

      blockingWall = true;
      result = visibleObject(psViewer,psTarget);
      blockingWall = false;

      return result;
}

// Find the wall that is blocking LOS to a target (if any)
STRUCTURE* visGetBlockingWall(const BASE_OBJECT* psViewer, const BASE_OBJECT* psTarget)
{
      blockingWall = true;
      numWalls = 0;
      visibleObject(psViewer, psTarget);
      blockingWall = false;

      // see if there was a wall in the way
      if (numWalls == 1)
      {
            const int tileX = map_coord(wallX);
            const int tileY = map_coord(wallY);
            unsigned int player;

            for (player = 0; player < MAX_PLAYERS; ++player)
            {
                  STRUCTURE* psWall;

                  for (psWall = apsStructLists[player]; psWall; psWall = psWall->psNext)
                  {
                        if (map_coord(psWall->pos.x) == tileX
                         && map_coord(psWall->pos.y) == tileY)
                        {
                              return psWall;
                        }
                  }
            }
      }

      return NULL;
}

/* Find out what can see this object */
void processVisibility(BASE_OBJECT *psObj)
{
      UDWORD            i;
      BOOL        prevVis[MAX_PLAYERS], currVis[MAX_PLAYERS];
      SDWORD            visLevel;
      BASE_OBJECT *psViewer;
      MESSAGE           *psMessage;
      UDWORD            player, ally;

      // initialise the visibility array
      for (i=0; i<MAX_PLAYERS; i++)
      {
            prevVis[i] = psObj->visible[i] != 0;
      }
      if (psObj->type == OBJ_DROID)
      {
            memset (currVis, 0, sizeof(BOOL) * MAX_PLAYERS);

            // one can trivially see oneself
            currVis[psObj->player]=true;
      }
      else
      {
            memcpy(currVis, prevVis, sizeof(BOOL) * MAX_PLAYERS);
      }

      // get all the objects from the grid the droid is in
      gridStartIterate((SDWORD)psObj->pos.x, (SDWORD)psObj->pos.y);

      // Make sure allies can see us
      if (bMultiPlayer && game.alliance == ALLIANCES_TEAMS)
      {
            for(player=0; player<MAX_PLAYERS; player++)
            {
                  if(player!=psObj->player)
                  {
                        if(aiCheckAlliances(player,psObj->player))
                        {
                              currVis[player] = true;
                        }
                  }
            }
      }

      // if a player has a SAT_UPLINK structure, they can see everything!
      for (player=0; player<MAX_PLAYERS; player++)
      {
            if (getSatUplinkExists(player))
            {
                  currVis[player] = true;
                  if (psObj->visible[player] == 0)
                  {
                        psObj->visible[player] = 1;
                  }
            }
      }

      psViewer = gridIterate();
      while (psViewer != NULL)
      {
            // If we've got ranged line of sight...
            if ( (psViewer->type != OBJ_FEATURE) &&
                   !currVis[psViewer->player] &&
                   visibleObject(psViewer, psObj) )
            {
                  // Tell system that this side can see this object
                  currVis[psViewer->player]=true;
                  if (!prevVis[psViewer->player])
                  {

                        if (psObj->visible[psViewer->player] == 0)
                        {
                              psObj->visible[psViewer->player] = 1;
                        }
                        if(psObj->type != OBJ_FEATURE)
                        {
                              // features are not in the cluster system
                              clustObjectSeen(psObj, psViewer);
                        }
                  }

            }

            psViewer = gridIterate();
      }

      //forward out vision to our allies
      if (bMultiPlayer && game.alliance == ALLIANCES_TEAMS)
      {
            for(player = 0; player < MAX_PLAYERS; player++)
            {
                  for(ally = 0; ally < MAX_PLAYERS; ally++)
                  {
                        if (currVis[player] && aiCheckAlliances(player, ally))
                        {
                              currVis[ally] = true;
                        }
                  }
            }
      }

      // update the visibility levels
      for(i=0; i<MAX_PLAYERS; i++)
      {
            if (i == psObj->player)
            {
                  // owner can always see it fully
                  psObj->visible[i] = UBYTE_MAX;
                  continue;
            }

            visLevel = 0;
            if (currVis[i])
            {
                  visLevel = UBYTE_MAX;
            }

            if ( (visLevel < psObj->visible[i]) &&
                   (psObj->type == OBJ_DROID) )
            {
                  if (psObj->visible[i] <= visLevelDec)
                  {
                        psObj->visible[i] = 0;
                  }
                  else
                  {
                        psObj->visible[i] = (UBYTE)(psObj->visible[i] - visLevelDec);
                  }
            }
            else if (visLevel > psObj->visible[i])
            {
                  if (psObj->visible[i] + visLevelInc >= UBYTE_MAX)
                  {
                        psObj->visible[i] = UBYTE_MAX;
                  }
                  else
                  {
                        psObj->visible[i] = (UBYTE)(psObj->visible[i] + visLevelInc);
                  }
            }
      }

      /* Make sure all tiles under a feature/structure become visible when you see it */
      for(i=0; i<MAX_PLAYERS; i++)
      {
            if( (psObj->type == OBJ_STRUCTURE || psObj->type == OBJ_FEATURE) &&
                  (!prevVis[i] && psObj->visible[i]) )
            {
                  setUnderTilesVis(psObj,i);
            }
      }

      // if a feature has just become visible set the tile flags
      if (psObj->type == OBJ_FEATURE && !prevVis[selectedPlayer] && psObj->visible[selectedPlayer])
      {
            /*if this is an oil resource we want to add a proximity message for
            the selected Player - if there isn't an Resource Extractor on it*/
            if (((FEATURE *)psObj)->psStats->subType == FEAT_OIL_RESOURCE)
            {
                  if(!TileHasStructure(mapTile(map_coord(psObj->pos.x),
                                                 map_coord(psObj->pos.y))))
                  {
                        psMessage = addMessage(MSG_PROXIMITY, true, selectedPlayer);
                        if (psMessage)
                        {
                              psMessage->pViewData = (MSG_VIEWDATA *)psObj;
                        }
                        if(!bInTutorial)
                        {
                              //play message to indicate been seen
                              audio_QueueTrackPos( ID_SOUND_RESOURCE_HERE,
                                                            psObj->pos.x, psObj->pos.y, psObj->pos.z );
                        }
                        debug(LOG_MSG, "Added message for oil well, pViewData=%p", psMessage->pViewData);
                  }
            }

                  if (((FEATURE *)psObj)->psStats->subType == FEAT_GEN_ARTE)
                  {
                        psMessage = addMessage(MSG_PROXIMITY, true, selectedPlayer);
                        if (psMessage)
                        {
                              psMessage->pViewData = (MSG_VIEWDATA *)psObj;
                        }
                        if(!bInTutorial)
                        {
                              //play message to indicate been seen
                              audio_QueueTrackPos( ID_SOUND_ARTEFACT_DISC,
                                                      psObj->pos.x, psObj->pos.y, psObj->pos.z );
                        }
                        debug(LOG_MSG, "Added message for artefact, pViewData=%p", psMessage->pViewData);
                  }
      }
}

void  setUnderTilesVis(BASE_OBJECT *psObj,UDWORD player)
{
UDWORD            i,j;
UDWORD            mapX, mapY, width,breadth;
FEATURE           *psFeature;
STRUCTURE   *psStructure;
FEATURE_STATS     *psStats;
MAPTILE           *psTile;

      if(psObj->type == OBJ_FEATURE)
      {
            psFeature = (FEATURE*)psObj;
            psStats = psFeature->psStats;
            width = psStats->baseWidth;
            breadth = psStats->baseBreadth;
            mapX = map_coord(psFeature->pos.x - width * TILE_UNITS / 2);
            mapY = map_coord(psFeature->pos.y - breadth * TILE_UNITS / 2);
      }
      else
      {
            /* Must be a structure */
            psStructure = (STRUCTURE*)psObj;
            width = psStructure->pStructureType->baseWidth;
            breadth = psStructure->pStructureType->baseBreadth;
            mapX = map_coord(psStructure->pos.x - width * TILE_UNITS / 2);
            mapY = map_coord(psStructure->pos.y - breadth * TILE_UNITS / 2);
      }

      for (i = 0; i < width; i++)
      {
            for (j = 0; j < breadth; j++)
            {

                  /* Slow fade up */
                  if(getRevealStatus())
                  {
                        if(player == selectedPlayer)
                        {
                              avInformOfChange(mapX+i,mapY+j);
                        }
                  }

                  psTile = mapTile(mapX+i,mapY+j);
                  SET_TILE_VISIBLE(player, psTile);
            }
      }
}

void updateSensorDisplay()
{
      MAPTILE           *psTile = psMapTiles;
      int         x;
      DROID       *psDroid;
      STRUCTURE   *psStruct;

      // clear sensor info
      for (x = 0; x < mapWidth * mapHeight; x++)
      {
            psTile->activeSensor = false;
            psTile++;
      }

      // process the sensor range of all droids/structs.

      // units.
      for(psDroid = apsDroidLists[selectedPlayer];psDroid;psDroid=psDroid->psNext)
      {
            visTilesUpdate((BASE_OBJECT*)psDroid);
      }

      // structs.
      for(psStruct = apsStructLists[selectedPlayer];psStruct;psStruct=psStruct->psNext)
      {
            if (psStruct->pStructureType->type != REF_WALL
                && psStruct->pStructureType->type != REF_WALLCORNER)
            {
                  visTilesUpdate((BASE_OBJECT*)psStruct);
            }
      }
}

Generated by  Doxygen 1.6.0   Back to index