Logo Search packages:      
Sourcecode: warzone2100 version File versions

display3d.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 display3d.c
 * Draws the 3D terrain view. Both the 3D and pseudo-3D components - textured tiles.

      -------------------------------------------------------------------
      -     Alex McLean & Jeremy Sallis, Pumpkin Studios, EIDOS INTERACTIVE -
      -------------------------------------------------------------------
*/
#include "lib/framework/frame.h"
#include "lib/framework/math-help.h"

/* Includes direct access to render library */
#include "lib/ivis_common/piedef.h"
#include "lib/ivis_common/tex.h"
#include "lib/ivis_common/piestate.h"
#include "lib/ivis_common/piepalette.h"
#include "lib/ivis_opengl/piematrix.h"
#include "lib/ivis_common/piemode.h"
#include "lib/ivis_common/piefunc.h"
#include "lib/ivis_common/rendmode.h"

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

#include "e3demo.h"
#include "loop.h"
#include "atmos.h"
#include "raycast.h"
#include "levels.h"
#include "map.h"
#include "move.h"
#include "visibility.h"
#include "geometry.h"
#include "messagedef.h"
#include "miscimd.h"
#include "effects.h"
#include "edit3d.h"
#include "feature.h"
#include "hci.h"
#include "display.h"
#include "intdisplay.h"
#include "radar.h"
#include "display3d.h"
#include "lighting.h"
#include "console.h"
#include "projectile.h"
#include "bucket3d.h"
#include "intelmap.h"
#include "mapdisplay.h"
#include "message.h"
#include "component.h"
#include "warcam.h"
#include "scripttabs.h"
#include "scriptextern.h"
#include "scriptcb.h"
#include "target.h"
#include "keymap.h"
#include "drive.h"
#include "gateway.h"
#include "transporter.h"
#include "warzoneconfig.h"
#include "action.h"
#include "keybind.h"
#include "combat.h"
#include "order.h"
#include "scores.h"
#include "multiplay.h"
#include "environ.h"
#include "advvis.h"
#include "texture.h"
#include "anim_id.h"
#include "cmddroid.h"


#define WATER_ALPHA_LEVEL 255 //was 164   // Amount to alpha blend water.
#define WATER_ZOFFSET 32            // Sorting offset for main water tile.
#define WATER_EDGE_ZOFFSET 64 // Sorting offset for water edge tiles.
#define WATER_DEPTH     127               // Amount to push terrain below water.

/********************  Prototypes  ********************/

static UDWORD     getTargettingGfx(void);
static void drawDroidGroupNumber(DROID *psDroid);
static void trackHeight(float desiredHeight);
static void renderSurroundings(void);
static void locateMouse(void);
static void preprocessTiles(void);
static BOOL renderWallSection(STRUCTURE *psStructure);
static void drawDragBox(void);
static void calcFlagPosScreenCoords(SDWORD *pX, SDWORD *pY, SDWORD *pR);
static void flipsAndRots(unsigned int tileNumber, unsigned int i, unsigned int j);
static void displayTerrain(void);
static iIMDShape  *flattenImd(iIMDShape *imd, UDWORD structX, UDWORD structY, UDWORD direction);
static void drawTiles(iView *player);
static void display3DProjectiles(void);
static void drawDroidSelections(void);
static void drawStructureSelections(void);
static void displayAnimation(ANIM_OBJECT * psAnimObj, BOOL bHoldOnFirstFrame);
static void processSensorTarget(void);
static void processDestinationTarget(void);
static BOOL eitherSelected(DROID *psDroid);
static void structureEffects(void);
static void showDroidSensorRanges(void);
static void showSensorRange2(BASE_OBJECT *psObj);
static void drawRangeAtPos(SDWORD centerX, SDWORD centerY, SDWORD radius);
static void addConstructionLine(DROID *psDroid, STRUCTURE *psStructure);
static void doConstructionLines(void);
static void drawDroidCmndNo(DROID *psDroid);
static void drawDroidRank(DROID *psDroid);
static void drawDroidSensorLock(DROID *psDroid);
static void calcAverageTerrainHeight(iView *player);
BOOL  doWeDrawProximitys(void);

static void drawTerrainTile(UDWORD i, UDWORD j, BOOL onWaterEdge);
static void drawTerrainWaterTile(UDWORD i, UDWORD j);

/********************  Variables  ********************/
// Should be cleaned up properly and be put in structures.

BOOL  bRender3DOnly;
static BOOL bRangeDisplay = false;
static SDWORD     rangeCenterX,rangeCenterY,rangeRadius;
static BOOL bDrawProximitys = true;
BOOL  godMode;
BOOL  showGateways = false;
BOOL  showPath = false;

static char skyboxPageName[PATH_MAX] = "page-25";

static float waterRealValue = 0.0f;
#define WAVE_SPEED 0.015f

// When to display HP bars
UWORD barMode;

/* Have we made a selection by clicking the mouse - used for dragging etc */
BOOL  selectAttempt = false;

/* Vectors that hold the player and camera directions and positions */
iView player;

/* Temporary rotation vectors to store rotations for droids etc */
static Vector3i   imdRot,imdRot2;

/* How far away are we from the terrain */
UDWORD            distance;

/* Stores the screen coordinates of the transformed terrain tiles */
static TERRAIN_VERTEX tileScreenInfo[VISIBLE_YTILES+1][VISIBLE_XTILES+1];

/* Records the present X and Y values for the current mouse tile (in tiles */
SDWORD mouseTileX, mouseTileY;

/* Do we want the radar to be rendered */
BOOL  radarOnScreen=false;

/* Show unit/building gun/sensor range*/
bool rangeOnScreen = false;  // For now, most likely will change later!  -Q 5-10-05   A very nice effect - Per

/* Temporary values for the terrain render - top left corner of grid to be rendered */
static int playerXTile, playerZTile;

/* Have we located the mouse? */
static BOOL mouseLocated = true;

/* The box used for multiple selection - present screen coordinates */
static UDWORD currentGameFrame;
static UDWORD numTiles = 0;
static SDWORD tileZ = 8000;
static QUAD dragQuad;

/* temporary buffer used for flattening IMDs */
static Vector3f alteredPoints[iV_IMD_MAX_POINTS];

//number of tiles visible
// FIXME This should become dynamic! (A function of resolution, angle and zoom maybe.)
Vector2i visibleTiles = { VISIBLE_XTILES, VISIBLE_YTILES };

UDWORD      terrainMidX;
UDWORD      terrainMidY;

static unsigned int underwaterTile = WATER_TILE;
static unsigned int rubbleTile = BLOCKING_RUBBLE_TILE;

bool showFPS = false;       // default OFF, turn ON via console command 'showfps'
bool showSAMPLES = false;   // default OFF, turn ON via console command 'showsamples'
UDWORD geoOffset;

static int averageCentreTerrainHeight;

static UDWORD     lastTargetAssignation = 0;
static UDWORD     lastDestAssignation = 0;
static BOOL bSensorTargetting = false;
static BOOL bDestTargetting = false;
static BASE_OBJECT *psSensorObj = NULL;
static UDWORD     destTargetX,destTargetY;
static UDWORD     destTileX=0,destTileY=0;

#define     TARGET_TO_SENSOR_TIME   ((4*(GAME_TICKS_PER_SEC))/5)
#define     DEST_TARGET_TIME  (GAME_TICKS_PER_SEC/4)
#define STRUCTURE_ANIM_RATE 4

/* Colour strobe values for the strobing drag selection box */
#define BOX_PULSE_SIZE  10

/********************  Functions  ********************/

static void displayMultiChat(void)
{
      UDWORD      pixelLength;
      UDWORD      pixelHeight;

      pixelLength = iV_GetTextWidth(sTextToSend);
      pixelHeight = iV_GetTextLineSize();

      if((gameTime2 % 500) < 250)
      {
            // implement blinking cursor in multiplayer chat
            pie_BoxFill(RET_X + pixelLength + 3, 474 + E_H - (pixelHeight/4), RET_X + pixelLength + 10, 473 + E_H, WZCOL_CURSOR);
      }

      /* FIXME: GET RID OF THE MAGIC NUMBERS BELOW */
      iV_TransBoxFill(RET_X + 1, 474 + E_H - pixelHeight, RET_X + 1 + pixelLength + 2, 473 + E_H);

      iV_DrawText(sTextToSend, RET_X + 3, 469 + E_H);
}

// Optimisation to stop it being calculated every frame
static SDWORD     gridCentreX,gridCentreZ,gridVarCalls;
SDWORD      getCentreX( void )
{
      gridVarCalls++;
      return(gridCentreX);
}

SDWORD      getCentreZ( void )
{
      return(gridCentreZ);
}

static void showDroidPaths(void)
{
      DROID *psDroid;

      if (((gameTime2 / 250) % 2) != 0)
      {
            return;
      }

      for (psDroid = apsDroidLists[selectedPlayer]; psDroid; psDroid=psDroid->psNext)
      {
            if (psDroid->selected)
            {
                  int   i;
                  int   len = psDroid->sMove.numPoints;

                  i = MAX(psDroid->sMove.Position-1,0);
                  for (; i < len; i++)
                  {
                        Vector3i pos;

                        ASSERT(tileOnMap(psDroid->sMove.asPath[i].x, psDroid->sMove.asPath[i].y), "Path off map!");
                        pos.x = world_coord(psDroid->sMove.asPath[i].x) + TILE_UNITS / 2;
                        pos.z = world_coord(psDroid->sMove.asPath[i].y) + TILE_UNITS / 2;
                        pos.y = map_Height(pos.x, pos.z) + 16;

                        ASSERT(worldOnMap(pos.x, pos.y), "Effect off map!");
                        effectGiveAuxVar(80);
                        addEffect(&pos, EFFECT_EXPLOSION, EXPLOSION_TYPE_LASER, false, NULL, 0);
                  }
            }
      }
}

/* Render the 3D world */
void draw3DScene( void )
{
      BOOL bPlayerHasHQ = false;

      // the world centre - used for decaying lighting etc
      gridCentreX = player.p.x + world_coord(visibleTiles.x / 2);
      gridCentreZ = player.p.z + world_coord(visibleTiles.y / 2);

      /* What frame number are we on? */
      currentGameFrame = frameGetFrameNumber();

      /* Build the drag quad */
      if(dragBox3D.status == DRAG_RELEASED)
      {
            dragQuad.coords[0].x = dragBox3D.x1; // TOP LEFT
            dragQuad.coords[0].y = dragBox3D.y1;

            dragQuad.coords[1].x = dragBox3D.x2; // TOP RIGHT
            dragQuad.coords[1].y = dragBox3D.y1;

            dragQuad.coords[2].x = dragBox3D.x2; // BOTTOM RIGHT
            dragQuad.coords[2].y = dragBox3D.y2;

            dragQuad.coords[3].x = dragBox3D.x1; // BOTTOM LEFT
            dragQuad.coords[3].y = dragBox3D.y2;
      }

      /* Calculate the position of the sun */
//    findSunVector();

      pie_Begin3DScene();
      /* Set 3D world origins */
      pie_SetGeometricOffset((rendSurface.width >> 1), geoOffset);

      // draw sky and fogbox
      renderSurroundings();

      // draw terrain
      displayTerrain();

      pie_BeginInterface();
      updateLightLevels();
      drawDroidSelections();

      drawStructureSelections();

      bPlayerHasHQ = getHQExists(selectedPlayer);

      if(radarOnScreen && bPlayerHasHQ)
      {
            pie_SetDepthBufferStatus(DEPTH_CMP_ALWAYS_WRT_ON);
            pie_SetFogStatus(false);
            drawRadar();
            pie_SetDepthBufferStatus(DEPTH_CMP_LEQ_WRT_ON);
            pie_SetFogStatus(true);
      }

      if(!bRender3DOnly)
      {
            /* Ensure that any text messages are displayed at bottom of screen */
            pie_SetFogStatus(false);
            displayConsoleMessages();
      }
      pie_SetDepthBufferStatus(DEPTH_CMP_ALWAYS_WRT_OFF);
      pie_SetFogStatus(false);
      iV_SetTextColour(WZCOL_TEXT_BRIGHT);

      /* Dont remove this folks!!!! */
      if(!bAllowOtherKeyPresses)
      {
            displayMultiChat();
      }
      if (showSAMPLES)        //Displays the number of sound samples we currently have
      {
            unsigned int width, height;
            const char *Qbuf, *Lbuf, *Abuf;

            sasprintf((char**)&Qbuf,"Que: %04u",audio_GetSampleQueueCount());
            sasprintf((char**)&Lbuf,"Lst: %04u",audio_GetSampleListCount());
            sasprintf((char**)&Abuf,"Act: %04u",sound_GetActiveSamplesCount());
            width = iV_GetTextWidth(Qbuf) + 11;
            height = iV_GetTextHeight(Qbuf);

            iV_DrawText(Qbuf, pie_GetVideoBufferWidth() - width, height + 2);
            iV_DrawText(Lbuf, pie_GetVideoBufferWidth() - width, height + 48);
            iV_DrawText(Abuf, pie_GetVideoBufferWidth() - width, height + 59);
      }
      if (showFPS)
      {
            unsigned int width, height;
            const char* fps;
            sasprintf((char**)&fps, "FPS: %u", frameGetAverageRate());
            width = iV_GetTextWidth(fps) + 10;
            height = iV_GetTextHeight(fps);

            iV_DrawText(fps, pie_GetVideoBufferWidth() - width, pie_GetVideoBufferHeight() - height);
      }
      if(getDebugMappingStatus() && !demoGetStatus() && !gamePaused())
      {
            iV_DrawText( "DEBUG ", RET_X + 134, 440 + E_H );
      }
      else
      {
#ifdef DEBUG
            if(!gamePaused())
            {
                  char buildInfo[255];
                  iV_DrawText( getLevelName(), RET_X + 134, 420 + E_H );
                  getAsciiTime(buildInfo,gameTime);
                  iV_DrawText( buildInfo, RET_X + 134, 434 + E_H );
            }
#endif
      }

      while(player.r.y>DEG(360))
      {
            player.r.y-=DEG(360);
      }

      /* If we don't have an active camera track, then track terrain height! */
      if(!getWarCamStatus())
      {
            /* Move the autonomous camera if necessary */
            calcAverageTerrainHeight(&player);
            trackHeight(averageCentreTerrainHeight+CAMERA_PIVOT_HEIGHT);
      }
      else
      {
            processWarCam();
      }

      if(demoGetStatus())
      {
            flushConsoleMessages();
            setConsolePermanence(true, true);
            permitNewConsoleMessages(true);
            addConsoleMessage("Warzone 2100 : Pumpkin Studios ", RIGHT_JUSTIFY,SYSTEM_MESSAGE);
            permitNewConsoleMessages(false);
      }

#ifdef ALEXM
      iV_DrawTextF(100, 200, "Skipped effects : %d", getNumSkippedEffects());
      iV_DrawTextF(100, 220, "Miss Count : %d", getMissCount());
      iV_DrawTextF(100, 240, "Even effects : %d", getNumEvenEffects());
#endif

      processDemoCam();
      processSensorTarget();
      processDestinationTarget();

      structureEffects(); // add fancy effects to structures

      showDroidSensorRanges(); //shows sensor data for units/droids/whatever...-Q 5-10-05

      //visualize radius if needed
      if (bRangeDisplay)
      {
            drawRangeAtPos(rangeCenterX,rangeCenterY,rangeRadius);
      }

      if (showPath)
      {
            showDroidPaths();
      }
}


/* Draws the 3D textured terrain */
static void displayTerrain(void)
{
      tileZ = 8000;

      /* We haven't yet located which tile mouse is over */
      mouseLocated = false;

      numTiles = 0;

      pie_PerspectiveBegin();

      /* Setup tiles */
      preprocessTiles();

      /* Now, draw the terrain */
      drawTiles(&player);

      pie_PerspectiveEnd();

      /* Show the drag Box if necessary */
      drawDragBox();

      /* Have we released the drag box? */
      if(dragBox3D.status == DRAG_RELEASED)
      {
            dragBox3D.status = DRAG_INACTIVE;
      }
}

/***************************************************************************/
BOOL  doWeDrawProximitys( void )
{
      return(bDrawProximitys);
}
/***************************************************************************/
void  setProximityDraw(BOOL val)
{
      bDrawProximitys = val;
}
/***************************************************************************/

static void calcAverageTerrainHeight(iView *player)
{
      int numTilesAveraged = 0, i, j;

      /*    We track the height here - so make sure we get the average heights
            of the tiles directly underneath us
      */
      averageCentreTerrainHeight = 0;
      for (i = visibleTiles.y / 2 - 4; i < visibleTiles.y / 2 + 4; i++)
      {
            for (j = visibleTiles.x / 2 - 4; j < visibleTiles.x / 2 + 4; j++)
            {
                  if (tileOnMap(playerXTile + j, playerZTile + i))
                  {
                        /* Get a pointer to the tile at this location */
                        MAPTILE *psTile = mapTile(playerXTile + j, playerZTile + i);

                        averageCentreTerrainHeight += psTile->height * ELEVATION_SCALE;
                        numTilesAveraged++;
                  }
            }
      }
      /* Work out the average height. We use this information to keep the player camera
       * above the terrain. */
      if (numTilesAveraged) // might not be if off map
      {
            MAPTILE *psTile = mapTile(playerXTile + visibleTiles.x / 2, playerZTile + visibleTiles.y / 2);

            averageCentreTerrainHeight /= numTilesAveraged;
            if (averageCentreTerrainHeight < psTile->height * ELEVATION_SCALE)
            {
                  averageCentreTerrainHeight = psTile->height * ELEVATION_SCALE;
            }
      }
      else
      {
            averageCentreTerrainHeight = ELEVATION_SCALE * TILE_UNITS;
      }
}

PIELIGHT getTileColour(int x, int y)
{
      return mapTile(x, y)->colour;
}

void setTileColour(int x, int y, PIELIGHT colour)
{
      MAPTILE *psTile = mapTile(x, y);

      psTile->colour = colour;
}

static void drawTiles(iView *player)
{
      UDWORD i, j;
      SDWORD rx, rz;

      if (bDisplaySensorRange)
      {
            updateSensorDisplay();
      }

      // Animate the water texture, just cycles the V coordinate through half the tiles height.
      if(!gamePaused())
      {
            waterRealValue += timeAdjustedIncrement(WAVE_SPEED, false);
            if (waterRealValue >= (1.0f / TILES_IN_PAGE_ROW) / 2)
            {
                  waterRealValue = 0.0f;
            }
      }

      /* ---------------------------------------------------------------- */
      /* Do boundary and extent checking                                  */
      /* ---------------------------------------------------------------- */
      /* Get the mid point of the grid */
      terrainMidX = visibleTiles.x/2;
      terrainMidY = visibleTiles.y/2;

      /* Find our position in tile coordinates */
      playerXTile = map_coord(player->p.x);
      playerZTile = map_coord(player->p.z);

      /* Get the x,z translation components */
      rx = (player->p.x) & (TILE_UNITS-1);
      rz = (player->p.z) & (TILE_UNITS-1);

      /* ---------------------------------------------------------------- */
      /* Set up the geometry                                              */
      /* ---------------------------------------------------------------- */

      /* ---------------------------------------------------------------- */
      /* Push identity matrix onto stack */
      pie_MatBegin();

      // Now, scale the world according to what resolution we're running in
      pie_MatScale(pie_GetResScalingFactor());

      /* Set the camera position */
      pie_MATTRANS(0, 0, distance);

      /* Rotate for the player */
      pie_MatRotZ(player->r.z);
      pie_MatRotX(player->r.x);
      pie_MatRotY(player->r.y);

      /* Translate */
      pie_TRANSLATE(-rx, -player->p.y, rz);

      if (getDrawShadows())
      {
            Vector3f theSun = getTheSun();
            // this also detemines the length of the shadows
            pie_BeginLighting(&theSun);
      }

      /* ---------------------------------------------------------------- */
      /* Rotate and project all the tiles within the grid                 */
      /* ---------------------------------------------------------------- */
      for (i = 0; i < visibleTiles.y+1; i++)
      {
            /* Go through the x's */
            for (j = 0; j < visibleTiles.x+1; j++)
            {
                  Vector2i screen;
                  PIELIGHT TileIllum = WZCOL_BLACK;
                  int shiftVal = 0;

                  tileScreenInfo[i][j].pos.x = world_coord(j - terrainMidX);
                  tileScreenInfo[i][j].pos.z = world_coord(terrainMidY - i);
                  tileScreenInfo[i][j].pos.y = 0;

                  if (!tileOnMap(playerXTile + j, playerZTile + i))
                  {
                        // Special past-edge-of-map tiles
                        tileScreenInfo[i][j].u = 0;
                        tileScreenInfo[i][j].v = 0;
                  }
                  else
                  {
                        BOOL bEdgeTile = false;
                        MAPTILE *psTile = mapTile(playerXTile + j, playerZTile + i);
                        BOOL pushedDown = false;

                        tileScreenInfo[i][j].pos.y = map_TileHeight(playerXTile + j, playerZTile + i);

                        if (getRevealStatus() && !godMode)
                        {
                              TileIllum = pal_SetBrightness(psTile->level < 0 ? 1 : psTile->level);
                        }
                        else
                        {
                              TileIllum = pal_SetBrightness(psTile->illumination);
                        }

                        // Real fog of war - darken where we cannot see enemy units moving around
                        if (bDisplaySensorRange && !psTile->activeSensor)
                        {
                              TileIllum.byte.r = TileIllum.byte.r / 2;
                              TileIllum.byte.g = TileIllum.byte.g / 2;
                              TileIllum.byte.b = TileIllum.byte.b / 2;
                        }

                        if ( playerXTile+j <= 1 ||
                               playerZTile+i <= 1 ||
                               playerXTile+j >= mapWidth-2 ||
                               playerZTile+i >= mapHeight-2 )
                        {
                              bEdgeTile = true;
                        }

                        // If it's the main water tile (has water texture) then..
                        if (TileNumber_tile(psTile->texture) == WATER_TILE && !bEdgeTile)
                        {
                              // Push the terrain down for the river bed.
                              shiftVal = WATER_DEPTH + environGetData(playerXTile+j, playerZTile+i) * 1.5f;
                              tileScreenInfo[i][j].pos.y -= shiftVal;
                              // And darken it.
                              TileIllum.byte.r = (TileIllum.byte.r * 2) / 3;
                              TileIllum.byte.g = (TileIllum.byte.g * 2) / 3;
                              TileIllum.byte.b = (TileIllum.byte.b * 2) / 3;
                              pushedDown = true;
                        }

                        // to prevent a sharp edge to the map, make sure the border is black
                        if (i == 0 || j == 0 || i == visibleTiles.y || j == visibleTiles.x)
                        {
                              TileIllum.byte.r = 0;
                              TileIllum.byte.g = 0;
                              TileIllum.byte.b = 0;
                        }
                        // and fade towards the black border gradually
                        if (j == 1)
                        {
                              TileIllum.byte.r = TileIllum.byte.r*(TILE_UNITS-rx)/TILE_UNITS;
                              TileIllum.byte.g = TileIllum.byte.g*(TILE_UNITS-rx)/TILE_UNITS;
                              TileIllum.byte.b = TileIllum.byte.b*(TILE_UNITS-rx)/TILE_UNITS;
                        }
                        if (j == visibleTiles.x-1)
                        {
                              TileIllum.byte.r = TileIllum.byte.r*(rx)/TILE_UNITS;
                              TileIllum.byte.g = TileIllum.byte.g*(rx)/TILE_UNITS;
                              TileIllum.byte.b = TileIllum.byte.b*(rx)/TILE_UNITS;
                        }
                        if (i == 1)
                        {
                              TileIllum.byte.r = TileIllum.byte.r*(TILE_UNITS-rz)/TILE_UNITS;
                              TileIllum.byte.g = TileIllum.byte.g*(TILE_UNITS-rz)/TILE_UNITS;
                              TileIllum.byte.b = TileIllum.byte.b*(TILE_UNITS-rz)/TILE_UNITS;
                        }
                        if (i == visibleTiles.y-1)
                        {
                              TileIllum.byte.r = TileIllum.byte.r*(rz)/TILE_UNITS;
                              TileIllum.byte.g = TileIllum.byte.g*(rz)/TILE_UNITS;
                              TileIllum.byte.b = TileIllum.byte.b*(rz)/TILE_UNITS;
                        }

                        setTileColour(playerXTile + j, playerZTile + i, TileIllum);
                  }
                  // hack since tileScreenInfo[i][j].screen is Vector3i and pie_RotateProject takes Vector2i as 2nd param
                  screen.x = tileScreenInfo[i][j].screen.x;
                  screen.y = tileScreenInfo[i][j].screen.y;
                  tileScreenInfo[i][j].screen.z = pie_RotateProject(&tileScreenInfo[i][j].pos, &screen);
                  tileScreenInfo[i][j].screen.x = screen.x;
                  tileScreenInfo[i][j].screen.y = screen.y;
            }
      }

      /* This is done here as effects can light the terrain - pause mode problems though */
      processEffects();
      atmosUpdateSystem();

      if(getRevealStatus())
      {
            avUpdateTiles();
      }

      // Draw all the normal tiles
      pie_SetAlphaTest(false);
      pie_SetFogStatus(true);
      pie_SetTexturePage(terrainPage);
      for (i = 0; i < visibleTiles.y; i++)
      {
            for (j = 0; j < visibleTiles.x; j++)
            {
                  //get distance of furthest corner
                  int zMax = MAX(tileScreenInfo[i][j].screen.z, tileScreenInfo[i+1][j].screen.z);
                  zMax = MAX(zMax, tileScreenInfo[i + 1][j + 1].screen.z);
                  zMax = MAX(zMax, tileScreenInfo[i][j + 1].screen.z);

                  if (zMax < 0)
                  {
                        // clipped
                        continue;
                  }

                  drawTerrainTile(i, j, false);
            }
      }
      pie_DrawTerrain(MAX(0, playerXTile), MAX(0, playerZTile),
                        MIN(playerXTile + visibleTiles.x, mapWidth - 1),  MIN(playerZTile + visibleTiles.y, mapHeight - 1));

      // Update height for water
      for (i = 0; i < visibleTiles.y + 1; i++)
      {
            /* Go through the x's */
            for (j = 0; j < visibleTiles.x + 1; j++)
            {
                  if (!(playerXTile + j < 0 || playerZTile + i < 0 || playerXTile + j > (SDWORD)(mapWidth - 1)
                        || playerZTile + i > (SDWORD)(mapHeight - 1)))
                  {
                        tileScreenInfo[i][j].pos.y = map_TileHeight(playerXTile + j, playerZTile + i);
                  }
                  else
                  {
                        tileScreenInfo[i][j].pos.y = 0;
                  }
            }
      }

      // Draw water edges
      pie_SetDepthOffset(-2.0);
      pie_SetAlphaTest(true);
      for (i = 0; i < MIN(visibleTiles.y, mapHeight); i++)
      {
            for (j = 0; j < MIN(visibleTiles.x, mapWidth); j++)
            {
                  MAPTILE *psTile;

                  if (!tileOnMap(playerXTile + j, playerZTile + i))
                  {
                        continue;
                  }
                  psTile = mapTile(playerXTile + j, playerZTile + i);

                  // check if we need to draw a water edge
                  if (terrainType(psTile) == TER_WATER && TileNumber_tile(psTile->texture) != WATER_TILE)
                  {
                        //get distance of furthest corner
                        int zMax = MAX(tileScreenInfo[i][j].screen.z, tileScreenInfo[i + 1][j].screen.z);
                        zMax = MAX(zMax, tileScreenInfo[i + 1][j + 1].screen.z);
                        zMax = MAX(zMax, tileScreenInfo[i][j + 1].screen.z);

                        if (zMax < 0)
                        {
                              // clipped
                              continue;
                        }

                        // the edge is in front of the water (which is drawn at z-index -1)
                        drawTerrainTile(i, j, true);
                  }
            }
      }

      // Now draw the water tiles in a second pass to get alpha sort order correct
      pie_TranslateTextureBegin(Vector2f_New(0.0f, waterRealValue));
      pie_SetRendMode(REND_ALPHA_TEX);
      pie_SetAlphaTest(false);
      pie_SetDepthOffset(-1.0f);
      for (i = 0; i < MIN(visibleTiles.y, mapHeight ); i++)
      {
            for (j = 0; j < MIN(visibleTiles.x, mapWidth); j++)
            {
                  MAPTILE *psTile;

                  if (!tileOnMap(playerXTile + j, playerZTile + i))
                  {
                        continue;
                  }
                  psTile = mapTile(playerXTile + j, playerZTile + i);

                  if (terrainType(psTile) == TER_WATER)
                  {
                        //get distance of furthest corner
                        int zMax = MAX(tileScreenInfo[i][j].screen.z, tileScreenInfo[i + 1][j].screen.z);
                        zMax = MAX(zMax, tileScreenInfo[i + 1][j + 1].screen.z);
                        zMax = MAX(zMax, tileScreenInfo[i][j + 1].screen.z);

                        if (zMax < 0)
                        {
                              // clipped
                              continue;
                        }

                        drawTerrainWaterTile(i, j);
                  }
            }
      }
      pie_SetDepthOffset(0.0f);
      pie_SetRendMode(REND_GOURAUD_TEX);
      pie_SetAlphaTest(true);
      pie_TranslateTextureEnd();

      targetOpenList((BASE_OBJECT*)driveGetDriven());

      /* ---------------------------------------------------------------- */
      /* Now display all the static objects                               */
      /* ---------------------------------------------------------------- */
      displayStaticObjects(); // bucket render implemented
      displayFeatures(); // bucket render implemented
      displayDynamicObjects(); //bucket render implemented
      if(doWeDrawProximitys())
      {
            displayProximityMsgs(); // bucket render implemented
      }
      displayDelivPoints(); // bucket render implemented
      display3DProjectiles(); // bucket render implemented

      atmosDrawParticles();

      bucketRenderCurrentList();
      pie_RemainingPasses();
      pie_EndLighting();

#ifdef ARROWS
      arrowDrawAll();
#endif

      targetCloseList();

      if(driveModeActive()) {
            // If were in driving mode then mark the current target.
            if(targetGetCurrent() != NULL) {
                  targetMarkCurrent();
            }
      }
      if(!gamePaused())
      {
            doConstructionLines();
      }

      /* Clear the matrix stack */
      iV_MatrixEnd();
      locateMouse();
}


BOOL init3DView(void)
{
      /* Arbitrary choice - from direct read! */
      Vector3f theSun = { 225.0f, -600.0f, 450.0f };

      setTheSun(theSun);

      // the world centre - used for decaying lighting etc
      gridCentreX = player.p.x + world_coord(visibleTiles.x / 2);
      gridCentreZ = player.p.z + world_coord(visibleTiles.y / 2);

      /* Base Level */
      geoOffset = 192;

      /* There are no drag boxes */
      dragBox3D.status = DRAG_INACTIVE;

      /* Make sure and change these to comply with map.c */
      imdRot.x = -35;

      /* Initialize vertex arrays */
      pie_TerrainInit(mapWidth, mapHeight);

      /* Get all the init stuff out of here? */
      initWarCam();

      /* Init the game messaging system */
      initConsoleMessages();

      atmosInitSystem();

      // Set the initial fog distance
      UpdateFogDistance(distance);

      initDemoCamera();

      /* Set up the sine table for the bullets */
      initBulletTable();

      /* No initial rotations */
      imdRot2.x = 0;
      imdRot.y = 0;
      imdRot2.z = 0;

      bRender3DOnly = false;

      targetInitialise();

      pie_InitSkybox(iV_GetTexture(skyboxPageName));

      // distance is not saved, so initialise it now
      distance = START_DISTANCE; // distance

      return true;
}


// set the view position from save game
void disp3d_setView(iView *newView)
{
      memcpy(&player,newView,sizeof(iView));
}

// reset the camera rotation (used for save games <= 10)
void disp3d_resetView()
{
      player.r.z = 0; // roll
      player.r.y = INITIAL_DESIRED_ROTATION; // rotation
      player.r.x = DEG(360 + INITIAL_STARTING_PITCH); // angle

      // and set the camera position
      player.p.y = START_HEIGHT; // height
}

// get the view position for save game
void disp3d_getView(iView *newView)
{
      memcpy(newView,&player,sizeof(iView));
}

/* John's routine - deals with flipping around the vertex ordering for source textures
when flips and rotations are being done */
static void flipsAndRots(unsigned int tileNumber, unsigned int i, unsigned int j)
{
      /* unmask proper values from compressed data */
      const unsigned short texture = TileNumber_texture(tileNumber);
      const unsigned short tile = TileNumber_tile(tileNumber);

      /* Used to calculate texture coordinates */
      const float xMult = 1.0f / TILES_IN_PAGE_COLUMN;
      const float yMult = 1.0f / TILES_IN_PAGE_ROW;
      const float one = 1.0f / (TILES_IN_PAGE_COLUMN * (float)getTextureSize());

      /*
       * Points for flipping the texture around if the tile is flipped or rotated
       * Store the source rect as four points
       */
      Vector2f
            sP1 = { one, one },
            sP2 = { xMult - one, one },
            sP3 = { xMult - one, yMult - one },
            sP4 = { one, yMult - one },
            sPTemp;

      if (texture & TILE_XFLIP)
      {
            sPTemp = sP1;
            sP1 = sP2;
            sP2 = sPTemp;

            sPTemp = sP3;
            sP3 = sP4;
            sP4 = sPTemp;
      }
      if (texture & TILE_YFLIP)
      {
            sPTemp = sP1;
            sP1 = sP4;
            sP4 = sPTemp;
            sPTemp = sP2;
            sP2 = sP3;
            sP3 = sPTemp;
      }

      switch ((texture & TILE_ROTMASK) >> TILE_ROTSHIFT)
      {
            case 1:
                  sPTemp = sP1;
                  sP1 = sP4;
                  sP4 = sP3;
                  sP3 = sP2;
                  sP2 = sPTemp;
                  break;
            case 2:
                  sPTemp = sP1;
                  sP1 = sP3;
                  sP3 = sPTemp;
                  sPTemp = sP4;
                  sP4 = sP2;
                  sP2 = sPTemp;
                  break;
            case 3:
                  sPTemp = sP1;
                  sP1 = sP2;
                  sP2 = sP3;
                  sP3 = sP4;
                  sP4 = sPTemp;
                  break;
      }

      tileScreenInfo[i + 0][j + 0].u = tileTexInfo[tile].uOffset + sP1.x;
      tileScreenInfo[i + 0][j + 0].v = tileTexInfo[tile].vOffset + sP1.y;

      tileScreenInfo[i + 0][j + 1].u = tileTexInfo[tile].uOffset + sP2.x;
      tileScreenInfo[i + 0][j + 1].v = tileTexInfo[tile].vOffset + sP2.y;

      tileScreenInfo[i + 1][j + 1].u = tileTexInfo[tile].uOffset + sP3.x;
      tileScreenInfo[i + 1][j + 1].v = tileTexInfo[tile].vOffset + sP3.y;

      tileScreenInfo[i + 1][j + 0].u = tileTexInfo[tile].uOffset + sP4.x;
      tileScreenInfo[i + 1][j + 0].v = tileTexInfo[tile].vOffset + sP4.y;
}


/* Clips anything - not necessarily a droid */
BOOL clipXY(SDWORD x, SDWORD y)
{
      if (x > (SDWORD)player.p.x &&  x < (SDWORD)(player.p.x+(visibleTiles.x * TILE_UNITS)) &&
            y > (SDWORD)player.p.z && y < (SDWORD)(player.p.z+(visibleTiles.y*TILE_UNITS)))
            return(true);
      else
            return(false);
}


/*    Get the onscreen corrdinates of a Object Position so we can draw a 'button' in
the Intelligence screen.  VERY similar to above function*/
static void calcFlagPosScreenCoords(SDWORD *pX, SDWORD *pY, SDWORD *pR)
{
      /* Get it's absolute dimensions */
      Vector3i center3d = {0, 0, 0};
      Vector2i center2d = {0, 0};
      /* How big a box do we want - will ultimately be calculated using xmax, ymax, zmax etc */
      UDWORD      radius = 22;

      /* Pop matrices and get the screen coordinates for last point*/
      pie_RotateProject( &center3d, &center2d );

      /*store the coords*/
      *pX = center2d.x;
      *pY = center2d.y;
      *pR = radius;
}


/* Renders the bullets and their effects in 3D */
static void display3DProjectiles( void )
{
      PROJECTILE        *psObj;

      psObj = proj_GetFirst();

      while ( psObj != NULL )
      {
            switch(psObj->state)
            {
            case PROJ_INFLIGHT:
                  // if source or destination is visible
                  if(gfxVisible(psObj))
                  {
                        /* don't display first frame of trajectory (projectile on firing object) */
                        if ( gameTime != psObj->born )
                        {
                              /* Draw a bullet at psObj->pos.x for X coord
                                                            psObj->pos.y for Z coord
                                                            whatever for Y (height) coord - arcing ?
                              */
                              /* these guys get drawn last */
                              if(psObj->psWStats->weaponSubClass == WSC_ROCKET ||
                                    psObj->psWStats->weaponSubClass == WSC_MISSILE ||
                                    psObj->psWStats->weaponSubClass == WSC_COMMAND ||
                                    psObj->psWStats->weaponSubClass == WSC_SLOWMISSILE ||
                                    psObj->psWStats->weaponSubClass == WSC_SLOWROCKET ||
                                    psObj->psWStats->weaponSubClass == WSC_ENERGY ||
                                    psObj->psWStats->weaponSubClass == WSC_EMP)
                              {
                                    bucketAddTypeToList(RENDER_PROJECTILE, psObj);
                              }
                              else
                              {
                                    renderProjectile(psObj);
                              }
                        }
                  }
                  break;

            case PROJ_IMPACT:
                  break;

            case PROJ_POSTIMPACT:
                  break;

            default:
                  break;
            }     /* end switch */
            psObj = proj_GetNext();
      }
}     /* end of function display3DProjectiles */


void  renderProjectile(PROJECTILE *psCurr)
{
      WEAPON_STATS      *psStats;
      Vector3i                dv;
      iIMDShape         *pIMD;
      SDWORD                  rx, rz;

      psStats = psCurr->psWStats;
      /* Reject flame or command since they have interim drawn fx */
      if(psStats->weaponSubClass == WSC_FLAME ||
            psStats->weaponSubClass == WSC_COMMAND || // || psStats->weaponSubClass == WSC_ENERGY)
            psStats->weaponSubClass == WSC_ELECTRONIC ||
            psStats->weaponSubClass == WSC_EMP ||
            (bMultiPlayer && psStats->weaponSubClass == WSC_LAS_SAT))
      {
            /* We don't do projectiles from these guys, cos there's an effect instead */
            return;
      }


      //the weapon stats holds the reference to which graphic to use
      /*Need to draw the graphic depending on what the projectile is doing - hitting target,
      missing target, in flight etc - JUST DO IN FLIGHT FOR NOW! */
      pIMD = psStats->pInFlightGraphic;

      if (clipXY(psCurr->pos.x,psCurr->pos.y))
      {
            /* Get bullet's x coord */
            dv.x = (psCurr->pos.x - player.p.x) - terrainMidX*TILE_UNITS;

            /* Get it's y coord (z coord in the 3d world */
            dv.z = terrainMidY*TILE_UNITS - (psCurr->pos.y - player.p.z);

            /* What's the present height of the bullet? */
            dv.y = psCurr->pos.z;
            /* Set up the matrix */
            iV_MatrixBegin();

            /* Translate to the correct position */
            iV_TRANSLATE(dv.x,dv.y,dv.z);
            /* Get the x,z translation components */
            rx = player.p.x & (TILE_UNITS-1);
            rz = player.p.z & (TILE_UNITS-1);

            /* Translate */
            iV_TRANSLATE(rx,0,-rz);

            /* Rotate it to the direction it's facing */
            imdRot2.y = DEG( (int)psCurr->direction );
            iV_MatrixRotateY(-imdRot2.y);

            /* pitch it */
            imdRot2.x = DEG(psCurr->pitch);
            iV_MatrixRotateX(imdRot2.x);

            if (psStats->weaponSubClass == WSC_ROCKET || psStats->weaponSubClass == WSC_MISSILE
                || psStats->weaponSubClass == WSC_SLOWROCKET || psStats->weaponSubClass == WSC_SLOWMISSILE)
            {
                  pie_Draw3DShape(pIMD, 0, 0, WZCOL_WHITE, WZCOL_BLACK, pie_ADDITIVE, 164);
            }
            else
            {
                  pie_Draw3DShape(pIMD, 0, 0, WZCOL_WHITE, WZCOL_BLACK, pie_NO_BILINEAR, 0);
            }

            iV_MatrixEnd();
      }
      /* Flush matrices */
}

void
renderAnimComponent( const COMPONENT_OBJECT *psObj )
{
      BASE_OBJECT *psParentObj = (BASE_OBJECT*)psObj->psParent;
      const SDWORD posX = psParentObj->pos.x + psObj->position.x,
            posY = psParentObj->pos.y + psObj->position.y;
      SWORD rx, rz;

      ASSERT( psParentObj != NULL, "renderAnimComponent: invalid parent object pointer" );

      /* only draw visible bits */
      if( (psParentObj->type == OBJ_DROID) && !godMode && !demoGetStatus() &&
            ((DROID*)psParentObj)->visible[selectedPlayer] != UBYTE_MAX )
      {
            return;
      }

      /* render */
      if( clipXY( posX, posY ) )
      {
            /* get parent object translation */
            const Vector3i dv = {
                  (psParentObj->pos.x - player.p.x) - terrainMidX * TILE_UNITS,
                  psParentObj->pos.z,
                  terrainMidY * TILE_UNITS - (psParentObj->pos.y - player.p.z)
            };
            SDWORD iPlayer;
            PIELIGHT brightness;

            psParentObj->sDisplay.frameNumber = currentGameFrame;

            /* Push the indentity matrix */
            iV_MatrixBegin();

            /* parent object translation */
            iV_TRANSLATE(dv.x, dv.y, dv.z);

            /* Get the x,z translation components */
            rx = player.p.x & (TILE_UNITS-1);
            rz = player.p.z & (TILE_UNITS-1);

            /* Translate */
            iV_TRANSLATE(rx, 0, -rz);

            /* parent object rotations */
            imdRot2.y = DEG( (int)psParentObj->direction );
            iV_MatrixRotateY(-imdRot2.y);
            imdRot2.x = DEG(psParentObj->pitch);
            iV_MatrixRotateX(imdRot2.x);

            /* object (animation) translations - ivis z and y flipped */
            iV_TRANSLATE( psObj->position.x, psObj->position.z, psObj->position.y );

            /* object (animation) rotations */
            iV_MatrixRotateY( -psObj->orientation.z );
            iV_MatrixRotateZ( -psObj->orientation.y );
            iV_MatrixRotateX( -psObj->orientation.x );

            /* Set frame numbers - look into this later?? FIXME!!!!!!!! */
            if( psParentObj->type == OBJ_DROID )
            {
                  DROID *psDroid = (DROID*)psParentObj;
                  if ( psDroid->droidType == DROID_PERSON )
                  {
                        iPlayer = psParentObj->player - 6;
                        pie_MatScale(75);
                  }
                  else
                  {
                        iPlayer = getPlayerColour(psParentObj->player);
                  }

                  /* Get the onscreen coordinates so we can draw a bounding box */
                  calcScreenCoords( psDroid );
                  targetAdd((BASE_OBJECT*)psDroid);
            }
            else
            {
                  iPlayer = getPlayerColour(psParentObj->player);
            }

            //brightness and fog calculation
            if (psParentObj->type == OBJ_STRUCTURE)
            {
                  const Vector3i zero = {0, 0, 0};
                  Vector2i s = {0, 0};
                  STRUCTURE *psStructure = (STRUCTURE*)psParentObj;

                  brightness = pal_SetBrightness(200 - 100 * getStructureDamage(psStructure));

                  pie_RotateProject( &zero, &s );
                  psStructure->sDisplay.screenX = s.x;
                  psStructure->sDisplay.screenY = s.y;

                  targetAdd((BASE_OBJECT*)psStructure);
            }
            else
            {
                  brightness = pal_SetBrightness(UBYTE_MAX);
            }

            if(getRevealStatus() && !godMode)
            {
                  brightness = pal_SetBrightness(avGetObjLightLevel((BASE_OBJECT*)psParentObj, brightness.byte.r));
            }

            pie_Draw3DShape(psObj->psShape, 0, iPlayer, brightness, WZCOL_BLACK, pie_NO_BILINEAR|pie_STATIC_SHADOW, 0);

            /* clear stack */
            iV_MatrixEnd();
      }
}


/* Draw the buildings */
void displayStaticObjects( void )
{
      STRUCTURE   *psStructure;
      UDWORD            clan;
      UDWORD            test = 0;
      ANIM_OBJECT *psAnimObj;

      // to solve the flickering edges of baseplates
      pie_SetDepthOffset(-1.0f);

      /* Go through all the players */
      for (clan = 0; clan < MAX_PLAYERS; clan++)
      {
            /* Now go all buildings for that player */
            for(psStructure = apsStructLists[clan]; psStructure != NULL;
                  psStructure = psStructure->psNext)
            {
                  test++;
                  /* Worth rendering the structure? */
                  if(clipXY(psStructure->pos.x,psStructure->pos.y))
                  {
                        if ( psStructure->pStructureType->type == REF_RESOURCE_EXTRACTOR &&
                              psStructure->psCurAnim == NULL &&
                              (psStructure->currentBuildPts > (SDWORD)psStructure->pStructureType->buildPoints) )
                        {
                              psStructure->psCurAnim = animObj_Add( psStructure, ID_ANIM_DERIK, 0, 0 );
                        }

                        if ( psStructure->psCurAnim == NULL ||
                                    psStructure->psCurAnim->bVisible == false ||
                                    (psAnimObj = animObj_Find( psStructure,
                                    psStructure->psCurAnim->uwID )) == NULL )
                        {
                              renderStructure(psStructure);
                        }
                        else
                        {
                              if ( psStructure->visible[selectedPlayer] || godMode )
                              {
                                    //check not a resource extractors
                                    if (psStructure->pStructureType->type !=
                                          REF_RESOURCE_EXTRACTOR)
                                    {
                                          displayAnimation( psAnimObj, false );
                                    }
                                    //check that a power gen exists before animationg res extrac
                                    //else if (getPowerGenExists(psStructure->player))
                                    /*check the building is active*/
                                    else if (psStructure->pFunctionality->resourceExtractor.active)
                                    {
                                          displayAnimation( psAnimObj, false );
                                          if(selectedPlayer == psStructure->player)
                                          {
                                                audio_PlayObjStaticTrack( (void *) psStructure, ID_SOUND_OIL_PUMP_2 );
                                          }
                                    }
                                    else
                                    {
                                          /* hold anim on first frame */
                                          displayAnimation( psAnimObj, true );
                                          audio_StopObjTrack( (void *) psStructure, ID_SOUND_OIL_PUMP_2 );
                                    }

                              }
                        }
                  }
            }
      }
      pie_SetDepthOffset(0.0f);
}

//draw Factory Delivery Points
void displayDelivPoints(void)
{
      FLAG_POSITION     *psDelivPoint;

      //only do the selected players'
      /* go through all DPs for that player */
      for(psDelivPoint = apsFlagPosLists[selectedPlayer]; psDelivPoint != NULL;
            psDelivPoint = psDelivPoint->psNext)
      {
            if (clipXY(psDelivPoint->coords.x, psDelivPoint->coords.y))
            {
                  renderDeliveryPoint(psDelivPoint);
            }
      }
}

/* Draw the features */
void displayFeatures( void )
{
FEATURE     *psFeature;
UDWORD            clan;

            /* player can only be 0 for the features */
            clan = 0;

            /* Go through all the features */
            for(psFeature = apsFeatureLists[clan]; psFeature != NULL;
                  psFeature = psFeature->psNext)
            {
                  /* Is the feature worth rendering? */
                  if(clipXY(psFeature->pos.x,psFeature->pos.y))
                  {
                        renderFeature(psFeature);
                  }
            }
}

/* Draw the Proximity messages for the **SELECTED PLAYER ONLY***/
void displayProximityMsgs( void )
{
      PROXIMITY_DISPLAY *psProxDisp;
      VIEW_PROXIMITY          *pViewProximity;
      UDWORD                        x, y;

      /* Go through all the proximity Displays*/
      for (psProxDisp = apsProxDisp[selectedPlayer]; psProxDisp != NULL;
            psProxDisp = psProxDisp->psNext)
      {
            if(!(psProxDisp->psMessage->read))
            {
                  if (psProxDisp->type == POS_PROXDATA)
                  {
                        pViewProximity = (VIEW_PROXIMITY*)((VIEWDATA *)psProxDisp->psMessage->
                              pViewData)->pData;
                        x = pViewProximity->x;
                        y = pViewProximity->y;
                  }
                  else
                  {
                        x = ((BASE_OBJECT *)psProxDisp->psMessage->pViewData)->pos.x;
                        y = ((BASE_OBJECT *)psProxDisp->psMessage->pViewData)->pos.y;
                  }
                  /* Is the Message worth rendering? */
                  if(clipXY(x,y))
                  {
                        renderProximityMsg(psProxDisp);
                  }
            }
      }
}

static void displayAnimation( ANIM_OBJECT * psAnimObj, BOOL bHoldOnFirstFrame )
{
      UWORD i, uwFrame;
      Vector3i vecPos, vecRot, vecScale;
      COMPONENT_OBJECT *psComp;

      for ( i = 0; i < psAnimObj->psAnim->uwObj; i++ )
      {
            if ( bHoldOnFirstFrame == true )
            {
                  uwFrame = 0;
                  vecPos.x = vecPos.y = vecPos.z = 0;
                  vecRot.x = vecRot.y = vecRot.z = 0;
                  vecScale.x = vecScale.y = vecScale.z = 0;
            }
            else
            {
                  uwFrame = anim_GetFrame3D( psAnimObj->psAnim, i, gameTime, psAnimObj->udwStartTime, psAnimObj->udwStartDelay, &vecPos, &vecRot, &vecScale );
            }

            if ( uwFrame != ANIM_DELAYED )
            {
                  if ( psAnimObj->psAnim->animType == ANIM_3D_TRANS )
                  {
                        psComp = &psAnimObj->apComponents[i];
                  }
                  else
                  {
                        psComp = &psAnimObj->apComponents[uwFrame];
                  }

                  psComp->position.x = vecPos.x;
                  psComp->position.y = vecPos.y;
                  psComp->position.z = vecPos.z;

                  psComp->orientation.x = vecRot.x;
                  psComp->orientation.y = vecRot.y;
                  psComp->orientation.z = vecRot.z;

                  bucketAddTypeToList( RENDER_ANIMATION, psComp );
            }
      }
}

/* Draw the droids */
void displayDynamicObjects( void )
{
      DROID       *psDroid;
      ANIM_OBJECT *psAnimObj;
      UDWORD            clan;

      /* Need to go through all the droid lists */
      for(clan = 0; clan < MAX_PLAYERS; clan++)
      {
            for(psDroid = apsDroidLists[clan]; psDroid != NULL;
                  psDroid = psDroid->psNext)
            {
                  /* Find out whether the droid is worth rendering */
                        if(clipXY(psDroid->pos.x,psDroid->pos.y))
                        {
                              /* No point in adding it if you can't see it? */
                              if(psDroid->visible[selectedPlayer] || godMode || demoGetStatus())
                              {
                                    psDroid->sDisplay.frameNumber = currentGameFrame;

                                    // NOTE! : anything that has multiple (anim) frames *must* use the bucket to render
                                    // In this case, AFAICT only DROID_CYBORG_SUPER had the issue.  (Same issue as oil pump anim)
                                    if (psDroid->droidType != DROID_CYBORG_SUPER)
                                    {
                                          renderDroid(psDroid);
                                    }
                                    else
                                    {     
                                          bucketAddTypeToList(RENDER_DROID, psDroid);
                                    }
                                    /* draw anim if visible */
                                    if ( psDroid->psCurAnim != NULL &&
                                          psDroid->psCurAnim->bVisible == true &&
                                          (psAnimObj = animObj_Find( psDroid,
                                          psDroid->psCurAnim->uwID )) != NULL )
                                    {
                                          displayAnimation( psAnimObj, false );
                                    }
                              }
                        } // end clipDroid
            } // end for
      } // end for clan
} // end Fn

/* Sets the player's position and view angle - defaults player rotations as well */
void setViewPos( UDWORD x, UDWORD y, WZ_DECL_UNUSED BOOL Pan )
{
      SDWORD midX,midY;

      /* Find centre of grid thats actually DRAWN */
      midX = x-(visibleTiles.x/2);
      midY = y-(visibleTiles.y/2);

      player.p.x = midX*TILE_UNITS;
      player.p.z = midY*TILE_UNITS;
      player.r.z = 0;

      if(getWarCamStatus())
      {
            camToggleStatus();
      }

      scroll();
}

void getPlayerPos(SDWORD *px, SDWORD *py)
{
      *px = player.p.x + (visibleTiles.x/2)*TILE_UNITS;
      *py = player.p.z + (visibleTiles.y/2)*TILE_UNITS;
}

void setPlayerPos(SDWORD x, SDWORD y)
{
      SDWORD midX,midY;


      ASSERT( (x >= 0) && (x < (SDWORD)(mapWidth*TILE_UNITS)) &&
                  (y >= 0) && (y < (SDWORD)(mapHeight*TILE_UNITS)),
            "setPlayerPos: position off map" );

      // Find centre of grid thats actually DRAWN
      midX = map_coord(x) - visibleTiles.x / 2;
      midY = map_coord(y) - visibleTiles.y / 2;

      player.p.x = midX*TILE_UNITS;
      player.p.z = midY*TILE_UNITS;
      player.r.z = 0;
}


void  setViewAngle(SDWORD angle)
{
      player.r.x = DEG(360 + angle);
}


UDWORD getViewDistance(void)
{
      return distance;
}

void  setViewDistance(UDWORD dist)
{
      dist = distance;
}


void  renderFeature(FEATURE *psFeature)
{
      UDWORD            featX,featY;
      SDWORD            rotation, rx, rz;
      PIELIGHT    brightness;
      Vector3i dv;
      Vector3f *vecTemp;
      BOOL bForceDraw = ( !getRevealStatus() && psFeature->psStats->visibleAtStart);
      int shadowFlags = 0;

      if (psFeature->visible[selectedPlayer] || godMode || demoGetStatus() || bForceDraw)
      {
            psFeature->sDisplay.frameNumber = currentGameFrame;
            /* Get it's x and y coordinates so we don't have to deref. struct later */
            featX = psFeature->pos.x;
            featY = psFeature->pos.y;
            /* Daft hack to get around the oild derrick issue */
            if (!TileHasFeature(mapTile(map_coord(featX), map_coord(featY))))
            {
                  return;
            }
            dv.x = (featX - player.p.x) - terrainMidX*TILE_UNITS;
            dv.z = terrainMidY*TILE_UNITS - (featY - player.p.z);

            /* features sits at the height of the tile it's centre is on */
            dv.y = psFeature->pos.z;

            /* Push the indentity matrix */
            iV_MatrixBegin();

            /* Translate the feature  - N.B. We can also do rotations here should we require
            buildings to face different ways - Don't know if this is necessary - should be IMO */
            iV_TRANSLATE(dv.x,dv.y,dv.z);
            /* Get the x,z translation components */
            rx = player.p.x & (TILE_UNITS-1);
            rz = player.p.z & (TILE_UNITS-1);

            /* Translate */
            iV_TRANSLATE(rx,0,-rz);
            rotation = DEG( (int)psFeature->direction );

            iV_MatrixRotateY(-rotation);

            brightness = pal_SetBrightness(200); //? HUH?

            if(psFeature->psStats->subType == FEAT_SKYSCRAPER)
            {
                  objectShimmy((BASE_OBJECT*)psFeature);
            }

            if(godMode || demoGetStatus() || bForceDraw)
            {
                  brightness = pal_SetBrightness(200);
            }
            else if(getRevealStatus())
            {
                  brightness = pal_SetBrightness(avGetObjLightLevel((BASE_OBJECT*)psFeature, brightness.byte.r));
            }

            if (psFeature->psStats->subType == FEAT_BUILDING || psFeature->psStats->subType == FEAT_SKYSCRAPER
                || psFeature->psStats->subType == FEAT_OIL_DRUM)
            {
                  // these cast a shadow
                  shadowFlags = pie_STATIC_SHADOW;
            }

            if(psFeature->psStats->subType == FEAT_OIL_RESOURCE)
            {
                  vecTemp = psFeature->sDisplay.imd->points;
                  flattenImd(psFeature->sDisplay.imd, psFeature->pos.x, psFeature->pos.y, 0);
                  /* currentGameFrame/2 set anim running - GJ hack */
                  pie_Draw3DShape(psFeature->sDisplay.imd, currentGameFrame/2, 0, brightness, WZCOL_BLACK, 0, 0);
                  psFeature->sDisplay.imd->points = vecTemp;
            }
            else
            {
                  pie_Draw3DShape(psFeature->sDisplay.imd, 0, 0, brightness, WZCOL_BLACK, shadowFlags,0);
            }

            {
                  Vector3i zero = {0, 0, 0};
                  Vector2i s = {0, 0};

                  pie_RotateProject( &zero, &s );
                  psFeature->sDisplay.screenX = s.x;
                  psFeature->sDisplay.screenY = s.y;

                  targetAdd((BASE_OBJECT*)psFeature);
            }

            iV_MatrixEnd();
      }
}

void renderProximityMsg(PROXIMITY_DISPLAY *psProxDisp)
{
      UDWORD                  msgX = 0, msgY = 0;
      Vector3i                dv = { 0, 0, 0 };
      VIEW_PROXIMITY    *pViewProximity = NULL;
      SDWORD                  x, y, r, rx, rz;
      iIMDShape         *proxImd = NULL;

      //store the frame number for when deciding what has been clicked on
      psProxDisp->frameNumber = currentGameFrame;

      /* Get it's x and y coordinates so we don't have to deref. struct later */
      if (psProxDisp->type == POS_PROXDATA)
      {
            pViewProximity = (VIEW_PROXIMITY*)((VIEWDATA *)psProxDisp->psMessage->
                  pViewData)->pData;
            if (pViewProximity)
            {
                  msgX = pViewProximity->x;
                  msgY = pViewProximity->y;
                  /* message sits at the height specified at input*/
                  dv.y = pViewProximity->z + 64;

                  /* in case of a beacon message put above objects */
                  if(((VIEWDATA *)psProxDisp->psMessage->pViewData)->type == VIEW_BEACON)
                  {
                        if(TileIsOccupied(mapTile(msgX / TILE_UNITS,msgY / TILE_UNITS)))
                              dv.y = pViewProximity->z + 150;
                  }

            }
      }
      else if (psProxDisp->type == POS_PROXOBJ)
      {
            msgX = ((BASE_OBJECT *)psProxDisp->psMessage->pViewData)->pos.x;
            msgY = ((BASE_OBJECT *)psProxDisp->psMessage->pViewData)->pos.y;
            /* message sits at the height specified at input*/
            dv.y = ((BASE_OBJECT *)psProxDisp->psMessage->pViewData)->pos.z + 64;
      }
      else
      {
            ASSERT(!"unknown proximity display message type", "Buggered proximity message type");
      }

      dv.x = (msgX - player.p.x) - terrainMidX*TILE_UNITS;
      dv.z = terrainMidY*TILE_UNITS - (msgY - player.p.z);

      /* Push the indentity matrix */
      iV_MatrixBegin();

      /* Translate the message */
      iV_TRANSLATE(dv.x,dv.y,dv.z);
      /* Get the x,z translation components */
      rx = player.p.x & (TILE_UNITS-1);
      rz = player.p.z & (TILE_UNITS-1);

      /* Translate */
      iV_TRANSLATE(rx,0,-rz);
      //get the appropriate IMD
      if (pViewProximity)
      {
            switch(pViewProximity->proxType)
            {
            case PROX_ENEMY:
                  proxImd = getImdFromIndex(MI_BLIP_ENEMY);
                  break;
            case PROX_RESOURCE:
                  proxImd = getImdFromIndex(MI_BLIP_RESOURCE);
                  break;
            case PROX_ARTEFACT:
                  proxImd = getImdFromIndex(MI_BLIP_ARTEFACT);
                  break;
            default:
                  ASSERT(!"unknown proximity display message type", "Buggered proximity message type");
                  break;
            }
      }
      else
      {
            //object Proximity displays are for oil resources and artefacts
            ASSERT( ((BASE_OBJECT *)psProxDisp->psMessage->pViewData)->type ==
                  OBJ_FEATURE, "renderProximityMsg: invalid feature" );

            if (((FEATURE *)psProxDisp->psMessage->pViewData)->psStats->subType ==
                  FEAT_OIL_RESOURCE)
            {
                  //resource
                  proxImd = getImdFromIndex(MI_BLIP_RESOURCE);
            }
            else
            {
                  //artefact
                  proxImd = getImdFromIndex(MI_BLIP_ARTEFACT);
            }
      }

      iV_MatrixRotateY(-player.r.y);
      iV_MatrixRotateX(-player.r.x);

      if(!gamePaused())
      {
            pie_Draw3DShape(proxImd, getTimeValueRange(1000,4), 0, WZCOL_WHITE, WZCOL_BLACK, pie_ADDITIVE, 192);
      }
      else
      {
            pie_Draw3DShape(proxImd, 0, 0, WZCOL_WHITE, WZCOL_BLACK, pie_ADDITIVE, 192);
      }

      //get the screen coords for determining when clicked on
      calcFlagPosScreenCoords(&x, &y, &r);
      psProxDisp->screenX = x;
      psProxDisp->screenY = y;
      psProxDisp->screenR = r;

      iV_MatrixEnd();
}

// Draw the structures
void  renderStructure(STRUCTURE *psStructure)
{
      SDWORD                  structX, structY, rx, rz;
      SDWORD                  rotation;
      SDWORD                  frame;
      SDWORD                  playerFrame;
      SDWORD                  animFrame;
      UDWORD                  nWeaponStat;
      PIELIGHT          buildingBrightness;
      Vector3i          dv;
      SDWORD                  i;
      Vector3f          *temp = NULL;
      BOOL              bHitByElectronic = false;
      BOOL              defensive = false;
      iIMDShape         *strImd = psStructure->sDisplay.imd;

      if (psStructure->pStructureType->type == REF_WALL || psStructure->pStructureType->type == REF_WALLCORNER)
      {
            renderWallSection(psStructure);
            return;
      }
      else if (!psStructure->visible[selectedPlayer] && !godMode && !demoGetStatus())
      {
            return;
      }
      else if (psStructure->pStructureType->type == REF_DEFENSE)
      {
            defensive = true;
      }

      playerFrame = getPlayerColour(psStructure->player);

      /* Power stations and factories have pulsing lights  */
      if (!defensive && psStructure->sDisplay.imd->numFrames > 0)
      {
            /*OK, so we've got a hack for a new structure - its a 2x2 wall but
            we've called it a BLAST_DOOR cos we don't want it to use the wallDrag code
            So its got clan colour trim and not really an anim - these HACKS just keep
            coming back to haunt us hey? - AB 02/09/99*/
            if (bMultiPlayer && psStructure->pStructureType->type == REF_BLASTDOOR)
            {
                  animFrame = getPlayerColour( psStructure->player );
            }
            else
            {
                  //calculate an animation frame
                  animFrame = (gameTime % (STRUCTURE_ANIM_RATE * GAME_TICKS_PER_SEC)) / GAME_TICKS_PER_SEC;
            }
      }
      else if (!defensive)
      {
            animFrame = 0;
      } else {
            animFrame = playerFrame;
      }

      // -------------------------------------------------------------------------------

      /* Mark it as having been drawn */
      psStructure->sDisplay.frameNumber = currentGameFrame;

      /* Get it's x and y coordinates so we don't have to deref. struct later */
      structX = psStructure->pos.x;
      structY = psStructure->pos.y;

      if (defensive && strImd != NULL)
      {
            // Play with the imd so its flattened
            SDWORD strHeight;

            // Get a copy of the points
            memcpy(alteredPoints, strImd->points, strImd->npoints * sizeof(Vector3f));

            // Get the height of the centre point for reference
            strHeight = psStructure->pos.z;

            // Now we got through the shape looking for vertices on the edge
            for (i = 0; i < strImd->npoints; i++)
            {
                  if (alteredPoints[i].y <= 0)
                  {
                        SDWORD pointHeight, shift;

                        pointHeight = map_Height(structX + alteredPoints[i].x, structY - alteredPoints[i].z);
                        shift = strHeight - pointHeight;
                        alteredPoints[i].y -= shift;
                  }
            }
      }

      dv.x = (structX - player.p.x) - terrainMidX * TILE_UNITS;
      dv.z = terrainMidY * TILE_UNITS - (structY - player.p.z);
      if (defensive)
      {
            dv.y = psStructure->pos.z;
      } else {
            dv.y = map_TileHeight(map_coord(structX), map_coord(structY));
      }
      /* Push the indentity matrix */
      iV_MatrixBegin();

      /* Translate the building  - N.B. We can also do rotations here should we require
      buildings to face different ways - Don't know if this is necessary - should be IMO */
      iV_TRANSLATE(dv.x,dv.y,dv.z);

      /* Get the x,z translation components */
      rx = player.p.x & (TILE_UNITS-1);
      rz = player.p.z & (TILE_UNITS-1);

      /* Translate */
      iV_TRANSLATE(rx, 0, -rz);

      /* OK - here is where we establish which IMD to draw for the building - luckily static objects,
      * buildings in other words are NOT made up of components - much quicker! */

      rotation = DEG((int)psStructure->direction);
      iV_MatrixRotateY(-rotation);
      if (!defensive
          && gameTime2-psStructure->timeLastHit < ELEC_DAMAGE_DURATION
          && psStructure->lastHitWeapon == WSC_ELECTRONIC )
      {
            bHitByElectronic = true;
      }

      buildingBrightness = pal_SetBrightness(200 - 100 * getStructureDamage(psStructure));

      /* If it's selected, then it's brighter */
      if (psStructure->selected)
      {
            SDWORD brightVar;

            if (!gamePaused())
            {
                  brightVar = getStaticTimeValueRange(990, 110);
                  if (brightVar > 55)
                  {
                        brightVar = 110 - brightVar;
                  }
            }
            else
            {
                  brightVar = 55;
            }
            buildingBrightness = pal_SetBrightness(200 + brightVar);
      }
      if (!godMode && !demoGetStatus() && getRevealStatus())
      {
            buildingBrightness = pal_SetBrightness(avGetObjLightLevel((BASE_OBJECT*)psStructure, buildingBrightness.byte.r));
      }

      if (!defensive)
      {
            /* Draw the building's base first */
            if (psStructure->pStructureType->pBaseIMD != NULL)
            {
                  pie_Draw3DShape(psStructure->pStructureType->pBaseIMD, 0, 0, buildingBrightness, WZCOL_BLACK, 0,0);
            }

            // override
            if(bHitByElectronic)
            {
                  buildingBrightness = pal_SetBrightness(150);
            }
      }

      if (bHitByElectronic)
      {
            objectShimmy((BASE_OBJECT *)psStructure);
      }

      //first check if partially built - ANOTHER HACK!
      if (psStructure->status == SS_BEING_BUILT
          || psStructure->status == SS_BEING_DEMOLISHED
          || (psStructure->status == SS_BEING_BUILT && psStructure->pStructureType->type == REF_RESOURCE_EXTRACTOR))
      {
            if (defensive)
            {
                  temp = strImd->points;
                  strImd->points = alteredPoints;
            }
            pie_Draw3DShape(strImd, 0, playerFrame, buildingBrightness, WZCOL_BLACK, pie_HEIGHT_SCALED | pie_SHADOW,
                            (SDWORD)(structHeightScale(psStructure) * pie_RAISE_SCALE));
            if (defensive)
            {
                  strImd->points = temp;
            }
      }
      else if (psStructure->status == SS_BUILT)
      {
            if (defensive)
            {
                  temp = strImd->points;
                  strImd->points = alteredPoints;
            }
            pie_Draw3DShape(strImd, animFrame, 0, buildingBrightness, WZCOL_BLACK, pie_STATIC_SHADOW, 0);
            if (defensive)
            {
                  strImd->points = temp;
            }

            // It might have weapons on it
            if (psStructure->sDisplay.imd->nconnectors > 0)
            {
                  iIMDShape   *mountImd[STRUCT_MAXWEAPS];
                  iIMDShape   *weaponImd[STRUCT_MAXWEAPS];
                  iIMDShape   *flashImd[STRUCT_MAXWEAPS];

                  for (i = 0; i < STRUCT_MAXWEAPS; i++)
                  {
                        weaponImd[i] = NULL;//weapon is gun ecm or sensor
                        mountImd[i] = NULL;
                        flashImd[i] = NULL;
                  }
                  //get an imd to draw on the connector priority is weapon, ECM, sensor
                  //check for weapon
                  if (psStructure->numWeaps > 0)
                  {
                        for (i = 0; i < psStructure->numWeaps; i++)
                        {
                              if (psStructure->asWeaps[i].nStat > 0)
                              {
                                    nWeaponStat = psStructure->asWeaps[i].nStat;
                                    weaponImd[i] =  asWeaponStats[nWeaponStat].pIMD;
                                    mountImd[i] =  asWeaponStats[nWeaponStat].pMountGraphic;
                                    flashImd[i] =  asWeaponStats[nWeaponStat].pMuzzleGraphic;
                              }
                        }
                  }
                  else
                  {
                        if (psStructure->asWeaps[0].nStat > 0)
                        {
                              nWeaponStat = psStructure->asWeaps[0].nStat;
                              weaponImd[0] =  asWeaponStats[nWeaponStat].pIMD;
                              mountImd[0] =  asWeaponStats[nWeaponStat].pMountGraphic;
                              flashImd[0] =  asWeaponStats[nWeaponStat].pMuzzleGraphic;
                        }
                  }

                  if (weaponImd[0] == NULL)
                  {
                        //check for ECM
                        if (psStructure->pStructureType->pECM != NULL)
                        {
                              weaponImd[0] =  psStructure->pStructureType->pECM->pIMD;
                              mountImd[0] =  psStructure->pStructureType->pECM->pMountGraphic;
                              flashImd[0] = NULL;
                        }
                  }

                  if (weaponImd[0] == NULL)
                  {
                        //check for sensor
                        if (psStructure->pStructureType->pSensor != NULL)
                        {
                              weaponImd[0] =  psStructure->pStructureType->pSensor->pIMD;
                              /* No recoil for sensors */
                              psStructure->asWeaps[0].recoilValue = 0;
                              mountImd[0]  =  psStructure->pStructureType->pSensor->pMountGraphic;
                              flashImd[0] = NULL;
                        }
                  }

                  // draw Weapon / ECM / Sensor for structure
                  for (i = 0; i < psStructure->numWeaps || i == 0; i++)
                  {
                        if (weaponImd[i] != NULL)
                        {
                              iV_MatrixBegin();
                              iV_TRANSLATE(strImd->connectors[i].x, strImd->connectors[i].z, strImd->connectors[i].y);
                              pie_MatRotY(DEG(-((SDWORD)psStructure->turretRotation[i])));
                              if (mountImd[i] != NULL)
                              {
                                    pie_TRANSLATE(0, 0, psStructure->asWeaps[i].recoilValue / 3);

                                    pie_Draw3DShape(mountImd[i], animFrame, 0, buildingBrightness, WZCOL_BLACK, pie_SHADOW, 0);
                                    if(mountImd[i]->nconnectors)
                                    {
                                          iV_TRANSLATE(mountImd[i]->connectors->x, mountImd[i]->connectors->z, mountImd[i]->connectors->y);
                                    }
                              }
                              iV_MatrixRotateX(DEG(psStructure->turretPitch[i]));
                              pie_TRANSLATE(0, 0, psStructure->asWeaps[i].recoilValue);

                              pie_Draw3DShape(weaponImd[i], playerFrame, 0, buildingBrightness, WZCOL_BLACK, pie_SHADOW,0);
                              if (psStructure->pStructureType->type == REF_REPAIR_FACILITY)
                              {
                                    REPAIR_FACILITY* psRepairFac = &psStructure->pFunctionality->repairFacility;
                                    // draw repair flash if the Repair Facility has a target which it has started work on
                                    if (weaponImd[i]->nconnectors && psRepairFac->psObj != NULL
                                        && psRepairFac->psObj->type == OBJ_DROID
                                        && ((DROID *)psRepairFac->psObj)->action == DACTION_WAITDURINGREPAIR )
                                    {
                                          iIMDShape   *pRepImd;

                                          iV_TRANSLATE(weaponImd[i]->connectors->x,weaponImd[i]->connectors->z-12,weaponImd[i]->connectors->y);
                                          pRepImd = getImdFromIndex(MI_FLAME);

                                          pie_MatRotY(DEG((SDWORD)psStructure->turretRotation[i]));

                                          iV_MatrixRotateY(-player.r.y);
                                          iV_MatrixRotateX(-player.r.x);
                                          pie_Draw3DShape(pRepImd, getStaticTimeValueRange(100,pRepImd->numFrames), 0, buildingBrightness, WZCOL_BLACK, pie_ADDITIVE, 192);

                                          iV_MatrixRotateX(player.r.x);
                                          iV_MatrixRotateY(player.r.y);
                                          pie_MatRotY(DEG((SDWORD)psStructure->turretRotation[i]));
                                    }
                              }
                              // we have a droid weapon so do we draw a muzzle flash
                              else if( weaponImd[i]->nconnectors && psStructure->visible[selectedPlayer]>(UBYTE_MAX/2))
                              {
                                    /* Now we need to move to the end of the barrel */
                                    pie_TRANSLATE(weaponImd[i]->connectors[i].x, weaponImd[i]->connectors[i].z, weaponImd[i]->connectors[i].y );
                                    // and draw the muzzle flash
                                    // animate for the duration of the flash only
                                    if (flashImd[i])
                                    {
                                          // assume no clan colours formuzzle effects
                                          if (flashImd[i]->numFrames == 0 || flashImd[i]->animInterval <= 0)
                                          {
                                                // no anim so display one frame for a fixed time
                                                if (gameTime < (psStructure->asWeaps[i].lastFired + BASE_MUZZLE_FLASH_DURATION))
                                                {
                                                      pie_Draw3DShape(flashImd[i], 0, 0, buildingBrightness, WZCOL_BLACK, pie_ADDITIVE, 128);//muzzle flash
                                                }
                                          }
                                          else
                                          {
                                                frame = (gameTime - psStructure->asWeaps[i].lastFired)/flashImd[i]->animInterval;
                                                if (frame < flashImd[i]->numFrames)
                                                {
                                                      pie_Draw3DShape(flashImd[i], frame, 0, buildingBrightness, WZCOL_BLACK, pie_ADDITIVE, 20);//muzzle flash
                                                }
                                          }
                                    }
                              }
                              iV_MatrixEnd();
                        }
                        // no IMD, its a baba machine gun, bunker, etc.
                        else if (psStructure->asWeaps[i].nStat > 0)
                        {
                              flashImd[i] = NULL;
                              // get an imd to draw on the connector priority is weapon, ECM, sensor
                              // check for weapon
                              nWeaponStat = psStructure->asWeaps[i].nStat;
                              flashImd[i] =  asWeaponStats[nWeaponStat].pMuzzleGraphic;
                              // draw Weapon/ECM/Sensor for structure
                              if (flashImd[i] != NULL)
                              {
                                    iV_MatrixBegin();
                                    // horrendous hack
                                    if (strImd->max.y > 80) // babatower
                                    {
                                          iV_TRANSLATE(0, 80, 0);
                                          pie_MatRotY(DEG(-((SDWORD)psStructure->turretRotation[i])));
                                          iV_TRANSLATE(0, 0, -20);
                                    }
                                    else//baba bunker
                                    {
                                          iV_TRANSLATE(0, 10, 0);
                                          pie_MatRotY(DEG(-((SDWORD)psStructure->turretRotation[i])));
                                          iV_TRANSLATE(0, 0, -40);
                                    }
                                    iV_MatrixRotateX(DEG(psStructure->turretPitch[i]));
                                    // and draw the muzzle flash
                                    // animate for the duration of the flash only
                                    // assume no clan colours formuzzle effects
                                    if (flashImd[i]->numFrames == 0 || flashImd[i]->animInterval <= 0)
                                    {
                                          // no anim so display one frame for a fixed time
                                          if (gameTime < psStructure->asWeaps[i].lastFired + BASE_MUZZLE_FLASH_DURATION)
                                          {
                                                pie_Draw3DShape(flashImd[i], 0, 0, buildingBrightness, WZCOL_BLACK, 0, 0); //muzzle flash
                                          }
                                    }
                                    else
                                    {
                                          frame = (gameTime - psStructure->asWeaps[i].lastFired) / flashImd[i]->animInterval;
                                          if (frame < flashImd[i]->numFrames)
                                          {
                                                pie_Draw3DShape(flashImd[i], 0, 0, buildingBrightness, WZCOL_BLACK, 0, 0); //muzzle flash
                                          }
                                    }
                                    iV_MatrixEnd();
                              }
                        }
                        // if there is an unused connector, but not the first connector, add a light to it
                        else if (psStructure->sDisplay.imd->nconnectors > 1)
                        {
                              for (i = 0; i < psStructure->sDisplay.imd->nconnectors; i++)
                              {
                                    iIMDShape *lImd;

                                    iV_MatrixBegin();
                                    iV_TRANSLATE(psStructure->sDisplay.imd->connectors->x, psStructure->sDisplay.imd->connectors->z,
                                                 psStructure->sDisplay.imd->connectors->y);
                                    lImd = getImdFromIndex(MI_LANDING);
                                    pie_Draw3DShape(lImd, getStaticTimeValueRange(1024, lImd->numFrames), 0, buildingBrightness, WZCOL_BLACK, 0, 0);
                                    iV_MatrixEnd();
                              }
                        }
                  }
            }
      }

      {
            Vector3i zero = { 0, 0, 0 };
            Vector2i s = { 0, 0 };

            pie_RotateProject(&zero, &s);
            psStructure->sDisplay.screenX = s.x;
            psStructure->sDisplay.screenY = s.y;

            targetAdd((BASE_OBJECT*)psStructure);
      }

      iV_MatrixEnd();
}

/*draw the delivery points */
void  renderDeliveryPoint(FLAG_POSITION *psPosition)
{
      Vector3i dv;
      SDWORD                  x, y, r, rx, rz;
      Vector3f *temp = NULL;
      //store the frame number for when deciding what has been clicked on
      psPosition->frameNumber = currentGameFrame;

      dv.x = (psPosition->coords.x - player.p.x) - terrainMidX*TILE_UNITS;
      dv.z = terrainMidY*TILE_UNITS - (psPosition->coords.y - player.p.z);
      dv.y = psPosition->coords.z;

      /* Push the indentity matrix */
      iV_MatrixBegin();

      iV_TRANSLATE(dv.x,dv.y,dv.z);

      /* Get the x,z translation components */
      rx = player.p.x & (TILE_UNITS-1);
      rz = player.p.z & (TILE_UNITS-1);

      /* Translate */
      iV_TRANSLATE(rx,0,-rz);

      //quick check for invalid data
      ASSERT( psPosition->factoryType < NUM_FLAG_TYPES && psPosition->factoryInc < MAX_FACTORY, "Invalid assembly point" );

      if(!psPosition->selected)
      {
            temp = pAssemblyPointIMDs[psPosition->factoryType][psPosition->factoryInc]->points;
            flattenImd(pAssemblyPointIMDs[psPosition->factoryType][psPosition->factoryInc],
                  psPosition->coords.x, psPosition->coords.y,0);
      }

      pie_MatScale(50); // they are all big now so make this one smaller too

      pie_Draw3DShape(pAssemblyPointIMDs[psPosition->factoryType][psPosition->factoryInc], 0, 0, WZCOL_WHITE, WZCOL_BLACK, pie_NO_BILINEAR, 0);

      if(!psPosition->selected)
      {
            pAssemblyPointIMDs[psPosition->factoryType][psPosition->factoryInc]->points = temp;
      }

      //get the screen coords for the DP
      calcFlagPosScreenCoords(&x, &y, &r);
      psPosition->screenX = x;
      psPosition->screenY = y;
      psPosition->screenR = r;

      iV_MatrixEnd();
}

static BOOL renderWallSection(STRUCTURE *psStructure)
{
      SDWORD                  structX, structY, rx, rz;
      PIELIGHT          brightness, specular = WZCOL_BLACK, buildingBrightness;
      iIMDShape         *imd;
      SDWORD                  rotation;
      Vector3i                dv;
      UDWORD                  i;
      Vector3f                *temp;

      if(psStructure->visible[selectedPlayer] || godMode || demoGetStatus())
      {
            psStructure->sDisplay.frameNumber = currentGameFrame;
            /* Get it's x and y coordinates so we don't have to deref. struct later */
            structX = psStructure->pos.x;
            structY = psStructure->pos.y;
            buildingBrightness = pal_SetBrightness(200 - 100 * getStructureDamage(psStructure));

            if(psStructure->selected)
            {
                  SDWORD brightVar;

                  if(!gamePaused())
                  {
                        brightVar = getStaticTimeValueRange(990,110);
                        if(brightVar>55) brightVar = 110-brightVar;
                  }
                  else
                  {
                        brightVar = 55;
                  }


                  buildingBrightness = pal_SetBrightness(200 + brightVar);
            }

            if(godMode || demoGetStatus())
            {
                  /* NOP */
            }
            else if(getRevealStatus())
            {
                  buildingBrightness = pal_SetBrightness(avGetObjLightLevel((BASE_OBJECT*)psStructure, buildingBrightness.byte.r));
            }

            brightness = buildingBrightness;

            /*
            Right, now the tricky bit, we need to bugger about with the coordinates of the imd to make it
            fit tightly to the ground and to neighbours.
            */
            imd = psStructure->pStructureType->pBaseIMD;
            if(imd != NULL)
            {
                  UDWORD centreHeight;

                  // Get a copy of the points
                  memcpy(alteredPoints,imd->points,imd->npoints*sizeof(Vector3i));
                  // Get the height of the centre point for reference
                  centreHeight = map_Height(structX,structY);
                  // Now we got through the shape looking for vertices on the edge
                  for(i=0; i<(UDWORD)imd->npoints; i++)
                  {
                        UDWORD pointHeight;
                        SDWORD shift;

                        pointHeight = map_Height(structX+alteredPoints[i].x,structY-alteredPoints[i].z);
                        shift = centreHeight - pointHeight;
                        alteredPoints[i].y -= shift;
                  }
            }
            /* Establish where it is in the world */
            dv.x = (structX - player.p.x) - terrainMidX*TILE_UNITS;
            dv.z = terrainMidY*TILE_UNITS - (structY - player.p.z);
            dv.y = map_Height(structX, structY);

            /* Push the indentity matrix */
            iV_MatrixBegin();

            /* Translate */
            iV_TRANSLATE(dv.x,dv.y,dv.z);

            /* Get the x,z translation components */
            rx = player.p.x & (TILE_UNITS-1);
            rz = player.p.z & (TILE_UNITS-1);

            /* Translate */
            iV_TRANSLATE(rx, 0, -rz);

            rotation = DEG( (int)psStructure->direction );
            iV_MatrixRotateY(-rotation);

            if(imd != NULL)
            {
                  // Make the imd pointer to the vertex list point to ours
                  temp = imd->points;
                  imd->points = alteredPoints;
                  // Actually render it
                  pie_Draw3DShape(imd, 0, getPlayerColour(psStructure->player), brightness, specular, 0, 0);
                  imd->points = temp;
            }

            imd = psStructure->sDisplay.imd;
            temp = imd->points;

            flattenImd(imd, structX, structY, psStructure->direction );

            /* Actually render it */
            if ( (psStructure->status == SS_BEING_BUILT ) ||
                  (psStructure->status == SS_BEING_DEMOLISHED ) ||
                  (psStructure->status == SS_BEING_BUILT && psStructure->pStructureType->type == REF_RESOURCE_EXTRACTOR) )
            {
                  pie_Draw3DShape(psStructure->sDisplay.imd, 0, getPlayerColour(psStructure->player),
                                          brightness, specular, pie_HEIGHT_SCALED|pie_SHADOW,
                                          (SDWORD)(structHeightScale(psStructure) * pie_RAISE_SCALE) );
            }
            else if(psStructure->status == SS_BUILT)
            {
                  if (psStructure->pStructureType->type == REF_WALL)
                  {
                        // draw walls with a dynamic shadow as they can be rotated, and can be tank traps
                        pie_Draw3DShape(imd, 0, getPlayerColour(psStructure->player), brightness, specular, pie_SHADOW, 0);
                  }
                  else
                  {
                        pie_Draw3DShape(imd, 0, getPlayerColour(psStructure->player), brightness, specular, pie_STATIC_SHADOW, 0);
                  }
            }
            imd->points = temp;

            {
                  Vector3i zero = {0, 0, 0};
                  Vector2i s = {0, 0};

                  pie_RotateProject( &zero, &s );
                  psStructure->sDisplay.screenX = s.x;
                  psStructure->sDisplay.screenY = s.y;
            }

            iV_MatrixEnd();

            return(true);
      }
      return false;
}

/* renderShadow: draws shadow under droid */
void renderShadow( DROID *psDroid, iIMDShape *psShadowIMD )
{
      Vector3i                dv;
      Vector3f                *pVecTemp;
      SDWORD                  shadowScale, rx, rz;

      dv.x = (psDroid->pos.x - player.p.x) - terrainMidX*TILE_UNITS;
      if(psDroid->droidType == DROID_TRANSPORTER)
      {
            dv.x -= bobTransporterHeight()/2;
      }
      dv.z = terrainMidY*TILE_UNITS - (psDroid->pos.y - player.p.z);
      dv.y = map_Height(psDroid->pos.x, psDroid->pos.y);

      /* Push the indentity matrix */
      iV_MatrixBegin();

      iV_TRANSLATE(dv.x,dv.y,dv.z);

      /* Get the x,z translation components */
      rx = player.p.x & (TILE_UNITS-1);
      rz = player.p.z & (TILE_UNITS-1);

      /* Translate */
      iV_TRANSLATE(rx,0,-rz);

      if(psDroid->droidType == DROID_TRANSPORTER)
      {
            iV_MatrixRotateY( DEG( -psDroid->direction ) );
      }

      pVecTemp = psShadowIMD->points;
      if(psDroid->droidType == DROID_TRANSPORTER)
      {
            flattenImd( psShadowIMD, psDroid->pos.x, psDroid->pos.y, 0);
            shadowScale = 100-(psDroid->pos.z/100);
            if(shadowScale < 50) shadowScale = 50;
      }
      else
      {
            pie_MatRotY( DEG(-psDroid->direction ) );
            pie_MatRotX( DEG( psDroid->pitch ) );
            pie_MatRotZ( DEG( psDroid->roll ) );
      }

      pie_Draw3DShape(psShadowIMD, 0, 0, WZCOL_WHITE, WZCOL_BLACK, pie_TRANSLUCENT, 128);
      psShadowIMD->points = pVecTemp;

      iV_MatrixEnd();
}

/* Draw the droids */
void renderDroid( DROID *psDroid )
{
      displayComponentObject( (BASE_OBJECT *) psDroid);
      targetAdd((BASE_OBJECT*)psDroid);
}


/* Draws the strobing 3D drag box that is used for multiple selection */
static void drawDragBox( void )
{
      int minX, maxX;         // SHURCOOL: These 4 ints will hold the corners of the selection box
      int minY, maxY;

      if(dragBox3D.status == DRAG_DRAGGING && buildState == BUILD3D_NONE)
      {
            if(gameTime - dragBox3D.lastTime > BOX_PULSE_SPEED)
            {
                  dragBox3D.pulse++;
                  if (dragBox3D.pulse >= BOX_PULSE_SIZE)
                  {
                        dragBox3D.pulse = 0;
                  }
                  dragBox3D.lastTime = gameTime;
            }

            // SHURCOOL: Determine the 4 corners of the selection box, and use them for consistent selection box rendering
            minX = MIN(dragBox3D.x1, mouseXPos);
            maxX = MAX(dragBox3D.x1, mouseXPos);
            minY = MIN(dragBox3D.y1, mouseYPos);
            maxY = MAX(dragBox3D.y1, mouseYPos);

            // SHURCOOL: Reduce the box in size to produce a (consistent) pulsing inward effect
            minX += dragBox3D.pulse / 2;
            maxX -= dragBox3D.pulse / 2;
            minY += dragBox3D.pulse / 2;
            maxY -= dragBox3D.pulse / 2;

            pie_SetDepthBufferStatus(DEPTH_CMP_ALWAYS_WRT_OFF);
            iV_Box(minX, minY, maxX, maxY, WZCOL_UNIT_SELECT_BORDER);
            pie_UniTransBoxFill(minX + 1, minY + 1, maxX - 1, maxY - 1, WZCOL_UNIT_SELECT_BOX);
            pie_SetDepthBufferStatus(DEPTH_CMP_LEQ_WRT_ON);
      }
}


// display reload bars for structures and droids
// Watermelon:make it to accept additional int value weapon_slot
// this should fix the overlapped reloadbar problem
static void drawWeaponReloadBar(BASE_OBJECT *psObj, WEAPON *psWeap, int weapon_slot)
{
      WEAPON_STATS            *psStats;
      BOOL              bSalvo;
      UDWORD                  firingStage, interval, damLevel;
      SDWORD                  scrX,scrY, scrR, scale;
      STRUCTURE         *psStruct;
      float             mulH; // display unit resistance instead of reload!
      DROID             *psDroid;

      if (ctrlShiftDown() && (psObj->type == OBJ_DROID))
      {
            psDroid = (DROID*)psObj;
            scrX = psObj->sDisplay.screenX;
            scrY = psObj->sDisplay.screenY;
            scrR = psObj->sDisplay.screenR;
            scrY += scrR + 2;

            if (psDroid->resistance)
            {
                  mulH = (float)psDroid->resistance / (float)droidResistance(psDroid);
            }
            else
            {
                  mulH = 100.f;
            }
            firingStage = mulH;
            firingStage = ((((2*scrR)*10000)/100)*firingStage)/10000;
            if(firingStage >= (UDWORD)(2*scrR))
            {
                  firingStage = (2*scrR) - 1;
            }
            pie_BoxFill(scrX - scrR-1, 6+scrY + 0 + (weapon_slot * 5), scrX - scrR +(2*scrR),    6+scrY+3 + (weapon_slot * 5), WZCOL_RELOAD_BACKGROUND);
            pie_BoxFill(scrX - scrR,   6+scrY + 1 + (weapon_slot * 5), scrX - scrR +firingStage, 6+scrY+2 + (weapon_slot * 5), WZCOL_RELOAD_BAR);
            return;
      }

      if (psWeap->nStat == 0)
      {
            // no weapon
            return;
      }

      psStats = asWeaponStats + psWeap->nStat;

      /* Justifiable only when greater than a one second reload or intra salvo time  */
      bSalvo = false;
      if(psStats->numRounds > 1)
      {
            bSalvo = true;
      }
      if( (bSalvo && (psStats->reloadTime > GAME_TICKS_PER_SEC)) ||
            (psStats->firePause > GAME_TICKS_PER_SEC) ||
            ((psObj->type == OBJ_DROID) && isVtolDroid((DROID *)psObj)) )
      {
            if (psObj->type == OBJ_DROID && isVtolDroid((DROID *)psObj))
            {
                  //deal with VTOLs
                  firingStage = getNumAttackRuns((DROID *)psObj, weapon_slot) - ((DROID *)psObj)->sMove.iAttackRuns[weapon_slot];

                  //compare with max value
                  interval = getNumAttackRuns((DROID *)psObj, weapon_slot);
            }
            else
            {
                  firingStage = gameTime - psWeap->lastFired;
                  if (bSalvo)
                  {
                        interval = weaponReloadTime(psStats, psObj->player);
                  }
                  else
                  {
                        interval = weaponFirePause(psStats, psObj->player);
                  }
            }

            scrX = psObj->sDisplay.screenX;
            scrY = psObj->sDisplay.screenY;
            scrR = psObj->sDisplay.screenR;
            switch (psObj->type)
            {
            case OBJ_DROID:
                  damLevel = PERCENT(((DROID *)psObj)->body, ((DROID *)psObj)->originalBody);
                  scrY += scrR + 2;
                  break;
            case OBJ_STRUCTURE:
                  psStruct = (STRUCTURE *)psObj;
                  damLevel = (1. - getStructureDamage(psStruct)) * 100;
                  scale = MAX(psStruct->pStructureType->baseWidth, psStruct->pStructureType->baseBreadth);
                  scrY += scale * 10 - 1;
                  scrR = scale * 20;
                  break;
            default:
                  ASSERT(!"invalid object type", "drawWeaponReloadBars: invalid object type");
                  damLevel = 100;
                  break;
            }

            //now we know what it is!!
            if (damLevel < HEAVY_DAMAGE_LEVEL)
            {
                  interval += interval;
            }

            if(firingStage < interval)
            {
                  /* Get a percentage */
                  firingStage = PERCENT(firingStage,interval);

                  /* Scale it into an appropriate range */
                  firingStage = ((((2*scrR)*10000)/100)*firingStage)/10000;
                  if(firingStage >= (UDWORD)(2*scrR))
                  {
                        firingStage = (2*scrR) - 1;
                  }
                  /* Power bars */
                  pie_BoxFill(scrX - scrR-1, 6+scrY + 0 + (weapon_slot * 5), scrX - scrR +(2*scrR),    6+scrY+3 + (weapon_slot * 5), WZCOL_RELOAD_BACKGROUND);
                  pie_BoxFill(scrX - scrR,   6+scrY + 1 + (weapon_slot * 5), scrX - scrR +firingStage, 6+scrY+2 + (weapon_slot * 5), WZCOL_RELOAD_BAR);
            }
      }
}


static void drawStructureSelections( void )
{
      STRUCTURE   *psStruct;
      SDWORD            scrX,scrY,scrR;
      PIELIGHT    powerCol = WZCOL_BLACK;
      UDWORD            health,width;
      UDWORD            scale;
      UDWORD            i;
      BASE_OBJECT *psClickedOn;
      BOOL        bMouseOverStructure = false;
      BOOL        bMouseOverOwnStructure = false;
      float       mulH;

      psClickedOn = mouseTarget();
      if(psClickedOn!=NULL && psClickedOn->type == OBJ_STRUCTURE)
      {
            bMouseOverStructure = true;
            if(psClickedOn->player == selectedPlayer)
            {
                  bMouseOverOwnStructure = true;
            }
      }
      pie_SetDepthBufferStatus(DEPTH_CMP_ALWAYS_WRT_ON);
      pie_SetFogStatus(false);

      /* Go thru' all the buildings */
      for(psStruct = apsStructLists[selectedPlayer]; psStruct; psStruct = psStruct->psNext)
      {
            if(clipXY(psStruct->pos.x,psStruct->pos.y))
            {
                  /* If it's selected */
                  if (psStruct->selected
                      || (barMode == BAR_DROIDS_AND_STRUCTURES
                          && (psStruct->pStructureType->type != REF_WALL && psStruct->pStructureType->type != REF_WALLCORNER))
                      || (bMouseOverOwnStructure
                          && psStruct == (STRUCTURE *) psClickedOn
                          && ((STRUCTURE * )psClickedOn)->status == SS_BUILT
                              && psStruct->sDisplay.frameNumber == currentGameFrame))
                  {
                        scale = MAX(psStruct->pStructureType->baseWidth, psStruct->pStructureType->baseBreadth);
                        width = scale*20;
                        scrX = psStruct->sDisplay.screenX;
                        scrY = psStruct->sDisplay.screenY + (scale*10);
                        scrR = width;
                        //health = PERCENT(psStruct->body, psStruct->baseBodyPoints);
                        if (ctrlShiftDown())
                        {
                              //show resistance values if CTRL/SHIFT depressed
                              UDWORD  resistance = structureResistance(psStruct->pStructureType, psStruct->player);

                              if (resistance)
                              {
                                    health = PERCENT(psStruct->resistance, resistance);
                              }
                              else
                              {
                                    health = 100;
                              }
                        }
                        else
                        {
                              //show body points
                              health = (1. - getStructureDamage(psStruct)) * 100;
                        }
                        if(health>100) health =100;

                        if (health > REPAIRLEV_HIGH) {
                              powerCol = WZCOL_HEALTH_HIGH;
                        } else if (health >= REPAIRLEV_LOW) {
                              powerCol = WZCOL_HEALTH_MEDIUM;
                        } else {
                              powerCol = WZCOL_HEALTH_LOW;
                        }
                        mulH = (float)health / 100.f;
                        mulH *= (float)width;
                        health = mulH;
                        if(health>width) health = width;
                        health*=2;
                        pie_BoxFill(scrX-scrR - 1, scrY - 1, scrX + scrR + 1, scrY + 2, WZCOL_RELOAD_BACKGROUND);
                        pie_BoxFill(scrX-scrR, scrY, scrX - scrR + health, scrY + 1, powerCol);

                        for (i = 0; i < psStruct->numWeaps; i++)
                        {
                              drawWeaponReloadBar((BASE_OBJECT *)psStruct, &psStruct->asWeaps[i], i);
                        }
                  }
                  else
                  {
                        if(psStruct->status == SS_BEING_BUILT && psStruct->sDisplay.frameNumber == currentGameFrame)
                        {
                              scale = MAX(psStruct->pStructureType->baseWidth, psStruct->pStructureType->baseBreadth);
                              width = scale*20;
                              scrX = psStruct->sDisplay.screenX;
                              scrY = psStruct->sDisplay.screenY + (scale*10);
                              scrR = width;
                              health = PERCENT(psStruct->currentBuildPts ,
                              psStruct->pStructureType->buildPoints);
                              if (health >= 100)
                              {
                                    health = 100;     // belt and braces
                              }
                              powerCol = WZCOL_YELLOW;
                              mulH = (float)health / 100.f;
                              mulH *= (float)width;
                              health = mulH;
                              if (health > width)
                              {
                                    health = width;
                              }
                              health *= 2;
                              pie_BoxFill(scrX - scrR - 1, scrY - 1, scrX + scrR + 1, scrY + 2, WZCOL_RELOAD_BACKGROUND);
                              pie_BoxFill(scrX - scrR, scrY, scrX - scrR + health, scrY + 1, powerCol);
                        }
                  }
            }
      }

      for(i=0; i<MAX_PLAYERS; i++)
      {
            /* Go thru' all the buildings */
            for(psStruct = apsStructLists[i]; psStruct; psStruct = psStruct->psNext)
            {
                  if(i!=selectedPlayer)         // only see enemy buildings being targetted, not yours!
                  {
                        if(clipXY(psStruct->pos.x,psStruct->pos.y))
                        {
                              /* If it's targetted and on-screen */
                              if(psStruct->targetted)
                              {
                                    if(psStruct->sDisplay.frameNumber == currentGameFrame)

                                    {
                                          psStruct->targetted = 0;
                                          scrX = psStruct->sDisplay.screenX;
                                          scrY = psStruct->sDisplay.screenY - (psStruct->sDisplay.imd->max.y / 4);
                                          iV_DrawImage(IntImages,getTargettingGfx(),scrX,scrY);
                                    }
                              }
                        }
                  }
            }
      }

      if(bMouseOverStructure && !bMouseOverOwnStructure)
      {
            if(mouseDown(MOUSE_RMB))
            {
                  psStruct = (STRUCTURE*)psClickedOn;
                  if(psStruct->status==SS_BUILT)
                  {
                        scale = MAX(psStruct->pStructureType->baseWidth, psStruct->pStructureType->baseBreadth);
                        width = scale*20;
                        scrX = psStruct->sDisplay.screenX;
                        scrY = psStruct->sDisplay.screenY + (scale*10);
                        scrR = width;
                        //health = PERCENT(psStruct->body, psStruct->baseBodyPoints);
                        if (ctrlShiftDown())
                        {
                              //show resistance values if CTRL/SHIFT depressed
                              UDWORD  resistance = structureResistance(
                                    psStruct->pStructureType, psStruct->player);
                              if (resistance)
                              {
                                    health = PERCENT(psStruct->resistance, resistance);
                              }
                              else
                              {
                                    health = 100;
                              }
                        }
                        else
                        {
                              //show body points
                              health = (1. - getStructureDamage(psStruct)) * 100;
                        }
                        if (health > REPAIRLEV_HIGH)
                        {
                              powerCol = WZCOL_HEALTH_HIGH;
                        }
                        else if (health > REPAIRLEV_LOW)
                        {
                              powerCol = WZCOL_HEALTH_MEDIUM;
                        }
                        else
                        {
                              powerCol = WZCOL_HEALTH_LOW;
                        }
                        health = (((width*10000)/100)*health)/10000;
                        health*=2;
                        pie_BoxFill(scrX-scrR-1, scrY-1, scrX+scrR+1, scrY+2, WZCOL_RELOAD_BACKGROUND);
                        pie_BoxFill(scrX-scrR, scrY, scrX-scrR+health, scrY+1, powerCol);
                  }
                  else if(psStruct->status == SS_BEING_BUILT)
                  {
                        scale = MAX(psStruct->pStructureType->baseWidth, psStruct->pStructureType->baseBreadth);
                        width = scale*20;
                        scrX = psStruct->sDisplay.screenX;
                        scrY = psStruct->sDisplay.screenY + (scale*10);
                        scrR = width;
                        health =  PERCENT(psStruct->currentBuildPts , psStruct->pStructureType->buildPoints);
                        powerCol = WZCOL_GREEN;
                        health = (((width*10000)/100)*health)/10000;
                        health*=2;
                        pie_BoxFill(scrX - scrR - 1, scrY - 1, scrX + scrR + 1, scrY + 2, WZCOL_RELOAD_BACKGROUND);
                        pie_BoxFill(scrX - scrR - 1, scrY, scrX - scrR + health, scrY + 1, powerCol);
                  }
            }
      }

      pie_SetDepthBufferStatus(DEPTH_CMP_LEQ_WRT_ON);
}

static UDWORD     getTargettingGfx( void )
{
UDWORD      index;

      index = getTimeValueRange(1000,10);

      switch(index)
      {
      case  0:
      case  1:
      case  2:
            return(IMAGE_TARGET1+index);
            break;
      default:
            if(index & 0x01)
            {
                  return(IMAGE_TARGET4);
            }
            else
            {
                  return(IMAGE_TARGET5);
            }
            break;
      }
}

BOOL  eitherSelected(DROID *psDroid)
{
BOOL              retVal;
BASE_OBJECT       *psObj;

      retVal = false;
      if(psDroid->selected)
      {
            retVal = true;
      }

      if(psDroid->psGroup)
      {
            if(psDroid->psGroup->psCommander)
            {
                  if(psDroid->psGroup->psCommander->selected)
                  {
                        retVal = true;
                  }
            }
      }

      psObj = orderStateObj(psDroid, DORDER_FIRESUPPORT);
      if (psObj
       && psObj->selected)
      {
            retVal = true;
      }

      return retVal;
}

static void drawDroidSelections( void )
{
      UDWORD                  scrX,scrY,scrR;
      DROID             *psDroid;
      UDWORD                  damage;
      PIELIGHT          powerCol = WZCOL_BLACK;
      PIELIGHT          boxCol;
      BASE_OBJECT       *psClickedOn;
      BOOL              bMouseOverDroid = false;
      BOOL              bMouseOverOwnDroid = false;
      BOOL              bBeingTracked;
      UDWORD                  i,index;
      FEATURE                 *psFeature;
      float             mulH;

      psClickedOn = mouseTarget();
      if(psClickedOn!=NULL && psClickedOn->type == OBJ_DROID)
      {
            bMouseOverDroid = true;
            if(psClickedOn->player == selectedPlayer && !psClickedOn->selected)
            {
                  bMouseOverOwnDroid = true;
            }
      }

      pie_SetDepthBufferStatus(DEPTH_CMP_ALWAYS_WRT_ON);
      pie_SetFogStatus(false);
      for(psDroid = apsDroidLists[selectedPlayer]; psDroid; psDroid = psDroid->psNext)
      {
            bBeingTracked = false;
            /* If it's selected and on screen or it's the one the mouse is over ||*/
            // ABSOLUTELY MAD LOGICAL EXPRESSION!!! :-)
            if ((eitherSelected(psDroid) && psDroid->sDisplay.frameNumber == currentGameFrame)
             || (bMouseOverOwnDroid && psDroid == (DROID *) psClickedOn)
             || (droidUnderRepair(psDroid) && psDroid->sDisplay.frameNumber == currentGameFrame)
             ||  (barMode == BAR_DROIDS || barMode == BAR_DROIDS_AND_STRUCTURES))
            {
                  damage = PERCENT(psDroid->body, psDroid->originalBody);

                  if (damage > REPAIRLEV_HIGH)
                  {
                        powerCol = WZCOL_HEALTH_HIGH;
                  }
                  else if (damage > REPAIRLEV_LOW)
                  {
                        powerCol = WZCOL_HEALTH_MEDIUM;
                  }
                  else
                  {
                        powerCol = WZCOL_HEALTH_LOW;
                  }
                  mulH = (float)psDroid->body / (float)psDroid->originalBody;
                  damage = mulH * (float)psDroid->sDisplay.screenR;// (((psDroid->sDisplay.screenR*10000)/100)*damage)/10000;
                  if(damage>psDroid->sDisplay.screenR) damage = psDroid->sDisplay.screenR;

                  damage *=2;
                  scrX = psDroid->sDisplay.screenX;
                  scrY = psDroid->sDisplay.screenY;
                  scrR = psDroid->sDisplay.screenR;

                  /* Yeah, yeah yeah - hardcoded palette entries - need to change to #defined colour names */
                  /* Three DFX clips properly right now - not sure if software does */
//                if((scrX+scrR)>0 && (scrY+scrR)>0 && (scrX-scrR)<DISP_WIDTH
//                      && (scrY-scrR)<DISP_HEIGHT)
                  {
                        if(!driveModeActive() || driveIsDriven(psDroid)) {
                              boxCol = WZCOL_WHITE;
                        } else {
                              boxCol = WZCOL_GREEN;
                        }

                        if (psDroid->selected)
                        {
                              pie_BoxFill(scrX - scrR, scrY + scrR - 7, scrX - scrR+1, scrY + scrR, boxCol);
                              pie_BoxFill(scrX - scrR, scrY + scrR, scrX - scrR + 7, scrY + scrR + 1, boxCol);
                              pie_BoxFill(scrX + scrR - 7, scrY + scrR, scrX + scrR, scrY + scrR + 1, boxCol);
                              pie_BoxFill(scrX + scrR, scrY + scrR - 7, scrX + scrR + 1, scrY + scrR + 1, boxCol);
                        }

                        /* Power bars */
                        pie_BoxFill(scrX - scrR - 1, scrY + scrR+2, scrX + scrR + 1, scrY + scrR + 5, WZCOL_RELOAD_BACKGROUND);
                        pie_BoxFill(scrX - scrR, scrY + scrR+3, scrX - scrR + damage, scrY + scrR + 4, powerCol);

                        /* Write the droid rank out */
                        if((scrX+scrR)>0 && (scrY+scrR)>0 && (scrX-scrR) < pie_GetVideoBufferWidth() && (scrY-scrR) < pie_GetVideoBufferHeight())
                        {
                              drawDroidRank(psDroid);
                              drawDroidSensorLock(psDroid);

                              if ((psDroid->droidType == DROID_COMMAND) ||
                                    hasCommander(psDroid))
                              {
                                    drawDroidCmndNo(psDroid);
                              }
                              else if(psDroid->group!=UBYTE_MAX)
                              {
                                    drawDroidGroupNumber(psDroid);
                              }
                        }
                  }

                  for (i = 0;i < psDroid->numWeaps;i++)
                  {
                        drawWeaponReloadBar((BASE_OBJECT *)psDroid, &psDroid->asWeaps[i], i);
                  }
            }
      }



      /* Are we over an enemy droid */
      if(bMouseOverDroid && !bMouseOverOwnDroid)
      {
            if(mouseDown(MOUSE_RMB))
            {
                  if(psClickedOn->player!=selectedPlayer && psClickedOn->sDisplay.frameNumber == currentGameFrame)
                  {
                        psDroid = (DROID*)psClickedOn;
                        //show resistance values if CTRL/SHIFT depressed
                        if (ctrlShiftDown())
                        {
                              if (psDroid->resistance)
                              {
                                    damage = PERCENT(psDroid->resistance, droidResistance(psDroid));
                              }
                              else
                              {
                                    damage = 100;
                              }
                        }
                        else
                        {
                              damage = PERCENT(psDroid->body,psDroid->originalBody);
                        }

                        if (damage > REPAIRLEV_HIGH)
                        {
                              powerCol = WZCOL_HEALTH_HIGH;
                        }
                        else if (damage > REPAIRLEV_LOW)
                        {
                              powerCol = WZCOL_HEALTH_MEDIUM;
                        }
                        else
                        {
                              powerCol = WZCOL_HEALTH_LOW;
                        }

                        //show resistance values if CTRL/SHIFT depressed
                        if (ctrlShiftDown())
                        {
                              if (psDroid->resistance)
                              {
                                    mulH = (float)psDroid->resistance / (float)droidResistance(psDroid);
                              }
                              else
                              {
                                    mulH = 100.f;
                              }
                        }
                        else
                        {
                              mulH = (float)psDroid->body / (float)psDroid->originalBody;
                        }
                        damage = mulH * (float)psDroid->sDisplay.screenR;// (((psDroid->sDisplay.screenR*10000)/100)*damage)/10000;
                        if(damage>psDroid->sDisplay.screenR) damage = psDroid->sDisplay.screenR;
                        damage *=2;
                        scrX = psDroid->sDisplay.screenX;
                        scrY = psDroid->sDisplay.screenY;
                        scrR = psDroid->sDisplay.screenR;

                        /* Yeah, yeah yeah - hardcoded palette entries - need to change to #defined colour names */
                        /* Three DFX clips properly right now - not sure if software does */
                        if((scrX+scrR)>0 && (scrY+scrR)>0 && (scrX-scrR) < pie_GetVideoBufferWidth() && (scrY-scrR) < pie_GetVideoBufferHeight())
                        {
                              if(!driveModeActive() || driveIsDriven(psDroid)) {
                                    boxCol = WZCOL_WHITE;
                              } else {
                                    boxCol = WZCOL_GREEN;
                              }

                              //we always want to show the enemy health/resistance as energyBar - AB 18/06/99
                              //if(bEnergyBars)
                              {
                                    /* Power bars */
                                    pie_BoxFill(scrX - scrR - 1, scrY + scrR + 2, scrX + scrR + 1, scrY + scrR + 5, WZCOL_RELOAD_BACKGROUND);
                                    pie_BoxFill(scrX - scrR, scrY + scrR+3, scrX - scrR + damage, scrY + scrR + 4, powerCol);
                              }
                        }
                  }
            }
      }

      for(i=0; i<MAX_PLAYERS; i++)
      {
            /* Go thru' all the droidss */
            for(psDroid = apsDroidLists[i]; psDroid; psDroid = psDroid->psNext)
            {
                  if(i!=selectedPlayer && !psDroid->died && psDroid->sDisplay.frameNumber == currentGameFrame)
                  {
                        /* If it's selected */
                        if(psDroid->bTargetted && (psDroid->visible[selectedPlayer] == UBYTE_MAX))
                        {
                              psDroid->bTargetted = false;
                              scrX = psDroid->sDisplay.screenX;
                              scrY = psDroid->sDisplay.screenY - 8;
                              index = IMAGE_BLUE1+getTimeValueRange(1020,5);
                              iV_DrawImage(IntImages,index,scrX,scrY);
                        }
                  }
            }
      }

      for(psFeature = apsFeatureLists[0]; psFeature; psFeature = psFeature->psNext)
      {
            if(!psFeature->died && psFeature->sDisplay.frameNumber == currentGameFrame)
            {
                  if(psFeature->bTargetted)
                  {
                        psFeature->bTargetted = false;
                        scrX = psFeature->sDisplay.screenX;
                        scrY = psFeature->sDisplay.screenY - (psFeature->sDisplay.imd->max.y / 4);
                        iV_DrawImage(IntImages,getTargettingGfx(),scrX,scrY);
                  }
            }
      }


      pie_SetDepthBufferStatus(DEPTH_CMP_LEQ_WRT_ON);
}

/* ---------------------------------------------------------------------------- */
#define GN_X_OFFSET     (28)
#define GN_Y_OFFSET (17)
static void drawDroidGroupNumber(DROID *psDroid)
{
UWORD id;
UDWORD      id2;
BOOL  bDraw;
SDWORD      xShift,yShift;

      bDraw = true;

      id = id2 = UDWORD_MAX;

      /* Is the unit in a group? */
      if(hasCommander(psDroid))
      {
            id2 = IMAGE_GN_STAR;

      }
      //else
      {
            switch(psDroid->group)
            {
            case 0:
                  id = IMAGE_GN_0;
                  break;
            case 1:
                  id = IMAGE_GN_1;
                  break;
            case 2:
                  id = IMAGE_GN_2;
                  break;
            case 3:
                  id = IMAGE_GN_3;
                  break;
            case 4:
                  id = IMAGE_GN_4;
                  break;
            case 5:
                  id = IMAGE_GN_5;
                  break;
            case 6:
                  id = IMAGE_GN_6;
                  break;
            case 7:
                  id = IMAGE_GN_7;
                  break;
            case 8:
                  id = IMAGE_GN_8;
                  break;
            case 9:
                  id = IMAGE_GN_9;
                  break;
            default:
                  bDraw = false;
                  break;
            }
      }
      if(bDraw)
      {
            xShift = GN_X_OFFSET;  // yeah yeah, I know
            yShift = GN_Y_OFFSET;
            xShift = ((xShift*pie_GetResScalingFactor())/100);
            yShift = ((yShift*pie_GetResScalingFactor())/100);
            iV_DrawImage(IntImages,id,psDroid->sDisplay.screenX-xShift,psDroid->sDisplay.screenY+yShift);
            if(id2!=UDWORD_MAX)
            {
                  iV_DrawImage(IntImages,id2,psDroid->sDisplay.screenX-xShift,psDroid->sDisplay.screenY+yShift-8);
            }
      }
}

/* ---------------------------------------------------------------------------- */
static void drawDroidCmndNo(DROID *psDroid)
{
UWORD id;
UDWORD      id2;
BOOL  bDraw;
SDWORD      xShift,yShift, index;

      bDraw = true;

      id = id2 = UDWORD_MAX;

      id2 = IMAGE_GN_STAR;
      index = SDWORD_MAX;
      if (psDroid->droidType == DROID_COMMAND)
      {
            index = cmdDroidGetIndex(psDroid);
      }
      else if (hasCommander(psDroid))
      {
            index = cmdDroidGetIndex(psDroid->psGroup->psCommander);
      }
      switch(index)
      {
      case 1:
            id = IMAGE_GN_1;
            break;
      case 2:
            id = IMAGE_GN_2;
            break;
      case 3:
            id = IMAGE_GN_3;
            break;
      case 4:
            id = IMAGE_GN_4;
            break;
      case 5:
            id = IMAGE_GN_5;
            break;
      case 6:
            id = IMAGE_GN_6;
            break;
      case 7:
            id = IMAGE_GN_7;
            break;
      case 8:
            id = IMAGE_GN_8;
            break;
      case 9:
            id = IMAGE_GN_9;
            break;
      default:
            bDraw = false;
            break;
      }

      if(bDraw)
      {
            xShift = GN_X_OFFSET;  // yeah yeah, I know
            yShift = GN_Y_OFFSET;
            xShift = ((xShift*pie_GetResScalingFactor())/100);
            yShift = ((yShift*pie_GetResScalingFactor())/100);
            iV_DrawImage(IntImages,id2,psDroid->sDisplay.screenX-xShift-6,psDroid->sDisplay.screenY+yShift);
            iV_DrawImage(IntImages,id,psDroid->sDisplay.screenX-xShift,psDroid->sDisplay.screenY+yShift);
      }
}
/* ---------------------------------------------------------------------------- */


/*    Get the onscreen corrdinates of a droid - so we can draw a bounding box - this need to be severely
      speeded up and the accuracy increased to allow variable size bouding boxes */
void calcScreenCoords(DROID *psDroid)
{
      /* Get it's absolute dimensions */
      const Vector3i zero = {0, 0, 0};
      Vector2i center = {0, 0};
      SDWORD cZ;
      UDWORD radius;

      /* How big a box do we want - will ultimately be calculated using xmax, ymax, zmax etc */
      if(psDroid->droidType == DROID_TRANSPORTER)
      {
            radius = 45;
      }
      else
      {
            radius = 22;
      }

      /* Pop matrices and get the screen corrdinates */
      cZ = pie_RotateProject( &zero, &center );

      //Watermelon:added a crash protection hack...
      if (cZ != 0)
      {
            radius = (radius * pie_GetResScalingFactor()) * 80 / cZ;
      }

      /* Deselect all the droids if we've released the drag box */
      if(dragBox3D.status == DRAG_RELEASED)
      {
            if(inQuad(&center, &dragQuad) && psDroid->player == selectedPlayer)
            {
                  //don't allow Transporter Droids to be selected here
                  //unless we're in multiPlayer mode!!!!
                  if (psDroid->droidType != DROID_TRANSPORTER || bMultiPlayer)
                  {
                        dealWithDroidSelect(psDroid, true);
                  }
            }
      }
      center.y -= 4;

      /* Store away the screen coordinates so we can select the droids without doing a trasform */
      psDroid->sDisplay.screenX = center.x;
      psDroid->sDisplay.screenY = center.y;
      psDroid->sDisplay.screenR = radius;
}


static void preprocessTiles(void)
{
      UDWORD i, j;
      UDWORD left,right,up,down, size;
      DROID *psDroid;
      SDWORD order;

      /* Set up the highlights if we're putting down a wall */
      if (wallDrag.status == DRAG_PLACING || wallDrag.status == DRAG_DRAGGING)
      {
            /* Ensure the start point is always shown */
            SET_TILE_HIGHLIGHT(mapTile(wallDrag.x1, wallDrag.y1));
            if( wallDrag.x1 == wallDrag.x2 || wallDrag.y1 == wallDrag.y2 )
            {
                  /* First process the ones inside the wall dragging area */
                  left = MIN(wallDrag.x1, wallDrag.x2);
                  right = MAX(wallDrag.x1, wallDrag.x2) + 1;
                  up = MIN(wallDrag.y1, wallDrag.y2);
                  down = MAX(wallDrag.y1, wallDrag.y2) + 1;

                  for(i = left; i < right; i++)
                  {
                        for(j = up; j < down; j++)
                        {
                              SET_TILE_HIGHLIGHT(mapTile(i,j));
                        }
                  }
            }
      }
      /* Only bother if we're placing a building */
      else if (buildState == BUILD3D_VALID || buildState == BUILD3D_POS)
      {
      /* Now do the ones inside the building highlight */
            left = buildSite.xTL;
            right = buildSite.xBR + 1;
            up = buildSite.yTL;
            down = buildSite.yBR + 1;

            for(i = left; i < right; i++)
            {
                  for(j = up; j < down; j++)
                  {
                        SET_TILE_HIGHLIGHT(mapTile(i,j));
                  }
            }
      }

      // HACK don't display until we're releasing this feature in an update!
#ifndef DISABLE_BUILD_QUEUE
      if (intBuildSelectMode())
      {
            //and there may be multiple building sites that need highlighting - AB 26/04/99
            if (ctrlShiftDown())
            {
                  //this highlights ALL constructor units' build sites
                  for (psDroid = apsDroidLists[selectedPlayer]; psDroid; psDroid = psDroid->psNext)
                  {
                        //psDroid = (DROID *)psObj;
                        if (psDroid->droidType == DROID_CONSTRUCT ||
                              psDroid->droidType == DROID_CYBORG_CONSTRUCT)
                        {
                              //draw the current build site if its a line of structures
                              if (psDroid->order == DORDER_LINEBUILD)
                              {
                                          left = map_coord(psDroid->orderX);
                                          right = map_coord(psDroid->orderX2) + 1;
                                          if (left > right)
                                          {
                                                size = left;
                                                left = right;
                                                right = size;
                                          }
                                          up = map_coord(psDroid->orderY);
                                          down = map_coord(psDroid->orderY2) + 1;
                                          if (up > down)
                                          {
                                                size = up;
                                                up = down;
                                                down = size;
                                          }
                                          //hilight the tiles
                                          for(i = left; i < right; i++)
                                          {
                                                for(j = up; j < down; j++)
                                                {
                                                      SET_TILE_HIGHLIGHT(mapTile(i,j));
                                                }
                                          }
                              }
                              //now look thru' the list of orders to see if more building sites
                              for (order = 0; order < psDroid->listSize; order++)
                              {
                                    if (psDroid->asOrderList[order].order == DORDER_BUILD)
                                    {
                                          //set up coords for tiles
                                          size = ((STRUCTURE_STATS *)psDroid->asOrderList[order].
                                                psOrderTarget)->baseWidth;
                                          left = map_coord(psDroid->asOrderList[order].x) - size/2;
                                          right = left + size;
                                          size = ((STRUCTURE_STATS *)psDroid->asOrderList[order].
                                                psOrderTarget)->baseBreadth;
                                          up = map_coord(psDroid->asOrderList[order].y) - size/2;
                                          down = up + size;
                                          //hilight the tiles
                                          for(i = left; i < right; i++)
                                          {
                                                for(j = up; j < down; j++)
                                                {
                                                      SET_TILE_HIGHLIGHT(mapTile(i,j));
                                                }
                                          }
                                    }
                                    else if (psDroid->asOrderList[order].order == DORDER_LINEBUILD)
                                    {
                                          //need to highlight the length of the wall
                                          left = map_coord(psDroid->asOrderList[order].x);
                                          right = map_coord(psDroid->asOrderList[order].x2);
                                          if (left > right)
                                          {
                                                size = left;
                                                left = right;
                                                right = size;
                                          }
                                          up = map_coord(psDroid->asOrderList[order].y);
                                          down = map_coord(psDroid->asOrderList[order].y2);
                                          if (up > down)
                                          {
                                                size = up;
                                                up = down;
                                                down = size;
                                          }
                                          //hilight the tiles
                                          for(i = left; i <= right; i++)
                                          {
                                                for(j = up; j <= down; j++)
                                                {
                                                      SET_TILE_HIGHLIGHT(mapTile(i, j));
                                                }
                                          }
                                    }
                              }
                        }
                  }
            }
      }
#endif
}


/*!
 * Find the tile the mouse is currently over
 */
/* TODO This is slow - speed it up */
03537 static void locateMouse(void)
{
      const Vector2i pt = {mouseXPos, mouseYPos};
      unsigned int i;
      int nearestZ = INT_MAX;

      for(i = 0; i < visibleTiles.x; ++i)
      {
            unsigned int j;
            for(j = 0; j < visibleTiles.y; ++j)
            {
                  int tileZ = tileScreenInfo[i][j].screen.z;

                  if(tileZ <= nearestZ)
                  {
                        QUAD quad;

                        quad.coords[0].x = tileScreenInfo[i+0][j+0].screen.x;
                        quad.coords[0].y = tileScreenInfo[i+0][j+0].screen.y;

                        quad.coords[1].x = tileScreenInfo[i+0][j+1].screen.x;
                        quad.coords[1].y = tileScreenInfo[i+0][j+1].screen.y;

                        quad.coords[2].x = tileScreenInfo[i+1][j+1].screen.x;
                        quad.coords[2].y = tileScreenInfo[i+1][j+1].screen.y;

                        quad.coords[3].x = tileScreenInfo[i+1][j+0].screen.x;
                        quad.coords[3].y = tileScreenInfo[i+1][j+0].screen.y;

                        /* We've got a match for our mouse coords */
                        if (inQuad(&pt, &quad))
                        {
                              mouseTileX = playerXTile + j;
                              mouseTileY = playerZTile + i;
                              if (mouseTileX < 0)
                                    mouseTileX = 0;
                              else if (mouseTileX > mapWidth-1)
                                    mouseTileX = mapWidth - 1;
                              if (mouseTileY < 0)
                                    mouseTileY = 0;
                              else if (mouseTileY > mapHeight-1)
                                    mouseTileY = mapHeight - 1;

                              /* Store away z value */
                              nearestZ = tileZ;
                        }
                  }
            }
      }
}


// Render the sky and surroundings
static void renderSurroundings(void)
{
      static float wind = 0.0f;
      const float skybox_scale = 10000.0f;
      const float height = 20.0f * TILE_UNITS;
      const float wider  = 2.0f * (visibleTiles.x * TILE_UNITS);
      int left, right, front, back, rx, rz;

      // set up matrices and textures
      pie_PerspectiveBegin();

      // Push identity matrix onto stack
      pie_MatBegin();

      // Now, scale the world according to what resolution we're running in
      pie_MatScale(pie_GetResScalingFactor());

      // Set the camera position
      pie_MATTRANS(0, 0, distance);

      // Rotate for the player and for the wind
      pie_MatRotZ(player.r.z);
      pie_MatRotX(player.r.x);
      pie_MatRotY(player.r.y);

      // Fogbox //
      rx = (player.p.x) & (TILE_UNITS-1);
      rz = (player.p.z) & (TILE_UNITS-1);
      pie_TRANSLATE(-rx, -player.p.y, rz);

      left  = TILE_UNITS * MIN(visibleTiles.x/2, playerXTile+visibleTiles.x/2+1);
      right = TILE_UNITS * MIN(visibleTiles.x/2, mapWidth-playerXTile-visibleTiles.x/2);
      front = TILE_UNITS * MIN(visibleTiles.y/2, playerZTile+visibleTiles.y/2+1);
      back  = TILE_UNITS * MIN(visibleTiles.y/2, mapHeight-playerZTile-visibleTiles.y/2);

      pie_DrawFogBox(left, right, front, back, height, wider);

      // undo the translation
      pie_TRANSLATE(rx,player.p.y,-rz);

      // Skybox //
      // rotate it
      pie_MatRotY(DEG(1) * wind);

      // move it somewhat below ground level for the blending effect
      pie_TRANSLATE(0, -skybox_scale/8, 0);

      // Set the texture page
      pie_SetTexturePage(iV_GetTexture(skyboxPageName));

      if(!gamePaused())
      {
            wind = wrapf(wind + timeAdjustedIncrement(0.5f, false), 360.0f);
      }
      pie_DrawSkybox(skybox_scale, 0, 128, 256, 128);

      // Load Saved State
      pie_MatEnd();
      pie_PerspectiveEnd();
}

/* Flattens an imd to the landscape and handles 4 different rotations */
static iIMDShape  *flattenImd(iIMDShape *imd, UDWORD structX, UDWORD structY, UDWORD direction)
{
      UDWORD i, centreHeight;

      ASSERT( imd->npoints < iV_IMD_MAX_POINTS, "flattenImd: too many points in the PIE to flatten it" );

      /* Get a copy of the points */
      memcpy(alteredPoints, imd->points, imd->npoints * sizeof(Vector3f));

      /* Get the height of the centre point for reference */
      centreHeight = map_Height(structX,structY);

      /* Now we go through the shape looking for vertices on the edge */
      /* Flip reference coords if we're on a vertical wall */

      /* Little hack below 'cos sometimes they're not exactly 90 degree alligned. */
      direction /= 90;
      direction *= 90;

      switch(direction)
      {
      case 0:
            for(i = 0; i < (UDWORD)imd->npoints; i++)
            {
                  if (abs(alteredPoints[i].x) >= 63 || abs(alteredPoints[i].z) >= 63)
                  {
                        UDWORD tempX = MIN(structX + alteredPoints[i].x, world_coord(mapWidth - 1));
                        UDWORD tempY = MAX(structY - alteredPoints[i].z, 0);
                        SDWORD shift = centreHeight - map_Height(tempX, tempY);

                        alteredPoints[i].y -= (shift - 4);
                  }
            }
            break;
      case 90:
            for(i=0; i<(UDWORD)imd->npoints; i++)
            {
                  if (abs(alteredPoints[i].x) >= 63 || abs(alteredPoints[i].z) >= 63)
                  {
                        UDWORD tempX = MAX(structX - alteredPoints[i].z, 0);
                        UDWORD tempY = MAX(structY - alteredPoints[i].x, 0);
                        SDWORD shift = centreHeight - map_Height(tempX, tempY);

                        alteredPoints[i].y -= (shift - 4);
                  }
            }
            break;
      case 180:
            for(i=0; i<(UDWORD)imd->npoints; i++)
            {
                  if (abs(alteredPoints[i].x) >= 63 || abs(alteredPoints[i].z) >= 63)
                  {
                        UDWORD tempX = MAX(structX - alteredPoints[i].x, 0);
                        UDWORD tempY = MIN(structY + alteredPoints[i].z, world_coord(mapHeight - 1));
                        SDWORD shift = centreHeight - map_Height(tempX, tempY);

                        alteredPoints[i].y -= (shift - 4);
                  }
            }
            break;
      case 270:
            for(i=0; i<(UDWORD)imd->npoints; i++)
            {
                  if(abs(alteredPoints[i].x) >= 63 || abs(alteredPoints[i].z)>=63)
                  {
                        UDWORD tempX = MIN(structX + alteredPoints[i].z, world_coord(mapWidth - 1));
                        UDWORD tempY = MIN(structY + alteredPoints[i].x, world_coord(mapHeight - 1));
                        SDWORD shift = centreHeight - map_Height(tempX, tempY);

                        alteredPoints[i].y -= (shift - 4);
                  }
            }
            break;
      default:
            debug(LOG_ERROR, "Weird direction (%u) for a structure in flattenImd", direction);
            abort();
            break;
      }

      imd->points = alteredPoints;
      return imd;
}

// -------------------------------------------------------------------------------------
/* New improved (and much faster) tile drawer */
// -------------------------------------------------------------------------------------
static void drawTerrainTile(UDWORD i, UDWORD j, BOOL onWaterEdge)
{
      /* Get the correct tile index for the x/y coordinates */
      SDWORD actualX = playerXTile + j, actualY = playerZTile + i;
      MAPTILE *psTile = NULL;
      BOOL bOutlined = false;
      UDWORD tileNumber = 0;
      TERRAIN_VERTEX vertices[3];
      PIELIGHT colour[2][2];

      colour[0][0] = WZCOL_BLACK;
      colour[1][0] = WZCOL_BLACK;
      colour[0][1] = WZCOL_BLACK;
      colour[1][1] = WZCOL_BLACK;

      /* Let's just get out now if we're not supposed to draw it */
      if (actualX < 0 || actualY < 0 || actualX > mapWidth - 1 || actualY > mapHeight - 1)
      {
            tileNumber = 0;
      }
      else
      {
            psTile = mapTile(actualX, actualY);

            colour[0][0] = psTile->colour;
            if (actualY + 1 < mapHeight - 1)
            {
                  colour[1][0] = mapTile(actualX, actualY + 1)->colour;
            }
            if (actualX + 1 < mapWidth - 1)
            {
                  colour[0][1] = mapTile(actualX + 1, actualY)->colour;
            }
            if (actualX + 1 < mapWidth - 1 && actualY + 1 < mapHeight - 1)
            {
                  colour[1][1] = mapTile(actualX + 1, actualY + 1)->colour;
            }
            if ( terrainType(psTile) != TER_WATER || onWaterEdge )
            {
                  // what tile texture number is it?
                  tileNumber = psTile->texture;
            }
            else
            {
                  // If it's a water tile then force it to be the river bed texture.
                  tileNumber = RIVERBED_TILE;
            }
      }

      /* Show gateways */
      if (psTile && psTile->tileInfoBits & BITS_GATEWAY && showGateways)
      {
            colour[0][0].byte.g = 255;
            colour[1][0].byte.g = 255;
            colour[0][1].byte.g = 255;
            colour[1][1].byte.g = 255;
      }

      /* Is the tile highlighted? Perhaps because there's a building foundation on it */
      if (psTile && !onWaterEdge && TileIsHighlighted(psTile))
      {
            /* Clear it for next time round */
            CLEAR_TILE_HIGHLIGHT(psTile);
            bOutlined = true;
            if ( i < visibleTiles.x && j < visibleTiles.y ) // FIXME
            {
                  if (outlineTile)
                  {
                        colour[0][0] = pal_SetBrightness(255);
                        colour[1][0] = pal_SetBrightness(255);
                        colour[0][1] = pal_SetBrightness(255);
                        colour[1][1] = pal_SetBrightness(255);
                  }
                  else
                  {
                        colour[0][0].byte.r = 255;
                        colour[1][0].byte.r = 255;
                        colour[0][1].byte.r = 255;
                        colour[1][1].byte.r = 255;
                  }
            }
      }

      /* Check for rotations and flips - this sets up the coordinates for texturing */
      flipsAndRots(tileNumber, i, j);

      /* The first triangle */
      vertices[0] = tileScreenInfo[i + 0][j + 0];
      vertices[1] = tileScreenInfo[i + 0][j + 1];
      vertices[0].light = colour[0][0];
      vertices[1].light = colour[0][1];

      if (psTile && TRI_FLIPPED(psTile))
      {
            vertices[2] = tileScreenInfo[i + 1][j + 0];
            vertices[2].light = colour[1][0];
      }
      else
      {
            vertices[2] = tileScreenInfo[i + 1][j + 1];
            vertices[2].light = colour[1][1];
      }

      if (onWaterEdge)
      {
            pie_DrawWaterTriangle(vertices);
      }
      else if (tileOnMap(actualX, actualY))
      {
            pie_DrawTerrainTriangle(actualX, actualY, 0, vertices);
      }

      /* The second triangle */
      if (psTile && TRI_FLIPPED(psTile))
      {
            vertices[0] = tileScreenInfo[i + 0][j + 1];
            vertices[0].light = colour[0][1];
      }
      else
      {
            vertices[0] = tileScreenInfo[i + 0][j + 0];
            vertices[0].light = colour[0][0];
      }

      vertices[1] = tileScreenInfo[i + 1][j + 1];
      vertices[2] = tileScreenInfo[i + 1][j + 0];
      vertices[1].light = colour[1][1];
      vertices[2].light = colour[1][0];

      if (onWaterEdge)
      {
            pie_DrawWaterTriangle(vertices);
      }
      else if (tileOnMap(actualX, actualY))
      {
            pie_DrawTerrainTriangle(actualX, actualY, 1, vertices);
      }
}


// Render a water tile.
//
static void drawTerrainWaterTile(UDWORD i, UDWORD j)
{
      /* Get the correct tile index for the x/y coordinates */
      const unsigned int actualX = playerXTile + j, actualY = playerZTile + i;
      PIELIGHT colour[2][2];
      MAPTILE *psTile;

      /* Let's just get out now if we're not supposed to draw it */
      if ( actualX > mapWidth - 1 || actualY > mapHeight - 1 )
      {
            return;
      }

      psTile = mapTile(actualX, actualY);
      colour[0][0] = psTile->colour;
      colour[1][0] = WZCOL_BLACK;
      colour[0][1] = WZCOL_BLACK;
      colour[1][1] = WZCOL_BLACK;

      if (actualY + 1 < mapHeight - 1)
      {
            colour[1][0] = mapTile(actualX, actualY + 1)->colour;
      }
      if (actualX + 1 < mapWidth - 1)
      {
            colour[0][1] = mapTile(actualX + 1, actualY)->colour;
      }
      if (actualX + 1 < mapWidth - 1 && actualY + 1 < mapHeight - 1)
      {
            colour[1][1] = mapTile(actualX + 1, actualY + 1)->colour;
      }

      // If it's a water tile then draw the water
      if (terrainType(psTile) == TER_WATER)
      {
            /* Used to calculate texture coordinates */
            const float xMult = 1.0f / TILES_IN_PAGE_COLUMN;
            const float yMult = 1.0f / (2.0f * TILES_IN_PAGE_ROW);
            const float one = 1.0f / (TILES_IN_PAGE_COLUMN * (float)getTextureSize());
            const unsigned int tileNumber = getWaterTileNum();
            TERRAIN_VERTEX vertices[3];

            tileScreenInfo[i+0][j+0].u = tileTexInfo[TileNumber_tile(tileNumber)].uOffset + one;
            tileScreenInfo[i+0][j+0].v = tileTexInfo[TileNumber_tile(tileNumber)].vOffset;

            tileScreenInfo[i+0][j+1].u = tileTexInfo[TileNumber_tile(tileNumber)].uOffset + (xMult - one);
            tileScreenInfo[i+0][j+1].v = tileTexInfo[TileNumber_tile(tileNumber)].vOffset;

            tileScreenInfo[i+1][j+1].u = tileTexInfo[TileNumber_tile(tileNumber)].uOffset + (xMult - one);
            tileScreenInfo[i+1][j+1].v = tileTexInfo[TileNumber_tile(tileNumber)].vOffset + (yMult - one);

            tileScreenInfo[i+1][j+0].u = tileTexInfo[TileNumber_tile(tileNumber)].uOffset + one;
            tileScreenInfo[i+1][j+0].v = tileTexInfo[TileNumber_tile(tileNumber)].vOffset + (yMult - one);

            vertices[0] = tileScreenInfo[i + 0][j + 0];
            vertices[0].light = colour[0][0];
            vertices[0].light.byte.a = WATER_ALPHA_LEVEL;

            vertices[1] = tileScreenInfo[i + 0][j + 1];
            vertices[1].light = colour[0][1];
            vertices[1].light.byte.a = WATER_ALPHA_LEVEL;

            vertices[2] = tileScreenInfo[i + 1][j + 1];
            vertices[2].light = colour[1][1];
            vertices[2].light.byte.a = WATER_ALPHA_LEVEL;

            pie_DrawWaterTriangle(vertices);

            vertices[1] = vertices[2];
            vertices[2] = tileScreenInfo[i + 1][j + 0];
            vertices[2].light = colour[1][0];
            vertices[2].light.byte.a = WATER_ALPHA_LEVEL;

            pie_DrawWaterTriangle(vertices);
      }
}


UDWORD      getSuggestedPitch( void )
{
      UDWORD      worldAngle;
      UDWORD      xPos,yPos;
      SDWORD      pitch;

      worldAngle = (UDWORD) ((UDWORD)player.r.y/DEG_1)%360;
      /* Now, we need to track angle too - to avoid near z clip! */

      xPos = player.p.x + world_coord(visibleTiles.x/2);
      yPos = player.p.z + world_coord(visibleTiles.y/2);
//    getBestPitchToEdgeOfGrid(xPos,yPos,360-worldAngle,&pitch);
      getPitchToHighestPoint(xPos, yPos, 360-worldAngle, 0, &pitch);

      if (pitch < abs(MAX_PLAYER_X_ANGLE))
            pitch = abs(MAX_PLAYER_X_ANGLE);
      if (pitch > abs(MIN_PLAYER_X_ANGLE))
            pitch = abs(MIN_PLAYER_X_ANGLE);

      return(pitch);
}


// -------------------------------------------------------------------------------------
static void trackHeight( float desiredHeight )
{
      static float heightSpeed = 0.0f;
      float separation = desiredHeight - player.p.y;  // How far are we from desired height?
      float acceleration = ACCEL_CONSTANT * separation - VELOCITY_CONSTANT * heightSpeed; // Work out accelertion

      /* ...and now speed */
      heightSpeed += timeAdjustedIncrement(acceleration, false);

      /* Adjust the height accordingly */
      player.p.y += timeAdjustedIncrement(heightSpeed, false);
}


// -------------------------------------------------------------------------------------
void toggleEnergyBars(void)
{
      if (++barMode == BAR_LAST)
      {
            barMode = BAR_SELECTED;
      }
}

// -------------------------------------------------------------------------------------
void assignSensorTarget( BASE_OBJECT *psObj )
{
      bSensorTargetting = true;
      lastTargetAssignation = gameTime2;
      psSensorObj = psObj;
}

// -------------------------------------------------------------------------------------
void  assignDestTarget( void )
{
      bDestTargetting = true;
      lastDestAssignation = gameTime2;
      destTargetX = mouseX();
      destTargetY = mouseY();
      destTileX = mouseTileX;
      destTileY = mouseTileY;
}

// -------------------------------------------------------------------------------------
static void processSensorTarget( void )
{
      SWORD x,y;
      SWORD offset;
      SWORD x0,y0,x1,y1;
      UDWORD      index;


      if(bSensorTargetting)
      {
            if( (gameTime2 - lastTargetAssignation) < TARGET_TO_SENSOR_TIME)
            {
                  if(!psSensorObj->died && psSensorObj->sDisplay.frameNumber == currentGameFrame)
                  {
                        x = /*mouseX();*/(SWORD)psSensorObj->sDisplay.screenX;
                        y = (SWORD)psSensorObj->sDisplay.screenY;
                        if(!gamePaused())
                        {
                              index = IMAGE_BLUE1+getStaticTimeValueRange(1020,5);
                        }
                        else
                        {
                              index = IMAGE_BLUE1;
                        }
                        iV_DrawImage(IntImages,index,x,y);

                        offset = (SWORD)(12+ ((TARGET_TO_SENSOR_TIME)-(gameTime2-
                              lastTargetAssignation))/2);

                        x0 = (SWORD)(x-offset);
                        y0 = (SWORD)(y-offset);
                        x1 = (SWORD)(x+offset);
                        y1 = (SWORD)(y+offset);

                        iV_Line(x0, y0, x0 + 8, y0, WZCOL_WHITE);
                        iV_Line(x0, y0, x0, y0 + 8, WZCOL_WHITE);

                        iV_Line(x1, y0, x1 - 8, y0, WZCOL_WHITE);
                        iV_Line(x1, y0, x1, y0 + 8, WZCOL_WHITE);

                        iV_Line(x1, y1,x1 - 8, y1, WZCOL_WHITE);
                        iV_Line(x1, y1,x1, y1 - 8, WZCOL_WHITE);

                        iV_Line(x0, y1, x0 + 8, y1, WZCOL_WHITE);
                        iV_Line(x0, y1, x0, y1 - 8, WZCOL_WHITE);
                  }
                  else
                  {
                        bSensorTargetting = false;
                  }
            }
            else
            {
                  bSensorTargetting = false;
            }
      }

}

// -------------------------------------------------------------------------------------
static void processDestinationTarget( void )
{
      SWORD x,y;
      SWORD offset;
      SWORD x0,y0,x1,y1;


      if(bDestTargetting)
      {
            if( (gameTime2 - lastDestAssignation) < DEST_TARGET_TIME)
            {
                        x = (SWORD)destTargetX;
                        y = (SWORD)destTargetY;

                        offset = (SWORD)(((DEST_TARGET_TIME)-(gameTime2-lastDestAssignation))/2);

                        x0 = (SWORD)(x-offset);
                        y0 = (SWORD)(y-offset);
                        x1 = (SWORD)(x+offset);
                        y1 = (SWORD)(y+offset);

                        pie_BoxFill(x0, y0, x0 + 2, y0 + 2, WZCOL_WHITE);
                        pie_BoxFill(x1 - 2, y0 - 2, x1, y0, WZCOL_WHITE);
                        pie_BoxFill(x1 - 2, y1 - 2, x1, y1, WZCOL_WHITE);
                        pie_BoxFill(x0, y1, x0 + 2, y1 + 2, WZCOL_WHITE);
            }
            else
            {
                  bDestTargetting = false;
            }
      }
}

// -------------------------------------------------------------------------------------
void  setUnderwaterTile(UDWORD num)
{
      underwaterTile = num;
}
// -------------------------------------------------------------------------------------
void  setRubbleTile(UDWORD num)
{
      rubbleTile = num;
}
// -------------------------------------------------------------------------------------
UDWORD      getWaterTileNum( void )
{
      return(underwaterTile);
}
// -------------------------------------------------------------------------------------
UDWORD      getRubbleTileNum( void )
{
      return(rubbleTile);
}
// -------------------------------------------------------------------------------------

static UDWORD     lastSpinVal;

static void structureEffectsPlayer( UDWORD player )
{
      SDWORD      val;
      SDWORD      radius;
      UDWORD      angle;
      STRUCTURE   *psStructure;
      SDWORD      xDif,yDif;
      Vector3i    pos;
      UDWORD      numConnected;
      DROID *psDroid;
      UDWORD      gameDiv;
      UDWORD      i;
      BASE_OBJECT             *psChosenObj = NULL;
      UWORD bFXSize;

      for(psStructure = apsStructLists[player]; psStructure; psStructure = psStructure->psNext)
      {
            if(psStructure->status == SS_BUILT)
            {
                  if(psStructure->pStructureType->type == REF_POWER_GEN && psStructure->visible[selectedPlayer])
                  {
                        POWER_GEN* psPowerGen = &psStructure->pFunctionality->powerGenerator;
                        numConnected = 0;
                        for (i = 0; i < NUM_POWER_MODULES; i++)
                        {
                              if (psPowerGen->apResExtractors[i])
                              {
                                    numConnected++;
                              }
                        }
                        /* No effect if nothing connected */
                        if(!numConnected)
                        {
                              //keep looking for another!
                              continue;
                        }

                        else switch(numConnected)
                        {
                        case 1:
                        case 2:
                              gameDiv = 1440;
                              val = 4;
                              break;
                        case 3:
                        case 4:
                        default:
                              gameDiv = 1080;
                              val = 3;      // really fast!!!
                              break;
                        }

                        angle = gameTime2%gameDiv;
                        val = angle/val;

                        /* New addition - it shows how many are connected... */
                        for(i=0 ;i<numConnected; i++)
                        {
                              radius = 32 - (i*2);    // around the spire
                              xDif = radius * (SIN(DEG(val)));
                              yDif = radius * (COS(DEG(val)));

                              xDif = xDif/4096;  // cos it's fixed point
                              yDif = yDif/4096;

                              pos.x = psStructure->pos.x + xDif;
                              pos.z = psStructure->pos.y + yDif;
                              pos.y = map_Height(pos.x,pos.z) + 64 + (i*20);  // 64 up to get to base of spire
                              effectGiveAuxVar(50);   // half normal plasma size...
                              addEffect(&pos,EFFECT_EXPLOSION,EXPLOSION_TYPE_LASER,false,NULL,0);

                              pos.x = psStructure->pos.x - xDif;
                              pos.z = psStructure->pos.y - yDif;
//                            pos.y = map_Height(pos.x,pos.z) + 64 + (i*20);  // 64 up to get to base of spire
                              effectGiveAuxVar(50);   // half normal plasma size...

                              addEffect(&pos,EFFECT_EXPLOSION,EXPLOSION_TYPE_LASER,false,NULL,0);
                        }
                  }
                  /* Might be a re-arm pad! */
                  else if(psStructure->pStructureType->type == REF_REARM_PAD
                        && psStructure->visible[selectedPlayer] )
                  {
                        REARM_PAD* psReArmPad = &psStructure->pFunctionality->rearmPad;
                        psChosenObj = psReArmPad->psObj;
                        if(psChosenObj!=NULL)
                        {
                              if((((DROID*)psChosenObj)->visible[selectedPlayer]))
                              {
                                    bFXSize = 0;
                                    psDroid = (DROID*) psChosenObj;
                                    if(!psDroid->died && psDroid->action == DACTION_WAITDURINGREARM )
                                    {
                                          bFXSize = 30;

                                    }
                                    /* Then it's repairing...? */
                                    if(!gamePaused())
                                    {
                                          val = lastSpinVal = getTimeValueRange(720,360); // grab an angle - 4 seconds cyclic
                                    }
                                    else
                                    {
                                          val = lastSpinVal;
                                    }
                                    radius = psStructure->sDisplay.imd->radius;
                                    xDif = radius * (SIN(DEG(val)));
                                    yDif = radius * (COS(DEG(val)));
                                    xDif = xDif/4096;  // cos it's fixed point
                                    yDif = yDif/4096;
                                    pos.x = psStructure->pos.x + xDif;
                                    pos.z = psStructure->pos.y + yDif;
                                    pos.y = map_Height(pos.x,pos.z) + psStructure->sDisplay.imd->max.y;
                                    effectGiveAuxVar(30+bFXSize); // half normal plasma size...
                                    addEffect(&pos,EFFECT_EXPLOSION, EXPLOSION_TYPE_LASER,false,NULL,0);
                                    pos.x = psStructure->pos.x - xDif;
                                    pos.z = psStructure->pos.y - yDif;  // buildings are level!
            //                      pos.y = map_Height(pos.x,pos.z) + psStructure->sDisplay->pos.max.y;
                                    effectGiveAuxVar(30+bFXSize); // half normal plasma size...
                                    addEffect(&pos,EFFECT_EXPLOSION, EXPLOSION_TYPE_LASER,false,NULL,0);
                              }
                        }
                  }
            }
      }
}

static void structureEffects()
{
      UDWORD      i;

            /* Only do for player 0 power stations */

            if(bMultiPlayer)
            {
                  for(i=0;i<MAX_PLAYERS;i++)
                  {
                        if(isHumanPlayer(i) && apsStructLists[i] )
                        {
                              structureEffectsPlayer(i);
                        }

                  }
            }
            else if(apsStructLists[0])
            {
                  structureEffectsPlayer(0);
            }
}


static void showDroidSensorRanges(void)
{
DROID       *psDroid;
STRUCTURE   *psStruct;

      if(rangeOnScreen)       // note, we still have to decide what to do with multiple units selected, since it will draw it for all of them! -Q 5-10-05
      {
      for(psDroid= apsDroidLists[selectedPlayer]; psDroid; psDroid=psDroid->psNext)
      {
            if(psDroid->selected)
            {
                  showSensorRange2((BASE_OBJECT*)psDroid);
            }
      }

      for(psStruct = apsStructLists[selectedPlayer]; psStruct; psStruct = psStruct->psNext)
      {
            if(psStruct->selected)
            {
                  showSensorRange2((BASE_OBJECT*)psStruct);
            }
      }
      }//end if we want to display...
}

static void showSensorRange2(BASE_OBJECT *psObj)
{
      SDWORD      radius;
      SDWORD      xDif,yDif;
      UDWORD      sensorRange;
      Vector3i pos;
      UDWORD      i;
      DROID *psDroid;
      STRUCTURE   *psStruct;
      BOOL  bBuilding=false;

      for(i=0; i<360; i++)
      {
            if(psObj->type == OBJ_DROID)
            {
                  psDroid = (DROID*)psObj;
                  sensorRange = asSensorStats[psDroid->asBits[COMP_SENSOR].nStat].range;
            }
            else
            {
                  psStruct = (STRUCTURE*)psObj;
                  sensorRange = structSensorRange(psStruct);
                  bBuilding = true;
            }
            radius = sensorRange;
            xDif = radius * (SIN(DEG(i)));
            yDif = radius * (COS(DEG(i)));

            xDif = xDif/4096;  // cos it's fixed point
            yDif = yDif/4096;
            pos.x = MAX(psObj->pos.x - xDif, 0);
            pos.z = MAX(psObj->pos.y - yDif, 0);
            pos.y = map_Height(pos.x,pos.z) + 16;
            effectGiveAuxVar(80);   // half normal plasma size...
            if(bBuilding)
            {
                  addEffect(&pos,EFFECT_EXPLOSION,EXPLOSION_TYPE_SMALL,false,NULL,0);
            }
            else
            {
                  addEffect(&pos,EFFECT_EXPLOSION,EXPLOSION_TYPE_LASER,false,NULL,0);
            }
      }
}

static void drawRangeAtPos(SDWORD centerX, SDWORD centerY, SDWORD radius)
{
      SDWORD      xDif,yDif;
      Vector3i    pos;
      UDWORD      i;

      for(i=0; i<360; i++)
      {
            xDif = radius * (SIN(DEG(i)));
            yDif = radius * (COS(DEG(i)));

            xDif = xDif/4096;  // cos it's fixed point
            yDif = yDif/4096;
            pos.x = centerX - xDif;
            pos.z = centerY - yDif;

            // don't draw off map
            if(pos.x >= 0 && pos.z >= 0)
            {
                  pos.y = map_Height(pos.x,pos.z) + 16;
                  effectGiveAuxVar(80);   // half normal plasma size...

                  addEffect(&pos,EFFECT_EXPLOSION,EXPLOSION_TYPE_SMALL,false,NULL,0);
            }
      }
}

/* draw some effects at certain position to visualize the radius,
* negative radius turns this off
*/
void showRangeAtPos(SDWORD centerX, SDWORD centerY, SDWORD radius)
{
      rangeCenterX = centerX;
      rangeCenterY = centerY;
      rangeRadius = radius;

      bRangeDisplay = true;

      if(radius <= 0)
            bRangeDisplay = false;
}

/*returns the graphic ID for a droid rank*/
UDWORD  getDroidRankGraphic(DROID *psDroid)
{
      UDWORD gfxId;
      /* Not found yet */
      gfxId = UDWORD_MAX;

      /* Establish the numerical value of the droid's rank */
      switch(getDroidLevel(psDroid))
      {
            case 0:
                  break;
            case 1:
                  gfxId = IMAGE_LEV_0;
                  break;
            case 2:
                  gfxId = IMAGE_LEV_1;
                  break;
            case 3:
                  gfxId = IMAGE_LEV_2;
                  break;
            case 4:
                  gfxId = IMAGE_LEV_3;
                  break;
            case 5:
                  gfxId = IMAGE_LEV_4;
                  break;
            case 6:
                  gfxId = IMAGE_LEV_5;
                  break;
            case 7:
                  gfxId = IMAGE_LEV_6;
                  break;
            case 8:
                  gfxId = IMAGE_LEV_7;
                  break;
            default:
                  ASSERT(!"out of range droid rank", "Weird droid level in drawDroidRank");
            break;
      }

      return gfxId;
}

/*    DOES : Assumes matrix context set and that z-buffer write is force enabled (Always).
      Will render a graphic depiction of the droid's present rank.
      BY : Alex McLean.
*/
static void drawDroidRank(DROID *psDroid)
{
      UDWORD      gfxId = getDroidRankGraphic(psDroid);

      /* Did we get one? - We should have... */
      if(gfxId!=UDWORD_MAX)
      {
            /* Render the rank graphic at the correct location */ // remove hardcoded numbers?!
            iV_DrawImage(IntImages,(UWORD)gfxId,psDroid->sDisplay.screenX+20,psDroid->sDisplay.screenY+8);
      }
}

/*    DOES : Assumes matrix context set and that z-buffer write is force enabled (Always).
      Will render a graphic depiction of the droid's present rank.
*/
static void drawDroidSensorLock(DROID *psDroid)
{
      //if on fire support duty - must be locked to a Sensor Droid/Structure
      if (orderState(psDroid, DORDER_FIRESUPPORT))
      {
            /* Render the sensor graphic at the correct location - which is what?!*/
            iV_DrawImage(IntImages,IMAGE_GN_STAR,psDroid->sDisplay.screenX+20,
                  psDroid->sDisplay.screenY-20);
      }
}

static      void  doConstructionLines( void )
{
DROID *psDroid;
UDWORD      i;

      for(i=0; i<MAX_PLAYERS; i++)
      {
            for(psDroid= apsDroidLists[i]; psDroid; psDroid = psDroid->psNext)
            {
                  if(clipXY(psDroid->pos.x,psDroid->pos.y))
                  {

                        if( (psDroid->visible[selectedPlayer]==UBYTE_MAX) &&
                              (psDroid->sMove.Status != MOVESHUFFLE) )
                        {
                              if(psDroid->action == DACTION_BUILD)
                              {
                                    if (psDroid->psTarget)
                                    {
                                          if (psDroid->psTarget->type == OBJ_STRUCTURE)
                                          {
                                                addConstructionLine(psDroid, (STRUCTURE*)psDroid->psTarget);
                                          }
                                    }
                              }

                              else if ((psDroid->action == DACTION_DEMOLISH) ||
                                          (psDroid->action == DACTION_REPAIR) ||
                                          (psDroid->action == DACTION_CLEARWRECK) ||
                                          (psDroid->action == DACTION_RESTORE))
                              {
                                    if(psDroid->psActionTarget[0])
                                    {
                                          if(psDroid->psActionTarget[0]->type == OBJ_STRUCTURE)
                                          {
                                                addConstructionLine(psDroid, (STRUCTURE*)psDroid->psActionTarget[0]);
                                          }
                                    }
                              }
                        }

                  }
            }
      }
      pie_SetDepthBufferStatus(DEPTH_CMP_LEQ_WRT_ON);
}

static void addConstructionLine(DROID *psDroid, STRUCTURE *psStructure)
{
      CLIP_VERTEX pts[3];
      Vector3i each;
      Vector3f *point;
      UDWORD      pointIndex;
      SDWORD      realY;
      Vector3i null, vec;
      SDWORD      rx,rz;
      PIELIGHT colour;

      null.x = null.y = null.z = 0;
      each.x = psDroid->pos.x;
      each.z = psDroid->pos.y;
      each.y = psDroid->pos.z + 24;

      vec.x = (each.x - player.p.x) - terrainMidX*TILE_UNITS;
      vec.z = terrainMidY*TILE_UNITS - (each.z - player.p.z);
      vec.y = each.y;

      rx = player.p.x & (TILE_UNITS-1);
      rz = player.p.z & (TILE_UNITS-1);
      pts[0].pos.x = vec.x + rx;
      pts[0].pos.y = vec.y;
      pts[0].pos.z = vec.z - rz;

      pointIndex = rand()%(psStructure->sDisplay.imd->npoints-1);
      point = &(psStructure->sDisplay.imd->points[pointIndex]);

      each.x = psStructure->pos.x + point->x;
      realY = structHeightScale(psStructure) * point->y;
      each.y = psStructure->pos.z + realY;
      each.z = psStructure->pos.y - point->z;

      if(ONEINEIGHT)
      {
            effectSetSize(30);
            addEffect(&each,EFFECT_EXPLOSION,EXPLOSION_TYPE_SPECIFIED,true,getImdFromIndex(MI_PLASMA),0);
      }

      vec.x = (each.x - player.p.x) - terrainMidX*TILE_UNITS;
      vec.z = terrainMidY*TILE_UNITS - (each.z - player.p.z);
      vec.y = each.y;

      rx = player.p.x & (TILE_UNITS-1);
      rz = player.p.z & (TILE_UNITS-1);
      pts[1].pos.x = vec.x + rx;
      pts[1].pos.y = vec.y;
      pts[1].pos.z = vec.z - rz;

      pointIndex = rand()%(psStructure->sDisplay.imd->npoints-1);
      point = &(psStructure->sDisplay.imd->points[pointIndex]);

      each.x = psStructure->pos.x + point->x;
      realY = structHeightScale(psStructure) * point->y;
      each.y = psStructure->pos.z + realY;
      each.z = psStructure->pos.y - point->z;

      vec.x = (each.x - player.p.x) - terrainMidX*TILE_UNITS;
      vec.z = terrainMidY*TILE_UNITS - (each.z - player.p.z);
      vec.y = each.y;

      rx = player.p.x & (TILE_UNITS-1);
      rz = player.p.z & (TILE_UNITS-1);
      pts[2].pos.x = vec.x + rx;
      pts[2].pos.y = vec.y;
      pts[2].pos.z = vec.z - rz;

      // set the colour
      colour = pal_SetBrightness(UBYTE_MAX);

      if (psDroid->action == DACTION_DEMOLISH || psDroid->action == DACTION_CLEARWRECK)
      {
            colour.byte.g = 0;
            colour.byte.b = 0;
      } else {
            colour.byte.r = 0;
            colour.byte.g = 0;
      }
      pts[0].light.rgba = 0xff000000;
      pts[1].light.rgba = 0xff000000;
      pts[2].light.rgba = 0xff000000;

      pts[0].u = 0;
      pts[0].v = 0;

      pts[1].u = 0;
      pts[1].v = 0;

      pts[2].u = 0;
      pts[2].v = 0;

      pie_TransColouredTriangle(pts, colour);
}

Generated by  Doxygen 1.6.0   Back to index