ns/releases/3.2.0/source/mod/AvHMiniMap.cpp

540 lines
15 KiB
C++
Raw Normal View History

#include "mod/AvHMiniMap.h"
#include "mod/AvHSharedUtil.h"
#include "mod/AvHSpecials.h"
#include "mod/AvHNetworkMessages.h"
#ifdef AVH_CLIENT
#include "util/hl/spritegn.h"
#endif
#ifdef AVH_SERVER
#include "mod/AvHPlayer.h"
#endif
const int kMaxScreenWidth = 1600;
const int kMaxScreenHeight = 1200;
const int kHitWorldPaletteIndex = 0;
const int kBorderPaletteIndexStart = 1;
const int kBorderPaletteIndexEnd = 2;
const int kGroundStartPaletteIndex = 3;
const int kGroundEndPaletteIndex = 254;
const int kHitNothingPaletteIndex = 255;
// Network message:
// 0: means start processing, pass map name then num samples to process, map width, map height
// 1: means update, pass num pixels, then data
// 2: means end processing
void SafeWrite (FILE *f, void *buffer, int count)
{
if (fwrite (buffer, 1, count, f) != (size_t)count)
{
ASSERT(false);
}
}
// Init class members
AvHMiniMap::AvHMiniMap()
{
this->mMap = NULL;
this->mIsProcessing = false;
this->mNumSamplesProcessed = 0;
this->mNumSamplesToProcess = 0;
this->mMinX = this->mMinY = this->mMaxX = this->mMaxY = 0;
this->mMinViewHeight = this->mMaxViewHeight = 0;
#ifdef AVH_SERVER
this->mPlayer = NULL;
#endif
}
AvHMiniMap::~AvHMiniMap()
{
delete [] this->mMap;
this->mMap = NULL;
}
bool AvHMiniMap::GetIsProcessing(float* outPercentageDone) const
{
bool theIsProcessing = false;
if(this->mIsProcessing || (this->mNumSamplesProcessed == this->mNumSamplesToProcess && this->mNumSamplesProcessed > 0))
{
if(outPercentageDone)
{
*outPercentageDone = (float)this->mNumSamplesProcessed/this->mNumSamplesToProcess;
}
theIsProcessing = true;
}
return theIsProcessing;
}
#ifdef AVH_SERVER
void AvHMiniMap::BuildMiniMap(const char* inMapName, AvHPlayer* inPlayer, const AvHMapExtents& inMapExtents)
{
const int kNumGroundFloorColors = 249;
this->mMapName = inMapName;
this->mPlayer = inPlayer;
// In case BuildMiniMap is called multiple times
delete [] this->mMap;
// New a hi-res version of the map (enough for the 1600 version)
this->mMapWidth = kSpriteWidth;
this->mMapHeight = kSpriteWidth;
this->mNumSamplesToProcess = this->mMapWidth*this->mMapHeight;
this->mMap = new uint8[this->mNumSamplesToProcess];
this->mNumSamplesProcessed = 0;
this->mMinX = inMapExtents.GetMinMapX();//inMinX;
this->mMinY = inMapExtents.GetMinMapY();//inMinY;
this->mMaxX = inMapExtents.GetMaxMapX();//inMaxX;
this->mMaxY = inMapExtents.GetMaxMapY();//inMaxY;
this->mMinViewHeight = inMapExtents.GetMinViewHeight();//inMinViewHeight;
this->mMaxViewHeight = inMapExtents.GetMaxViewHeight();//inMaxViewHeight;
this->mIsProcessing = true;
// Tell player to rebuild minimap
NetMsg_BuildMiniMap_Initialize( this->mPlayer->pev, this->mMapName, this->mNumSamplesToProcess, this->mMapWidth, this->mMapHeight );
}
bool AvHMiniMap::Process()
{
bool theProcessingComplete = false;
if(this->GetIsProcessing())
{
// Process x pixels
// If we've calculated them all, return true
// positive y on component is down, but that means negative y in world
float theDiffY = this->mMaxY - this->mMinY;
// left to right
float theDiffX = this->mMaxX - this->mMinX;
// Preserve map aspect ratio
float theMapAspectRatio = (this->mMaxX - this->mMinX)/(this->mMaxY - this->mMinY);
float theXScale, theYScale;
if(theMapAspectRatio > 1.0f)
{
theXScale = 1.0f;
theYScale = 1.0f/theMapAspectRatio;
}
else
{
theXScale = 1.0f/theMapAspectRatio;
theYScale = 1.0f;
}
float theMapCenterX = (this->mMinX + this->mMaxX)/2.0f;
float theMapCenterY = (this->mMinY + this->mMaxY)/2.0f;
const int kNumPixelsPerCall = 50;
uint8 theSampleArray[kNumPixelsPerCall];
memset(theSampleArray, 0, kNumPixelsPerCall);
int i=0;
for(i = 0; (i < kNumPixelsPerCall) && (this->mNumSamplesProcessed < this->mNumSamplesToProcess); i++)
{
int theSampleIndex = this->mNumSamplesProcessed;
int theX = theSampleIndex % this->mMapWidth;
int theY = theSampleIndex/this->mMapWidth;
// Initialize the value to outside the map
int theValue = kHitNothingPaletteIndex;
// Account for map center and aspect ratio
float theXComponent = (theX/(float)this->mMapWidth) - .5f;
float theYComponent = (theY/(float)this->mMapHeight) - .5f;
float theCurrentX = theMapCenterX + theXComponent*theDiffX*theXScale;
float theCurrentY = theMapCenterY - theYComponent*theDiffY*theYScale;
// If the point is inside our map boundaries, do the trace
if((theCurrentX >= this->mMinX) && (theCurrentX <= this->mMaxX) && (theCurrentY >= this->mMinY) && (theCurrentY <= this->mMaxY))
{
// If we hit nothing, draw with the off map index
theValue = kHitNothingPaletteIndex;
int theUserThree = 0;
float theHitHeight;
float theHeightGradient = 0.0f;
if(AvHSHUTraceVerticalTangible(theCurrentX, theCurrentY, this->mMaxViewHeight, theUserThree, theHitHeight))
{
// TODO: Modify trace to return world brushes that are hit
// Set color to "world brush hit", it will be changed if an entity was hit
theValue = kHitWorldPaletteIndex;
theHitHeight = min(mMaxViewHeight, max(theHitHeight, mMinViewHeight));
theHeightGradient = 1.0f - (this->mMaxViewHeight - theHitHeight)/(this->mMaxViewHeight - this->mMinViewHeight);
theValue = kGroundStartPaletteIndex + (kGroundEndPaletteIndex - kGroundStartPaletteIndex)*theHeightGradient;
}
}
int theIndex = theX + theY*this->mMapWidth;
ASSERT(theIndex < this->mNumSamplesToProcess);
this->mMap[theIndex] = theValue;
theSampleArray[i] = theValue;
this->mNumSamplesProcessed++;
}
// This could be less than kNumPixelsPerCall if it's the last time through
int theNumSamples = i;
// Tell player to rebuild minimap
NetMsg_BuildMiniMap_Update( this->mPlayer->pev, theNumSamples, theSampleArray );
if(this->mNumSamplesProcessed == this->mNumSamplesToProcess)
{
theProcessingComplete = true;
this->mIsProcessing = false;
NetMsg_BuildMiniMap_Complete( this->mPlayer->pev );
}
}
return theProcessingComplete;
}
#endif
#ifdef AVH_CLIENT
string AvHMiniMap::GetSpriteNameFromMap(int inSpriteWidth, const string& inMapName, int useLabels)
{
char theWidthString[128];
sprintf(theWidthString, "%d", inSpriteWidth);
// puzl: 1064
// insert _labelled into the filename before ".spr"
string extraname="";
if ( useLabels == 1 ) {
extraname="_labelled";
}
string theMiniMapName = kMiniMapSpritesDirectory + string("/") /*+ string(theWidthString)*/ + inMapName + extraname + string(".spr");
// :puzl
return theMiniMapName;
}
void AvHMiniMap::InitializePalette()
{
// // Test data
// memset(this->mMap, kTransparentPaletteIndex, theNumSamples);
// for(int i = 0; i < this->mMapHeight; i++)
// {
// char theFillChar = i % 256;
// memset(this->mMap + i*this->mMapWidth, theFillChar, this->mMapWidth);
// }
//
// Set colors in image to use palette
memset(this->mPalette, 0, 256*3);
float theGradient = 0.0f;
for(int i = 0; i < 256; i++)
{
const int kHitWorldR = 29;
const int kHitWorldG = 59;
const int kHitWorldB = 121;
const int kBorderR = 144;
const int kBorderG = 159;
const int kBorderB = 189;
uint8* theColor = this->mPalette + i*3;
if (i >= kGroundStartPaletteIndex && i <= kGroundEndPaletteIndex)
{
// Ground start to end
// Set color according to height, blending to hit world color
theGradient = (float)(i - kGroundStartPaletteIndex)/(kGroundEndPaletteIndex - kGroundStartPaletteIndex);
theColor[0] = (int)(theGradient*kHitWorldR);
theColor[1] = (int)(theGradient*kHitWorldG);
theColor[2] = (int)(theGradient*kHitWorldB);
}
else if (i >= kBorderPaletteIndexStart && i <= kBorderPaletteIndexEnd)
{
theGradient = (float)(i - kBorderPaletteIndexStart)/(kBorderPaletteIndexEnd - kBorderPaletteIndexStart);
theColor[0] = (int)(theGradient*kBorderR);
theColor[1] = (int)(theGradient*kBorderG);
theColor[2] = (int)(theGradient*kBorderB);
}
else
{
switch(i)
{
// On map but inaccessible
case kHitNothingPaletteIndex:
theColor[0] = 255;
theColor[1] = 0;
theColor[2] = 0;
break;
case kHitWorldPaletteIndex:
theColor[0] = kHitWorldR;
theColor[1] = kHitWorldG;
theColor[2] = kHitWorldB;
break;
}
}
}
}
int AvHMiniMap::ReceiveFromNetworkStream(void* const buffer, const int size)
{
bool finished;
NetMsg_BuildMiniMap( buffer, size,
this->mMapName,
this->mNumSamplesToProcess,
this->mNumSamplesProcessed,
this->mMapWidth,
this->mMapHeight,
&this->mMap,
finished
);
this->mIsProcessing = !finished;
return 1;
}
bool AvHMiniMap::WriteMapToSprite()
{
bool theSuccess = false;
if(this->GetIsProcessing())
{
// Open file
// puzl: 1064
// We always want to use the normal filename when generating a minimap
string theSpriteFileName = string(getModDirectory()) + string("/") + GetSpriteNameFromMap(0, this->mMapName, 0);
// :puzl
FILE* theFile = fopen(theSpriteFileName.c_str(), "wb");
if(theFile)
{
// Clear sprite data to transparent
memset(this->mSpriteData, 0, kSpriteDataPixels);
// Copy data
memcpy(this->mSpriteData, this->mMap, kSpriteWidth*kSpriteHeight);
int theNumFrames = 1;
this->WriteMapToSprite(theFile);
fclose(theFile);
theSuccess = true;
}
}
return theSuccess;
}
void AvHMiniMap::WriteMapToSprite(FILE* inFileHandle)
{
// Compute the number for frames based on the size of the sprite.
const int spriteWidth = 256;
const int spriteHeight = 256;
int numXFrames = mMapWidth / spriteWidth;
int numYFrames = mMapHeight / spriteHeight;
// The extra frame is the commander mode version of the map.
int numFrames = numXFrames * numYFrames + 1;
//
// write out the sprite header
//
dsprite_t spritetemp;
spritetemp.type = SPR_SINGLE;
spritetemp.texFormat = SPR_ALPHTEST;
spritetemp.boundingradius = sqrt((float)kSpriteWidth*kSpriteWidth);
spritetemp.width = spriteWidth;
spritetemp.height = spriteHeight;
spritetemp.numframes = numFrames;
spritetemp.beamlength = 0;// LittleFloat (this->sprite.beamlength);
spritetemp.synctype = ST_SYNC;
spritetemp.version = SPRITE_VERSION;
spritetemp.ident = IDSPRITEHEADER;
SafeWrite(inFileHandle, &spritetemp, sizeof(spritetemp));
short cnt = 256;
SafeWrite(inFileHandle, (void *) &cnt, sizeof(cnt));
SafeWrite(inFileHandle, this->mPalette, cnt*3);
for (int y = 0; y < numYFrames; ++y)
{
for (int x = 0; x < numXFrames; ++x)
{
spriteframetype_t theType = SPR_SINGLE;
SafeWrite ( inFileHandle, &theType, sizeof(theType));
dspriteframe_t frametemp;
frametemp.origin[0] = 0;
frametemp.origin[1] = 0;
frametemp.width = spriteWidth;
frametemp.height = spriteHeight;
SafeWrite (inFileHandle, &frametemp, sizeof (frametemp));
for (int i = 0; i < spriteHeight; ++i)
{
SafeWrite (inFileHandle, mSpriteData + (y * spriteHeight + i) * mMapWidth + x * spriteWidth, spriteWidth);
}
}
}
spriteframetype_t theType = SPR_SINGLE;
SafeWrite ( inFileHandle, &theType, sizeof(theType));
dspriteframe_t frametemp;
frametemp.origin[0] = 0;
frametemp.origin[1] = 0;
frametemp.width = kSpriteWidth / 2;
frametemp.height = kSpriteHeight / 2;
SafeWrite (inFileHandle, &frametemp, sizeof (frametemp));
SafeWrite (inFileHandle, mCommanderSpriteData, kSpriteDataPixels / 4);
}
bool AvHMiniMap::WriteSpritesIfJustFinished()
{
bool theSuccess = false;
if(this->GetIsProcessing() && (this->mNumSamplesProcessed == this->mNumSamplesToProcess))
{
this->mIsProcessing = false;
this->InitializePalette();
// Create the commander mode version of the sprite.
for (int x = 0; x < kSpriteWidth / 2; ++x)
{
for (int y = 0; y < kSpriteHeight / 2; ++y)
{
mCommanderSpriteData[x + y * (kSpriteWidth / 2)] =
mMap[(x * 2) + (y * 2) * kSpriteWidth];
}
}
this->DrawEdges(mMap, kSpriteWidth, kSpriteHeight);
this->DrawEdges(mCommanderSpriteData, kSpriteWidth / 2, kSpriteHeight / 2);
if(this->WriteMapToSprite())
{
theSuccess = true;
}
this->mNumSamplesProcessed = this->mNumSamplesToProcess = 0;
this->mIsProcessing = false;
}
// For each resolution
return theSuccess;
}
void AvHMiniMap::DrawEdges(uint8* inMap, int width, int height)
{
const int numPixels = width * height;
uint8* newMap = new uint8[numPixels];
memset(newMap, kHitNothingPaletteIndex, numPixels);
for (int y = 1; y < width - 1; ++y)
{
for (int x = 1; x < height - 1; ++x)
{
int baseIndex = x + y * width;
int color = inMap[baseIndex];
if (color == kHitNothingPaletteIndex)
{
int count = 0;
if (inMap[(x-1) + (y-1)*width] != kHitNothingPaletteIndex) ++count;
if (inMap[(x+0) + (y-1)*width] != kHitNothingPaletteIndex) ++count;
if (inMap[(x+1) + (y-1)*width] != kHitNothingPaletteIndex) ++count;
if (inMap[(x-1) + (y+0)*width] != kHitNothingPaletteIndex) ++count;
if (inMap[(x+1) + (y+0)*width] != kHitNothingPaletteIndex) ++count;
if (inMap[(x-1) + (y+1)*width] != kHitNothingPaletteIndex) ++count;
if (inMap[(x+0) + (y+1)*width] != kHitNothingPaletteIndex) ++count;
if (inMap[(x+1) + (y+1)*width] != kHitNothingPaletteIndex) ++count;
if (count > 0)
{
float i = pow((count / 8.0f), 0.5f);
//color = i * (kBorderPaletteIndexEnd - kBorderPaletteIndexStart) + kBorderPaletteIndexStart;
color = kBorderPaletteIndexEnd;
}
/*
if (mMap[(x-1) + (y-1)*mMapWidth] != kHitNothingPaletteIndex ||
mMap[(x+0) + (y-1)*mMapWidth] != kHitNothingPaletteIndex ||
mMap[(x+1) + (y-1)*mMapWidth] != kHitNothingPaletteIndex ||
mMap[(x-1) + (y+0)*mMapWidth] != kHitNothingPaletteIndex ||
mMap[(x+1) + (y+0)*mMapWidth] != kHitNothingPaletteIndex ||
mMap[(x-1) + (y+1)*mMapWidth] != kHitNothingPaletteIndex ||
mMap[(x+0) + (y+1)*mMapWidth] != kHitNothingPaletteIndex ||
mMap[(x+1) + (y+1)*mMapWidth] != kHitNothingPaletteIndex)
{
color = kBorderPaletteIndex;
}
*/
}
newMap[baseIndex] = color;
}
}
memcpy(inMap, newMap, numPixels);
delete [] newMap;
}
#endif