warzone2100/src/radar.cpp

679 lines
18 KiB
C++

/*
This file is part of Warzone 2100.
Copyright (C) 1999-2004 Eidos Interactive
Copyright (C) 2005-2013 Warzone 2100 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
*/
#include <string.h>
#include "lib/framework/frame.h"
#include "lib/framework/fixedpoint.h"
#include "lib/ivis_opengl/pieblitfunc.h"
// FIXME Direct iVis implementation include!
#include "lib/ivis_opengl/piematrix.h"
#include "lib/ivis_opengl/piepalette.h"
#include "lib/ivis_opengl/piestate.h"
#include "lib/ivis_opengl/piefunc.h"
#include "lib/ivis_opengl/bitimage.h"
#include "lib/gamelib/gtime.h"
#include "advvis.h"
#include "objects.h"
#include "display3d.h"
#include "map.h"
#include "component.h"
#include "radar.h"
#include "mapdisplay.h"
#include "hci.h"
#include "geometry.h"
#include "intimage.h"
#include "loop.h"
#include "warcam.h"
#include "display.h"
#include "mission.h"
#include "multiplay.h"
#include "intdisplay.h"
#include "texture.h"
#define HIT_NOTIFICATION (GAME_TICKS_PER_SEC * 2)
#define RADAR_FRAME_SKIP 10
bool bEnemyAllyRadarColor = false; /**< Enemy/ally radar color. */
RADAR_DRAW_MODE radarDrawMode = RADAR_MODE_DEFAULT; /**< Current mini-map mode. */
bool rotateRadar; ///< Rotate the radar?
static PIELIGHT colRadarAlly, colRadarMe, colRadarEnemy;
static PIELIGHT tileColours[MAX_TILES];
static UDWORD *radarBuffer = NULL;
static Vector3i playerpos;
PIELIGHT clanColours[]=
{ // see frontend2.png for team color order.
// [r,g,b,a]
{{0,255,0,255}}, // green Player 0
{{255,192,40,255}}, // orange Player 1
{{255,255,255,255}}, // grey Player 2
{{0,0,0,255}}, // black Player 3
{{255,0,0,255}}, // red Player 4
{{20,20,255,255}}, // blue Player 5
{{255,0,192,255}}, // pink Player 6
{{0,255,255,255}}, // cyan Player 7
{{255,255,0,255}}, // yellow Player 8
{{144,0,255,255}}, // purple Player 9
{{200,255,255,255}}, // white Player A (Should be brighter than grey, but grey is already maximum.)
{{128,128,255,255}}, // bright blue Player B
{{128,255,128,255}}, // neon green Player C
{{128,0,0,255}}, // infrared Player D
{{64,0,128,255}}, // ultraviolet Player E
{{128,128,0,255}}, // brown Player F
};
static PIELIGHT flashColours[]=
{ //right now the flash color is all bright red
{{254,37,37,200}}, // Player 0
{{254,37,37,200}}, // Player 1
{{254,37,37,200}}, // Player 2
{{254,37,37,200}}, // Player 3
{{254,37,37,200}}, // Player 4 (notice, brighter red)
{{254,37,37,200}}, // Player 5
{{254,37,37,200}}, // Player 6
{{254,37,37,200}}, // Player 7
{{254,37,37,200}}, // Player 8
{{254,37,37,200}}, // Player 9
{{254,37,37,200}}, // Player A
{{254,37,37,200}}, // Player B
{{254,37,37,200}}, // Player C
{{254,37,37,200}}, // Player D
{{254,37,37,200}}, // Player E
{{254,37,37,200}}, // Player F
};
static SDWORD radarWidth, radarHeight, radarCenterX, radarCenterY, radarTexWidth, radarTexHeight;
static uint8_t RadarZoom;
static float RadarZoomMultiplier = 1.0f;
static UDWORD radarBufferSize = 0;
static int frameSkip = 0;
static void DrawRadarTiles();
static void DrawRadarObjects();
static void DrawRadarExtras();
static void DrawNorth();
static void setViewingWindow();
static void radarSize(int ZoomLevel)
{
float zoom = (float)ZoomLevel * RadarZoomMultiplier / 16.0;
radarWidth = radarTexWidth * zoom;
radarHeight = radarTexHeight * zoom;
if (rotateRadar)
{
radarCenterX = pie_GetVideoBufferWidth() - BASE_GAP * 4 - MAX(radarHeight, radarWidth) / 2;
radarCenterY = pie_GetVideoBufferHeight() - BASE_GAP * 4 - MAX(radarWidth, radarHeight) / 2;
}
else
{
radarCenterX = pie_GetVideoBufferWidth() - BASE_GAP * 4 - radarWidth / 2;
radarCenterY = pie_GetVideoBufferHeight() - BASE_GAP * 4 - radarHeight / 2;
}
debug(LOG_WZ, "radar=(%u,%u) tex=(%u,%u) size=(%u,%u)", radarCenterX, radarCenterY, radarTexWidth, radarTexHeight, radarWidth, radarHeight);
}
void radarInitVars()
{
radarTexWidth = 0;
radarTexHeight = 0;
RadarZoom = DEFAULT_RADARZOOM;
debug(LOG_WZ, "Resetting radar zoom to %u", RadarZoom);
radarSize(RadarZoom);
playerpos = Vector3i(-1, -1, -1);
}
bool InitRadar()
{
// Ally/enemy/me colors
colRadarAlly = WZCOL_YELLOW;
colRadarEnemy = WZCOL_RED;
colRadarMe = WZCOL_WHITE;
if (mapWidth < 150) // too small!
{
RadarZoom = pie_GetVideoBufferWidth() <= 640 ? 14 : DEFAULT_RADARZOOM * 2;
}
return true;
}
bool resizeRadar()
{
if (radarBuffer)
{
free(radarBuffer);
}
radarTexWidth = scrollMaxX - scrollMinX;
radarTexHeight = scrollMaxY - scrollMinY;
radarBufferSize = radarTexWidth * radarTexHeight * sizeof(UDWORD);
radarBuffer = (uint32_t *)malloc(radarBufferSize);
memset(radarBuffer, 0, radarBufferSize);
if (rotateRadar)
{
RadarZoomMultiplier = (float)MAX(RADWIDTH, RADHEIGHT) / (float)MAX(radarTexWidth, radarTexHeight);
}
else
{
RadarZoomMultiplier = 1.0f;
}
debug(LOG_WZ, "Setting radar zoom to %u", RadarZoom);
radarSize(RadarZoom);
pie_SetRadar(-radarWidth/2.0 - 1, -radarHeight/2.0 - 1, radarWidth, radarHeight,
radarTexWidth, radarTexHeight, rotateRadar || (RadarZoom % 16 != 0));
setViewingWindow();
return true;
}
bool ShutdownRadar()
{
free(radarBuffer);
radarBuffer = NULL;
return true;
}
void SetRadarZoom(uint8_t ZoomLevel)
{
if (ZoomLevel < 4) // old savegame format didn't save zoom levels very well
{
ZoomLevel = DEFAULT_RADARZOOM;
}
if (ZoomLevel > MAX_RADARZOOM)
{
ZoomLevel = MAX_RADARZOOM;
}
if (ZoomLevel < MIN_RADARZOOM)
{
ZoomLevel = MIN_RADARZOOM;
}
debug(LOG_WZ, "Setting radar zoom to %u from %u", ZoomLevel, RadarZoom);
RadarZoom = ZoomLevel;
radarSize(RadarZoom);
frameSkip = 0;
resizeRadar();
}
uint8_t GetRadarZoom()
{
return RadarZoom;
}
/** Calculate the radar pixel sizes. Returns pixels per tile. */
static void CalcRadarPixelSize(float *SizeH, float *SizeV)
{
*SizeH = (float)radarHeight / (float)radarTexHeight;
*SizeV = (float)radarWidth / (float)radarTexWidth;
}
/** Given a position within the radar, return a world coordinate. */
void CalcRadarPosition(int mX, int mY, int *PosX, int *PosY)
{
int sPosX, sPosY;
float pixSizeH, pixSizeV;
Vector2f pos;
pos.x = mX - radarCenterX;
pos.y = mY - radarCenterY;
if (rotateRadar)
{
pos = Vector2f_Rotate2f(pos, -player.r.y);
}
pos.x += radarWidth/2.0;
pos.y += radarHeight/2.0;
CalcRadarPixelSize(&pixSizeH, &pixSizeV);
sPosX = pos.x / pixSizeH; // adjust for pixel size
sPosY = pos.y / pixSizeV;
sPosX += scrollMinX; // adjust for scroll limits
sPosY += scrollMinY;
#if REALLY_DEBUG_RADAR
debug(LOG_ERROR, "m=(%d,%d) radar=(%d,%d) pos(%d,%d), scroll=(%u-%u,%u-%u) sPos=(%d,%d), pixSize=(%f,%f)",
mX, mY, radarX, radarY, posX, posY, scrollMinX, scrollMaxX, scrollMinY, scrollMaxY, sPosX, sPosY, pixSizeH, pixSizeV);
#endif
// old safety code -- still necessary?
sPosX = clip(sPosX, scrollMinX, scrollMaxX);
sPosY = clip(sPosY, scrollMinY, scrollMaxY);
*PosX = sPosX;
*PosY = sPosY;
}
void drawRadar()
{
float pixSizeH, pixSizeV;
CalcRadarPixelSize(&pixSizeH, &pixSizeV);
ASSERT_OR_RETURN(, radarBuffer, "No radar buffer allocated");
// Do not recalculate frustum window coordinates if position or zoom does not change
if (playerpos.x != player.p.x || playerpos.y != player.p.y || playerpos.z != player.p.z)
{
setViewingWindow();
}
playerpos = player.p; // cache position
if (frameSkip <= 0)
{
DrawRadarTiles();
DrawRadarObjects();
pie_DownLoadRadar(radarBuffer);
frameSkip = RADAR_FRAME_SKIP;
}
frameSkip--;
pie_SetRendMode(REND_ALPHA);
pie_MatBegin();
pie_TRANSLATE(radarCenterX, radarCenterY, 0);
if (rotateRadar)
{
// rotate the map
pie_MatRotZ(player.r.y);
DrawNorth();
}
pie_RenderRadar();
pie_MatBegin();
pie_TRANSLATE(-radarWidth/2 - 1, -radarHeight/2 - 1, 0);
DrawRadarExtras();
pie_MatEnd();
drawRadarBlips(-radarWidth/2.0 - 1, -radarHeight/2.0 - 1, pixSizeH, pixSizeV);
pie_MatEnd();
}
static void DrawNorth()
{
iV_DrawImage(IntImages, RADAR_NORTH, -((radarWidth / 2.0) + iV_GetImageWidth(IntImages, RADAR_NORTH) + 1), -(radarHeight / 2.0));
}
static PIELIGHT appliedRadarColour(RADAR_DRAW_MODE radarDrawMode, MAPTILE *WTile)
{
PIELIGHT WScr = WZCOL_BLACK; // squelch warning
// draw radar on/off feature
if (!getRevealStatus() && !TEST_TILE_VISIBLE(selectedPlayer, WTile))
{
return WZCOL_RADAR_BACKGROUND;
}
switch(radarDrawMode)
{
case RADAR_MODE_TERRAIN:
{
// draw radar terrain on/off feature
PIELIGHT col = tileColours[TileNumber_tile(WTile->texture)];
col.byte.r = sqrtf(col.byte.r * WTile->illumination);
col.byte.b = sqrtf(col.byte.b * WTile->illumination);
col.byte.g = sqrtf(col.byte.g * WTile->illumination);
if (terrainType(WTile) == TER_CLIFFFACE)
{
col.byte.r /= 2;
col.byte.g /= 2;
col.byte.b /= 2;
}
if (!hasSensorOnTile(WTile, selectedPlayer))
{
col.byte.r = col.byte.r * 2 / 3;
col.byte.g = col.byte.g * 2 / 3;
col.byte.b = col.byte.b * 2 / 3;
}
if (!TEST_TILE_VISIBLE(selectedPlayer, WTile))
{
col.byte.r /= 2;
col.byte.g /= 2;
col.byte.b /= 2;
}
WScr = col;
}
break;
case RADAR_MODE_COMBINED:
{
// draw radar terrain on/off feature
PIELIGHT col = tileColours[TileNumber_tile(WTile->texture)];
col.byte.r = sqrtf(col.byte.r * (WTile->illumination + WTile->height / ELEVATION_SCALE) / 2);
col.byte.b = sqrtf(col.byte.b * (WTile->illumination + WTile->height / ELEVATION_SCALE) / 2);
col.byte.g = sqrtf(col.byte.g * (WTile->illumination + WTile->height / ELEVATION_SCALE) / 2);
if (terrainType(WTile) == TER_CLIFFFACE)
{
col.byte.r /= 2;
col.byte.g /= 2;
col.byte.b /= 2;
}
if (!hasSensorOnTile(WTile, selectedPlayer))
{
col.byte.r = col.byte.r * 2 / 3;
col.byte.g = col.byte.g * 2 / 3;
col.byte.b = col.byte.b * 2 / 3;
}
if (!TEST_TILE_VISIBLE(selectedPlayer, WTile))
{
col.byte.r /= 2;
col.byte.g /= 2;
col.byte.b /= 2;
}
WScr = col;
}
break;
case RADAR_MODE_HEIGHT_MAP:
{
WScr.byte.r = WScr.byte.g = WScr.byte.b = WTile->height / ELEVATION_SCALE;
}
break;
case RADAR_MODE_NO_TERRAIN:
{
WScr = WZCOL_RADAR_BACKGROUND;
}
break;
case NUM_RADAR_MODES:
{
assert(false);
}
break;
}
return WScr;
}
/** Draw the map tiles on the radar. */
static void DrawRadarTiles()
{
SDWORD x, y;
for (x = scrollMinX; x < scrollMaxX; x++)
{
for (y = scrollMinY; y < scrollMaxY; y++)
{
MAPTILE *psTile = mapTile(x, y);
size_t pos = radarTexWidth * (y - scrollMinY) + (x - scrollMinX);
ASSERT(pos * sizeof(*radarBuffer) < radarBufferSize, "Buffer overrun");
if (y == scrollMinY || x == scrollMinX || y == scrollMaxY - 1 || x == scrollMaxX - 1)
{
radarBuffer[pos] = WZCOL_BLACK.rgba;
continue;
}
radarBuffer[pos] = appliedRadarColour(radarDrawMode, psTile).rgba;
}
}
}
/** Draw the droids and structure positions on the radar. */
static void DrawRadarObjects()
{
UBYTE clan;
PIELIGHT playerCol;
PIELIGHT flashCol;
int x, y;
/* Show droids on map - go through all players */
for(clan = 0; clan < MAX_PLAYERS; clan++)
{
DROID *psDroid;
//see if have to draw enemy/ally color
if (bEnemyAllyRadarColor)
{
if (clan == selectedPlayer)
{
playerCol = colRadarMe;
}
else
{
playerCol = (aiCheckAlliances(selectedPlayer, clan) ? colRadarAlly : colRadarEnemy);
}
}
else
{
//original 8-color mode
STATIC_ASSERT(MAX_PLAYERS <= ARRAY_SIZE(clanColours));
playerCol = clanColours[getPlayerColour(clan)];
}
STATIC_ASSERT(MAX_PLAYERS <= ARRAY_SIZE(flashColours));
flashCol = flashColours[getPlayerColour(clan)];
/* Go through all droids */
for(psDroid = apsDroidLists[clan]; psDroid != NULL; psDroid = psDroid->psNext)
{
if (psDroid->pos.x < world_coord(scrollMinX) || psDroid->pos.y < world_coord(scrollMinY)
|| psDroid->pos.x >= world_coord(scrollMaxX) || psDroid->pos.y >= world_coord(scrollMaxY))
{
continue;
}
if (psDroid->visible[selectedPlayer]
|| (bMultiPlayer && alliancesSharedVision(game.alliance)
&& aiCheckAlliances(selectedPlayer,psDroid->player)))
{
int x = psDroid->pos.x / TILE_UNITS;
int y = psDroid->pos.y / TILE_UNITS;
size_t pos = (x - scrollMinX) + (y - scrollMinY) * radarTexWidth;
ASSERT(pos * sizeof(*radarBuffer) < radarBufferSize, "Buffer overrun");
if (clan == selectedPlayer && gameTime-psDroid->timeLastHit < HIT_NOTIFICATION)
{
radarBuffer[pos] = flashCol.rgba;
}
else
{
radarBuffer[pos] = playerCol.rgba;
}
}
}
}
/* Do the same for structures */
for (x = scrollMinX; x < scrollMaxX; x++)
{
for (y = scrollMinY; y < scrollMaxY; y++)
{
MAPTILE *psTile = mapTile(x, y);
STRUCTURE *psStruct;
size_t pos = (x - scrollMinX) + (y - scrollMinY) * radarTexWidth;
ASSERT(pos * sizeof(*radarBuffer) < radarBufferSize, "Buffer overrun");
if (!TileHasStructure(psTile))
{
continue;
}
psStruct = (STRUCTURE *)psTile->psObject;
clan = psStruct->player;
//see if have to draw enemy/ally color
if (bEnemyAllyRadarColor)
{
if (clan == selectedPlayer)
{
playerCol = colRadarMe;
}
else
{
playerCol = (aiCheckAlliances(selectedPlayer, clan) ? colRadarAlly: colRadarEnemy);
}
}
else
{
//original 8-color mode
playerCol = clanColours[getPlayerColour(clan)];
}
flashCol = flashColours[getPlayerColour(clan)];
if (psStruct->visible[selectedPlayer]
|| (bMultiPlayer && alliancesSharedVision(game.alliance)
&& aiCheckAlliances(selectedPlayer, psStruct->player)))
{
if (clan == selectedPlayer && gameTime - psStruct->timeLastHit < HIT_NOTIFICATION)
{
radarBuffer[pos] = flashCol.rgba;
}
else
{
radarBuffer[pos] = playerCol.rgba;
}
}
}
}
}
/** Rotate an array of 2d vectors about a given angle, also translates them after rotating. */
static void RotateVector2D(Vector3i *Vector, Vector3i *TVector, Vector3i *Pos, int Angle, int Count)
{
int64_t Cos = iCos(Angle);
int64_t Sin = iSin(Angle);
int ox = 0;
int oy = 0;
int i;
Vector3i *Vec = Vector;
Vector3i *TVec = TVector;
if (Pos)
{
ox = Pos->x;
oy = Pos->y;
}
for (i = 0; i < Count; i++)
{
TVec->x = ((Vec->x*Cos + Vec->y*Sin) >> 16) + ox;
TVec->y = ((Vec->y*Cos - Vec->x*Sin) >> 16) + oy;
Vec++;
TVec++;
}
}
static SDWORD getDistanceAdjust( void )
{
int dif = std::max<int>(MAXDISTANCE - getViewDistance(), 0);
return dif / 100;
}
static SDWORD getLengthAdjust( void )
{
const int pitch = 360 - (player.r.x/DEG_1);
// Max at
const int lookingDown = (0 - MIN_PLAYER_X_ANGLE);
const int lookingFar = (0 - MAX_PLAYER_X_ANGLE);
int dif = MAX(pitch - lookingFar, 0);
if (dif > (lookingDown - lookingFar))
{
dif = (lookingDown - lookingFar);
}
return dif / 2;
}
/** Draws a Myth/FF7 style viewing window */
static void setViewingWindow()
{
float pixSizeH, pixSizeV;
Vector3i v[4], tv[4], centre;
int shortX, longX, yDrop, yDropVar;
int dif = getDistanceAdjust();
int dif2 = getLengthAdjust();
PIELIGHT colour;
CalcRadarPixelSize(&pixSizeH, &pixSizeV);
int x = player.p.x * pixSizeH / TILE_UNITS;
int y = player.p.z * pixSizeV / TILE_UNITS;
shortX = ((visibleTiles.x / 4) - (dif / 6)) * pixSizeH;
longX = ((visibleTiles.x / 2) - (dif / 4)) * pixSizeH;
yDropVar = ((visibleTiles.y / 2) - (dif2 / 3)) * pixSizeV;
yDrop = ((visibleTiles.y / 2) - dif2 / 3) * pixSizeV;
v[0].x = longX;
v[0].y = -yDropVar;
v[1].x = -longX;
v[1].y = -yDropVar;
v[2].x = shortX;
v[2].y = yDrop;
v[3].x = -shortX;
v[3].y = yDrop;
centre.x = x - scrollMinX * pixSizeH;
centre.y = y - scrollMinY * pixSizeV;
RotateVector2D(v,tv,&centre,player.r.y,4);
switch (getCampaignNumber())
{
case 1:
case 2:
// white
colour.byte.r = UBYTE_MAX;
colour.byte.g = UBYTE_MAX;
colour.byte.b = UBYTE_MAX;
colour.byte.a = 0x3f;
break;
case 3:
// greenish
colour.byte.r = 0x3f;
colour.byte.a = 0x3f;
colour.byte.g = UBYTE_MAX;
colour.byte.b = 0x3f;
default:
// black
colour.rgba = 0;
colour.byte.a = 0x3f;
break;
}
/* Send the four points to the draw routine and the clip box params */
pie_SetViewingWindow(tv, colour);
}
static void DrawRadarExtras()
{
pie_DrawViewingWindow();
RenderWindowFrame(FRAME_RADAR, -1, -1, radarWidth + 2, radarHeight + 2);
}
/** Does a screen coordinate lie within the radar area? */
bool CoordInRadar(int x,int y)
{
Vector2f pos;
pos.x = x - radarCenterX;
pos.y = y - radarCenterY;
if (rotateRadar)
{
pos = Vector2f_Rotate2f(pos, -player.r.y);
}
pos.x += radarWidth/2.0;
pos.y += radarHeight/2.0;
if (pos.x<0 || pos.y<0 || pos.x>=radarWidth || pos.y>=radarHeight)
{
return false;
}
return true;
}
void radarColour(UDWORD tileNumber, uint8_t r, uint8_t g, uint8_t b)
{
tileColours[tileNumber].byte.r = r;
tileColours[tileNumber].byte.g = g;
tileColours[tileNumber].byte.b = b;
tileColours[tileNumber].byte.a = 255;
}