mirror of
https://github.com/ddnet/ddnet.git
synced 2024-09-19 09:12:19 +00:00
Compare commits
62 commits
5a9c4d2e62
...
c832ce2e0a
Author | SHA1 | Date | |
---|---|---|---|
c832ce2e0a | |||
a2e0ab2dbe | |||
255694c061 | |||
b475c67039 | |||
a266cd2f70 | |||
2c77e79061 | |||
ChillerDragon | 46c5344d71 | ||
96ad30eb21 | |||
b03245f7dd | |||
ChillerDragon | 7192bbf397 | ||
ChillerDragon | 984845b2a9 | ||
60624d5599 | |||
9d7b476c33 | |||
217103a0ee | |||
d86b510291 | |||
4f352d95d4 | |||
8f93b63b6a | |||
e69e7d87c2 | |||
7dc6346b2d | |||
e8e52a8b66 | |||
3d746099fa | |||
5212d1d7ce | |||
67eb609452 | |||
ad0fc8898f | |||
0a3a095479 | |||
ce245f479a | |||
ChillerDragon | ac0621a062 | ||
20cb02048d | |||
a5138c078e | |||
bf442a9f95 | |||
0d93b1add7 | |||
0f12044fcd | |||
8f2c288698 | |||
9963a3e3db | |||
4b6f2e22a8 | |||
7bbd51cf73 | |||
dcd02b50bb | |||
4fe956dffc | |||
ChillerDragon | 7c2f058c40 | ||
6bf4a016ba | |||
0feee9aa3e | |||
d5be8d1633 | |||
65cf776846 | |||
62075d22e3 | |||
0369946156 | |||
232018de23 | |||
868c513c0c | |||
5a4d8e26e6 | |||
ffabdee953 | |||
7e1881263d | |||
bf5add67ec | |||
a09ce576ac | |||
6af07a78b3 | |||
1477b8c69e | |||
8d431f8feb | |||
80e2de13da | |||
66fb5d5d7f | |||
d3f0c2a156 | |||
d5e81ca78d | |||
128ffd2313 | |||
af1b32d296 | |||
0ad1c08c22 |
|
@ -996,6 +996,7 @@ endif()
|
|||
########################################################################
|
||||
|
||||
set(EXPECTED_DATA
|
||||
announcement.txt
|
||||
arrow.png
|
||||
assets/entities/comfort/ddnet.png
|
||||
assets/entities/license.txt
|
||||
|
@ -2033,6 +2034,7 @@ set_src(ENGINE_INTERFACE GLOB src/engine
|
|||
ghost.h
|
||||
graphics.h
|
||||
http.h
|
||||
image.h
|
||||
input.h
|
||||
kernel.h
|
||||
keys.h
|
||||
|
@ -2139,6 +2141,7 @@ set_src(ENGINE_SHARED GLOB_RECURSE src/engine/shared
|
|||
websockets.h
|
||||
)
|
||||
set_src(ENGINE_GFX GLOB src/engine/gfx
|
||||
image.cpp
|
||||
image_loader.cpp
|
||||
image_loader.h
|
||||
image_manipulation.cpp
|
||||
|
|
0
data/announcement.txt
Normal file
0
data/announcement.txt
Normal file
|
@ -80,10 +80,10 @@ sv_rescue_delay 5
|
|||
# Message on chat displayed when joining
|
||||
sv_welcome "Welcome to my server!"
|
||||
|
||||
# File which will have the announcements (each one in new line)
|
||||
# File which contains the announcements (One on each line)
|
||||
sv_announcement_filename "announcement.txt"
|
||||
|
||||
# Number of minutes before next announcement will be displayed (from the announcement file)
|
||||
# Number of minutes before the next announcement will be displayed (from the announcement file)
|
||||
sv_announcement_interval 120
|
||||
|
||||
# Whether announcements will be displayed in their order or chosen randomly
|
||||
|
|
|
@ -17,7 +17,7 @@ def parse_config_variables(lines):
|
|||
return matches
|
||||
|
||||
def generate_regex(variable_code):
|
||||
return fr'(g_Config\.m_{variable_code}|Config\(\)->m_{variable_code}|m_pConfig->m_{variable_code})'
|
||||
return fr'(g_Config\.m_{variable_code}\b|Config\(\)->m_{variable_code}\b|m_pConfig->m_{variable_code}\b)'
|
||||
|
||||
def find_config_variables(config_variables):
|
||||
"""Returns the config variables which were not found."""
|
||||
|
|
|
@ -762,6 +762,8 @@ void CClient::DummyDisconnect(const char *pReason)
|
|||
m_aReceivedSnapshots[1] = 0;
|
||||
m_DummyConnected = false;
|
||||
m_DummyConnecting = false;
|
||||
m_DummyReconnectOnReload = false;
|
||||
m_DummyDeactivateOnReconnect = false;
|
||||
GameClient()->OnDummyDisconnect();
|
||||
}
|
||||
|
||||
|
@ -1520,7 +1522,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
|
|||
}
|
||||
}
|
||||
|
||||
if(m_DummyConnected)
|
||||
if(m_DummyConnected && !m_DummyReconnectOnReload)
|
||||
{
|
||||
DummyDisconnect(0);
|
||||
}
|
||||
|
@ -1649,9 +1651,25 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
|
|||
}
|
||||
}
|
||||
}
|
||||
else if(Conn == CONN_MAIN && (pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_MAP_RELOAD)
|
||||
{
|
||||
if(m_DummyConnected)
|
||||
{
|
||||
m_DummyReconnectOnReload = true;
|
||||
m_DummyDeactivateOnReconnect = g_Config.m_ClDummy == 0;
|
||||
g_Config.m_ClDummy = 0;
|
||||
}
|
||||
else
|
||||
m_DummyDeactivateOnReconnect = false;
|
||||
}
|
||||
else if(Conn == CONN_MAIN && (pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_CON_READY)
|
||||
{
|
||||
GameClient()->OnConnected();
|
||||
if(m_DummyReconnectOnReload)
|
||||
{
|
||||
m_DummySendConnInfo = true;
|
||||
m_DummyReconnectOnReload = false;
|
||||
}
|
||||
}
|
||||
else if(Conn == CONN_DUMMY && Msg == NETMSG_CON_READY)
|
||||
{
|
||||
|
@ -1659,7 +1677,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
|
|||
m_DummyConnecting = false;
|
||||
g_Config.m_ClDummy = 1;
|
||||
Rcon("crashmeplx");
|
||||
if(m_aRconAuthed[0])
|
||||
if(m_aRconAuthed[0] && !m_aRconAuthed[1])
|
||||
RconAuth(m_aRconUsername, m_aRconPassword);
|
||||
}
|
||||
else if(Msg == NETMSG_PING)
|
||||
|
@ -2746,6 +2764,16 @@ void CClient::Update()
|
|||
}
|
||||
}
|
||||
|
||||
if(m_DummyDeactivateOnReconnect && g_Config.m_ClDummy == 1)
|
||||
{
|
||||
m_DummyDeactivateOnReconnect = false;
|
||||
g_Config.m_ClDummy = 0;
|
||||
}
|
||||
else if(!m_DummyConnected && m_DummyDeactivateOnReconnect)
|
||||
{
|
||||
m_DummyDeactivateOnReconnect = false;
|
||||
}
|
||||
|
||||
m_LastDummy = (bool)g_Config.m_ClDummy;
|
||||
}
|
||||
|
||||
|
@ -2929,6 +2957,24 @@ void CClient::Run()
|
|||
g_UuidManager.DebugDump();
|
||||
}
|
||||
|
||||
#ifndef CONF_WEBASM
|
||||
char aNetworkError[256];
|
||||
if(!InitNetworkClient(aNetworkError, sizeof(aNetworkError)))
|
||||
{
|
||||
log_error("client", "%s", aNetworkError);
|
||||
ShowMessageBox("Network Error", aNetworkError);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if(!m_Http.Init(std::chrono::seconds{1}))
|
||||
{
|
||||
const char *pErrorMessage = "Failed to initialize the HTTP client.";
|
||||
log_error("client", "%s", pErrorMessage);
|
||||
ShowMessageBox("HTTP Error", pErrorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// init graphics
|
||||
m_pGraphics = CreateEngineGraphicsThreaded();
|
||||
Kernel()->RegisterInterface(m_pGraphics); // IEngineGraphics
|
||||
|
@ -2952,24 +2998,6 @@ void CClient::Run()
|
|||
CVideo::Init();
|
||||
#endif
|
||||
|
||||
#ifndef CONF_WEBASM
|
||||
char aNetworkError[256];
|
||||
if(!InitNetworkClient(aNetworkError, sizeof(aNetworkError)))
|
||||
{
|
||||
log_error("client", "%s", aNetworkError);
|
||||
ShowMessageBox("Network Error", aNetworkError);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if(!m_Http.Init(std::chrono::seconds{1}))
|
||||
{
|
||||
const char *pErrorMessage = "Failed to initialize the HTTP client.";
|
||||
log_error("client", "%s", pErrorMessage);
|
||||
ShowMessageBox("HTTP Error", pErrorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// init text render
|
||||
m_pTextRender = Kernel()->RequestInterface<IEngineTextRender>();
|
||||
m_pTextRender->Init();
|
||||
|
|
|
@ -182,6 +182,8 @@ class CClient : public IClient, public CDemoPlayer::IListener
|
|||
bool m_DummyConnecting = false;
|
||||
bool m_DummyConnected = false;
|
||||
float m_LastDummyConnectTime = 0.0f;
|
||||
bool m_DummyReconnectOnReload = false;
|
||||
bool m_DummyDeactivateOnReconnect = false;
|
||||
|
||||
// graphs
|
||||
CGraph m_InputtimeMarginGraph;
|
||||
|
|
|
@ -300,54 +300,6 @@ void CGraphics_Threaded::UnloadTexture(CTextureHandle *pIndex)
|
|||
FreeTextureIndex(pIndex);
|
||||
}
|
||||
|
||||
bool ConvertToRGBA(uint8_t *pDest, const CImageInfo &SrcImage)
|
||||
{
|
||||
if(SrcImage.m_Format == CImageInfo::FORMAT_RGBA)
|
||||
{
|
||||
mem_copy(pDest, SrcImage.m_pData, SrcImage.DataSize());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
const size_t SrcChannelCount = CImageInfo::PixelSize(SrcImage.m_Format);
|
||||
const size_t DstChannelCount = CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA);
|
||||
for(size_t Y = 0; Y < SrcImage.m_Height; ++Y)
|
||||
{
|
||||
for(size_t X = 0; X < SrcImage.m_Width; ++X)
|
||||
{
|
||||
size_t ImgOffsetSrc = (Y * SrcImage.m_Width * SrcChannelCount) + (X * SrcChannelCount);
|
||||
size_t ImgOffsetDest = (Y * SrcImage.m_Width * DstChannelCount) + (X * DstChannelCount);
|
||||
size_t CopySize = SrcChannelCount;
|
||||
if(SrcImage.m_Format == CImageInfo::FORMAT_RGB)
|
||||
{
|
||||
mem_copy(&pDest[ImgOffsetDest], &SrcImage.m_pData[ImgOffsetSrc], CopySize);
|
||||
pDest[ImgOffsetDest + 3] = 255;
|
||||
}
|
||||
else if(SrcImage.m_Format == CImageInfo::FORMAT_SINGLE_COMPONENT)
|
||||
{
|
||||
pDest[ImgOffsetDest + 0] = 255;
|
||||
pDest[ImgOffsetDest + 1] = 255;
|
||||
pDest[ImgOffsetDest + 2] = 255;
|
||||
pDest[ImgOffsetDest + 3] = SrcImage.m_pData[ImgOffsetSrc];
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
IGraphics::CTextureHandle CGraphics_Threaded::LoadSpriteTextureImpl(const CImageInfo &FromImageInfo, int x, int y, size_t w, size_t h, const char *pName)
|
||||
{
|
||||
m_vSpriteHelper.resize(w * h * FromImageInfo.PixelSize());
|
||||
CopyTextureFromTextureBufferSub(m_vSpriteHelper.data(), w, h, FromImageInfo, x, y, w, h);
|
||||
CImageInfo SpriteInfo;
|
||||
SpriteInfo.m_Width = w;
|
||||
SpriteInfo.m_Height = h;
|
||||
SpriteInfo.m_Format = FromImageInfo.m_Format;
|
||||
SpriteInfo.m_pData = m_vSpriteHelper.data();
|
||||
return LoadTextureRaw(SpriteInfo, 0, pName);
|
||||
}
|
||||
|
||||
IGraphics::CTextureHandle CGraphics_Threaded::LoadSpriteTexture(const CImageInfo &FromImageInfo, const CDataSprite *pSprite)
|
||||
{
|
||||
int ImageGridX = FromImageInfo.m_Width / pSprite->m_pSet->m_Gridx;
|
||||
|
@ -356,12 +308,19 @@ IGraphics::CTextureHandle CGraphics_Threaded::LoadSpriteTexture(const CImageInfo
|
|||
int y = pSprite->m_Y * ImageGridY;
|
||||
int w = pSprite->m_W * ImageGridX;
|
||||
int h = pSprite->m_H * ImageGridY;
|
||||
return LoadSpriteTextureImpl(FromImageInfo, x, y, w, h, pSprite->m_pName);
|
||||
|
||||
CImageInfo SpriteInfo;
|
||||
SpriteInfo.m_Width = w;
|
||||
SpriteInfo.m_Height = h;
|
||||
SpriteInfo.m_Format = FromImageInfo.m_Format;
|
||||
SpriteInfo.m_pData = static_cast<uint8_t *>(malloc(SpriteInfo.DataSize()));
|
||||
SpriteInfo.CopyRectFrom(FromImageInfo, x, y, w, h, 0, 0);
|
||||
return LoadTextureRawMove(SpriteInfo, 0, pSprite->m_pName);
|
||||
}
|
||||
|
||||
bool CGraphics_Threaded::IsImageSubFullyTransparent(const CImageInfo &FromImageInfo, int x, int y, int w, int h)
|
||||
{
|
||||
if(FromImageInfo.m_Format == CImageInfo::FORMAT_SINGLE_COMPONENT || FromImageInfo.m_Format == CImageInfo::FORMAT_RGBA)
|
||||
if(FromImageInfo.m_Format == CImageInfo::FORMAT_R || FromImageInfo.m_Format == CImageInfo::FORMAT_RA || FromImageInfo.m_Format == CImageInfo::FORMAT_RGBA)
|
||||
{
|
||||
const uint8_t *pImgData = FromImageInfo.m_pData;
|
||||
const size_t PixelSize = FromImageInfo.PixelSize();
|
||||
|
@ -435,8 +394,8 @@ IGraphics::CTextureHandle CGraphics_Threaded::LoadTextureRaw(const CImageInfo &I
|
|||
CCommandBuffer::SCommand_Texture_Create Cmd = LoadTextureCreateCommand(TextureHandle.Id(), Image.m_Width, Image.m_Height, Flags);
|
||||
|
||||
// Copy texture data and convert if necessary
|
||||
uint8_t *pTmpData = static_cast<uint8_t *>(malloc(Image.m_Width * Image.m_Height * CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA)));
|
||||
if(!ConvertToRGBA(pTmpData, Image))
|
||||
uint8_t *pTmpData;
|
||||
if(!ConvertToRgbaAlloc(pTmpData, Image))
|
||||
{
|
||||
dbg_msg("graphics", "converted image '%s' to RGBA, consider making its file format RGBA", pTexName ? pTexName : "(no name)");
|
||||
}
|
||||
|
@ -472,7 +431,6 @@ IGraphics::CTextureHandle CGraphics_Threaded::LoadTextureRawMove(CImageInfo &Ima
|
|||
return TextureHandle;
|
||||
}
|
||||
|
||||
// simple uncompressed RGBA loaders
|
||||
IGraphics::CTextureHandle CGraphics_Threaded::LoadTexture(const char *pFilename, int StorageType, int Flags)
|
||||
{
|
||||
dbg_assert(pFilename[0] != '\0', "Cannot load texture from file with empty filename"); // would cause Valgrind to crash otherwise
|
||||
|
@ -544,81 +502,56 @@ bool CGraphics_Threaded::UpdateTextTexture(CTextureHandle TextureId, int x, int
|
|||
return true;
|
||||
}
|
||||
|
||||
bool CGraphics_Threaded::LoadPng(CImageInfo &Image, const char *pFilename, int StorageType)
|
||||
static SWarning FormatPngliteIncompatibilityWarning(int PngliteIncompatible, const char *pContextName)
|
||||
{
|
||||
char aCompleteFilename[IO_MAX_PATH_LENGTH];
|
||||
IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, StorageType, aCompleteFilename, sizeof(aCompleteFilename));
|
||||
if(File)
|
||||
SWarning Warning;
|
||||
str_format(Warning.m_aWarningMsg, sizeof(Warning.m_aWarningMsg), Localize("\"%s\" is not compatible with pnglite and cannot be loaded by old DDNet versions: "), pContextName);
|
||||
static const int FLAGS[] = {CImageLoader::PNGLITE_COLOR_TYPE, CImageLoader::PNGLITE_BIT_DEPTH, CImageLoader::PNGLITE_INTERLACE_TYPE, CImageLoader::PNGLITE_COMPRESSION_TYPE, CImageLoader::PNGLITE_FILTER_TYPE};
|
||||
static const char *const EXPLANATION[] = {"color type", "bit depth", "interlace type", "compression type", "filter type"};
|
||||
|
||||
bool First = true;
|
||||
for(size_t i = 0; i < std::size(FLAGS); ++i)
|
||||
{
|
||||
io_seek(File, 0, IOSEEK_END);
|
||||
long int FileSize = io_tell(File);
|
||||
if(FileSize <= 0)
|
||||
if((PngliteIncompatible & FLAGS[i]) != 0)
|
||||
{
|
||||
io_close(File);
|
||||
log_error("game/png", "failed to get file size (%ld). filename='%s'", FileSize, pFilename);
|
||||
return false;
|
||||
}
|
||||
io_seek(File, 0, IOSEEK_START);
|
||||
|
||||
TImageByteBuffer ByteBuffer;
|
||||
SImageByteBuffer ImageByteBuffer(&ByteBuffer);
|
||||
|
||||
ByteBuffer.resize(FileSize);
|
||||
io_read(File, &ByteBuffer.front(), FileSize);
|
||||
|
||||
io_close(File);
|
||||
|
||||
uint8_t *pImgBuffer = NULL;
|
||||
EImageFormat ImageFormat;
|
||||
int PngliteIncompatible;
|
||||
if(::LoadPng(ImageByteBuffer, pFilename, PngliteIncompatible, Image.m_Width, Image.m_Height, pImgBuffer, ImageFormat))
|
||||
{
|
||||
if(ImageFormat == IMAGE_FORMAT_RGB)
|
||||
Image.m_Format = CImageInfo::FORMAT_RGB;
|
||||
else if(ImageFormat == IMAGE_FORMAT_RGBA)
|
||||
Image.m_Format = CImageInfo::FORMAT_RGBA;
|
||||
else
|
||||
if(!First)
|
||||
{
|
||||
free(pImgBuffer);
|
||||
log_error("game/png", "image had unsupported image format. filename='%s' format='%d'", pFilename, (int)ImageFormat);
|
||||
return false;
|
||||
str_append(Warning.m_aWarningMsg, ", ");
|
||||
}
|
||||
Image.m_pData = pImgBuffer;
|
||||
|
||||
if(m_WarnPngliteIncompatibleImages && PngliteIncompatible != 0)
|
||||
{
|
||||
SWarning Warning;
|
||||
str_format(Warning.m_aWarningMsg, sizeof(Warning.m_aWarningMsg), Localize("\"%s\" is not compatible with pnglite and cannot be loaded by old DDNet versions: "), pFilename);
|
||||
static const int FLAGS[] = {PNGLITE_COLOR_TYPE, PNGLITE_BIT_DEPTH, PNGLITE_INTERLACE_TYPE, PNGLITE_COMPRESSION_TYPE, PNGLITE_FILTER_TYPE};
|
||||
static const char *const EXPLANATION[] = {"color type", "bit depth", "interlace type", "compression type", "filter type"};
|
||||
|
||||
bool First = true;
|
||||
for(size_t i = 0; i < std::size(FLAGS); ++i)
|
||||
{
|
||||
if((PngliteIncompatible & FLAGS[i]) != 0)
|
||||
{
|
||||
if(!First)
|
||||
{
|
||||
str_append(Warning.m_aWarningMsg, ", ");
|
||||
}
|
||||
str_append(Warning.m_aWarningMsg, EXPLANATION[i]);
|
||||
First = false;
|
||||
}
|
||||
}
|
||||
str_append(Warning.m_aWarningMsg, " unsupported");
|
||||
m_vWarnings.emplace_back(Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log_error("game/png", "failed to load file. filename='%s'", pFilename);
|
||||
return false;
|
||||
str_append(Warning.m_aWarningMsg, EXPLANATION[i]);
|
||||
First = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log_error("game/png", "failed to open file. filename='%s'", pFilename);
|
||||
str_append(Warning.m_aWarningMsg, " unsupported");
|
||||
return Warning;
|
||||
}
|
||||
|
||||
bool CGraphics_Threaded::LoadPng(CImageInfo &Image, const char *pFilename, int StorageType)
|
||||
{
|
||||
IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, StorageType);
|
||||
|
||||
int PngliteIncompatible;
|
||||
if(!CImageLoader::LoadPng(File, pFilename, Image, PngliteIncompatible))
|
||||
return false;
|
||||
|
||||
if(m_WarnPngliteIncompatibleImages && PngliteIncompatible != 0)
|
||||
{
|
||||
m_vWarnings.emplace_back(FormatPngliteIncompatibilityWarning(PngliteIncompatible, pFilename));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGraphics_Threaded::LoadPng(CImageInfo &Image, const uint8_t *pData, size_t DataSize, const char *pContextName)
|
||||
{
|
||||
CByteBufferReader Reader(pData, DataSize);
|
||||
int PngliteIncompatible;
|
||||
if(!CImageLoader::LoadPng(Reader, pContextName, Image, PngliteIncompatible))
|
||||
return false;
|
||||
|
||||
if(m_WarnPngliteIncompatibleImages && PngliteIncompatible != 0)
|
||||
{
|
||||
m_vWarnings.emplace_back(FormatPngliteIncompatibilityWarning(PngliteIncompatible, pContextName));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -655,12 +588,7 @@ bool CGraphics_Threaded::CheckImageDivisibility(const char *pContextName, CImage
|
|||
NewHeight = maximum<int>(HighestBit(Image.m_Height), DivY);
|
||||
NewWidth = (NewHeight / DivY) * DivX;
|
||||
}
|
||||
|
||||
uint8_t *pNewImage = ResizeImage(Image.m_pData, Image.m_Width, Image.m_Height, NewWidth, NewHeight, Image.PixelSize());
|
||||
free(Image.m_pData);
|
||||
Image.m_pData = pNewImage;
|
||||
Image.m_Width = NewWidth;
|
||||
Image.m_Height = NewHeight;
|
||||
ResizeImage(Image, NewWidth, NewHeight);
|
||||
ImageIsValid = true;
|
||||
}
|
||||
|
||||
|
@ -682,29 +610,6 @@ bool CGraphics_Threaded::IsImageFormatRgba(const char *pContextName, const CImag
|
|||
return true;
|
||||
}
|
||||
|
||||
void CGraphics_Threaded::CopyTextureBufferSub(uint8_t *pDestBuffer, const CImageInfo &SourceImage, size_t SubOffsetX, size_t SubOffsetY, size_t SubCopyWidth, size_t SubCopyHeight)
|
||||
{
|
||||
const size_t PixelSize = SourceImage.PixelSize();
|
||||
for(size_t Y = 0; Y < SubCopyHeight; ++Y)
|
||||
{
|
||||
const size_t ImgOffset = ((SubOffsetY + Y) * SourceImage.m_Width * PixelSize) + (SubOffsetX * PixelSize);
|
||||
const size_t CopySize = SubCopyWidth * PixelSize;
|
||||
mem_copy(&pDestBuffer[ImgOffset], &SourceImage.m_pData[ImgOffset], CopySize);
|
||||
}
|
||||
}
|
||||
|
||||
void CGraphics_Threaded::CopyTextureFromTextureBufferSub(uint8_t *pDestBuffer, size_t DestWidth, size_t DestHeight, const CImageInfo &SourceImage, size_t SrcSubOffsetX, size_t SrcSubOffsetY, size_t SrcSubCopyWidth, size_t SrcSubCopyHeight)
|
||||
{
|
||||
const size_t PixelSize = SourceImage.PixelSize();
|
||||
for(size_t Y = 0; Y < SrcSubCopyHeight; ++Y)
|
||||
{
|
||||
const size_t SrcImgOffset = ((SrcSubOffsetY + Y) * SourceImage.m_Width * PixelSize) + (SrcSubOffsetX * PixelSize);
|
||||
const size_t DstImgOffset = (Y * DestWidth * PixelSize);
|
||||
const size_t CopySize = SrcSubCopyWidth * PixelSize;
|
||||
mem_copy(&pDestBuffer[DstImgOffset], &SourceImage.m_pData[SrcImgOffset], CopySize);
|
||||
}
|
||||
}
|
||||
|
||||
void CGraphics_Threaded::KickCommandBuffer()
|
||||
{
|
||||
m_pBackend->RunBuffer(m_pCommandBuffer);
|
||||
|
@ -731,24 +636,14 @@ class CScreenshotSaveJob : public IJob
|
|||
IStorage *m_pStorage;
|
||||
IConsole *m_pConsole;
|
||||
char m_aName[IO_MAX_PATH_LENGTH];
|
||||
int m_Width;
|
||||
int m_Height;
|
||||
uint8_t *m_pData;
|
||||
CImageInfo m_Image;
|
||||
|
||||
void Run() override
|
||||
{
|
||||
char aWholePath[IO_MAX_PATH_LENGTH];
|
||||
char aBuf[64 + IO_MAX_PATH_LENGTH];
|
||||
IOHANDLE File = m_pStorage->OpenFile(m_aName, IOFLAG_WRITE, IStorage::TYPE_SAVE, aWholePath, sizeof(aWholePath));
|
||||
if(File)
|
||||
if(CImageLoader::SavePng(m_pStorage->OpenFile(m_aName, IOFLAG_WRITE, IStorage::TYPE_SAVE, aWholePath, sizeof(aWholePath)), m_aName, m_Image))
|
||||
{
|
||||
TImageByteBuffer ByteBuffer;
|
||||
SImageByteBuffer ImageByteBuffer(&ByteBuffer);
|
||||
|
||||
if(SavePng(IMAGE_FORMAT_RGBA, m_pData, ImageByteBuffer, m_Width, m_Height))
|
||||
io_write(File, &ByteBuffer.front(), ByteBuffer.size());
|
||||
io_close(File);
|
||||
|
||||
str_format(aBuf, sizeof(aBuf), "saved screenshot to '%s'", aWholePath);
|
||||
}
|
||||
else
|
||||
|
@ -759,19 +654,17 @@ class CScreenshotSaveJob : public IJob
|
|||
}
|
||||
|
||||
public:
|
||||
CScreenshotSaveJob(IStorage *pStorage, IConsole *pConsole, const char *pName, int Width, int Height, uint8_t *pData) :
|
||||
CScreenshotSaveJob(IStorage *pStorage, IConsole *pConsole, const char *pName, CImageInfo Image) :
|
||||
m_pStorage(pStorage),
|
||||
m_pConsole(pConsole),
|
||||
m_Width(Width),
|
||||
m_Height(Height),
|
||||
m_pData(pData)
|
||||
m_Image(Image)
|
||||
{
|
||||
str_copy(m_aName, pName);
|
||||
}
|
||||
|
||||
~CScreenshotSaveJob() override
|
||||
{
|
||||
free(m_pData);
|
||||
m_Image.Free();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -795,7 +688,7 @@ void CGraphics_Threaded::ScreenshotDirect(bool *pSwapped)
|
|||
|
||||
if(Image.m_pData)
|
||||
{
|
||||
m_pEngine->AddJob(std::make_shared<CScreenshotSaveJob>(m_pStorage, m_pConsole, m_aScreenshotName, Image.m_Width, Image.m_Height, Image.m_pData));
|
||||
m_pEngine->AddJob(std::make_shared<CScreenshotSaveJob>(m_pStorage, m_pConsole, m_aScreenshotName, Image));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -798,8 +798,6 @@ class CGraphics_Threaded : public IEngineGraphics
|
|||
size_t m_FirstFreeTexture;
|
||||
int m_TextureMemoryUsage;
|
||||
|
||||
std::vector<uint8_t> m_vSpriteHelper;
|
||||
|
||||
bool m_WarnPngliteIncompatibleImages = false;
|
||||
|
||||
std::vector<SWarning> m_vWarnings;
|
||||
|
@ -954,7 +952,6 @@ public:
|
|||
bool UnloadTextTextures(CTextureHandle &TextTexture, CTextureHandle &TextOutlineTexture) override;
|
||||
bool UpdateTextTexture(CTextureHandle TextureId, int x, int y, size_t Width, size_t Height, const uint8_t *pData) override;
|
||||
|
||||
CTextureHandle LoadSpriteTextureImpl(const CImageInfo &FromImageInfo, int x, int y, size_t w, size_t h, const char *pName);
|
||||
CTextureHandle LoadSpriteTexture(const CImageInfo &FromImageInfo, const struct CDataSprite *pSprite) override;
|
||||
|
||||
bool IsImageSubFullyTransparent(const CImageInfo &FromImageInfo, int x, int y, int w, int h) override;
|
||||
|
@ -963,13 +960,11 @@ public:
|
|||
// simple uncompressed RGBA loaders
|
||||
IGraphics::CTextureHandle LoadTexture(const char *pFilename, int StorageType, int Flags = 0) override;
|
||||
bool LoadPng(CImageInfo &Image, const char *pFilename, int StorageType) override;
|
||||
bool LoadPng(CImageInfo &Image, const uint8_t *pData, size_t DataSize, const char *pContextName) override;
|
||||
|
||||
bool CheckImageDivisibility(const char *pContextName, CImageInfo &Image, int DivX, int DivY, bool AllowResize) override;
|
||||
bool IsImageFormatRgba(const char *pContextName, const CImageInfo &Image) override;
|
||||
|
||||
void CopyTextureBufferSub(uint8_t *pDestBuffer, const CImageInfo &SourceImage, size_t SubOffsetX, size_t SubOffsetY, size_t SubCopyWidth, size_t SubCopyHeight) override;
|
||||
void CopyTextureFromTextureBufferSub(uint8_t *pDestBuffer, size_t DestWidth, size_t DestHeight, const CImageInfo &SourceImage, size_t SrcSubOffsetX, size_t SrcSubOffsetY, size_t SrcSubCopyWidth, size_t SrcSubCopyHeight) override;
|
||||
|
||||
void TextureSet(CTextureHandle TextureId) override;
|
||||
|
||||
void Clear(float r, float g, float b, bool ForceClearNow = false) override;
|
||||
|
|
|
@ -80,6 +80,7 @@ void CInput::Init()
|
|||
|
||||
m_pGraphics = Kernel()->RequestInterface<IEngineGraphics>();
|
||||
m_pConsole = Kernel()->RequestInterface<IConsole>();
|
||||
m_pConfigManager = Kernel()->RequestInterface<IConfigManager>();
|
||||
|
||||
MouseModeRelative();
|
||||
|
||||
|
@ -824,6 +825,9 @@ int CInput::Update()
|
|||
}
|
||||
break;
|
||||
case SDL_WINDOWEVENT_MINIMIZED:
|
||||
#if defined(CONF_PLATFORM_ANDROID) // Save the config when minimized on Android.
|
||||
m_pConfigManager->Save();
|
||||
#endif
|
||||
Graphics()->WindowDestroyNtf(Event.window.windowID);
|
||||
break;
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <vector>
|
||||
|
||||
class IEngineGraphics;
|
||||
class IConfigManager;
|
||||
|
||||
class CInput : public IEngineInput
|
||||
{
|
||||
|
@ -59,6 +60,7 @@ public:
|
|||
private:
|
||||
IEngineGraphics *m_pGraphics;
|
||||
IConsole *m_pConsole;
|
||||
IConfigManager *m_pConfigManager;
|
||||
|
||||
IEngineGraphics *Graphics() const { return m_pGraphics; }
|
||||
IConsole *Console() const { return m_pConsole; }
|
||||
|
|
|
@ -424,7 +424,10 @@ void CServerBrowserHttp::Refresh()
|
|||
}
|
||||
bool ServerbrowserParseUrl(NETADDR *pOut, const char *pUrl)
|
||||
{
|
||||
return net_addr_from_url(pOut, pUrl, nullptr, 0) != 0;
|
||||
int Failure = net_addr_from_url(pOut, pUrl, nullptr, 0);
|
||||
if(Failure || pOut->port == 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
bool CServerBrowserHttp::Validate(json_value *pJson)
|
||||
{
|
||||
|
|
120
src/engine/gfx/image.cpp
Normal file
120
src/engine/gfx/image.cpp
Normal file
|
@ -0,0 +1,120 @@
|
|||
#include <base/system.h>
|
||||
|
||||
#include <engine/image.h>
|
||||
|
||||
void CImageInfo::Free()
|
||||
{
|
||||
m_Width = 0;
|
||||
m_Height = 0;
|
||||
m_Format = FORMAT_UNDEFINED;
|
||||
free(m_pData);
|
||||
m_pData = nullptr;
|
||||
}
|
||||
|
||||
size_t CImageInfo::PixelSize(EImageFormat Format)
|
||||
{
|
||||
dbg_assert(Format != FORMAT_UNDEFINED, "Format undefined");
|
||||
static const size_t s_aSizes[] = {3, 4, 1, 2};
|
||||
return s_aSizes[(int)Format];
|
||||
}
|
||||
|
||||
const char *CImageInfo::FormatName(EImageFormat Format)
|
||||
{
|
||||
static const char *s_apNames[] = {"UNDEFINED", "RGBA", "RGB", "R", "RA"};
|
||||
return s_apNames[(int)Format + 1];
|
||||
}
|
||||
|
||||
size_t CImageInfo::PixelSize() const
|
||||
{
|
||||
return PixelSize(m_Format);
|
||||
}
|
||||
|
||||
const char *CImageInfo::FormatName() const
|
||||
{
|
||||
return FormatName(m_Format);
|
||||
}
|
||||
|
||||
size_t CImageInfo::DataSize() const
|
||||
{
|
||||
return m_Width * m_Height * PixelSize(m_Format);
|
||||
}
|
||||
|
||||
bool CImageInfo::DataEquals(const CImageInfo &Other) const
|
||||
{
|
||||
if(m_Width != Other.m_Width || m_Height != Other.m_Height || m_Format != Other.m_Format)
|
||||
return false;
|
||||
if(m_pData == nullptr && Other.m_pData == nullptr)
|
||||
return true;
|
||||
if(m_pData == nullptr || Other.m_pData == nullptr)
|
||||
return false;
|
||||
return mem_comp(m_pData, Other.m_pData, DataSize()) == 0;
|
||||
}
|
||||
|
||||
ColorRGBA CImageInfo::PixelColor(size_t x, size_t y) const
|
||||
{
|
||||
const size_t PixelStartIndex = (x + m_Width * y) * PixelSize();
|
||||
|
||||
ColorRGBA Color;
|
||||
if(m_Format == FORMAT_R)
|
||||
{
|
||||
Color.r = Color.g = Color.b = 1.0f;
|
||||
Color.a = m_pData[PixelStartIndex] / 255.0f;
|
||||
}
|
||||
else if(m_Format == FORMAT_RA)
|
||||
{
|
||||
Color.r = Color.g = Color.b = m_pData[PixelStartIndex] / 255.0f;
|
||||
Color.a = m_pData[PixelStartIndex + 1] / 255.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
Color.r = m_pData[PixelStartIndex + 0] / 255.0f;
|
||||
Color.g = m_pData[PixelStartIndex + 1] / 255.0f;
|
||||
Color.b = m_pData[PixelStartIndex + 2] / 255.0f;
|
||||
if(m_Format == FORMAT_RGBA)
|
||||
{
|
||||
Color.a = m_pData[PixelStartIndex + 3] / 255.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
Color.a = 1.0f;
|
||||
}
|
||||
}
|
||||
return Color;
|
||||
}
|
||||
|
||||
void CImageInfo::SetPixelColor(size_t x, size_t y, ColorRGBA Color) const
|
||||
{
|
||||
const size_t PixelStartIndex = (x + m_Width * y) * PixelSize();
|
||||
|
||||
if(m_Format == FORMAT_R)
|
||||
{
|
||||
m_pData[PixelStartIndex] = round_to_int(Color.a * 255.0f);
|
||||
}
|
||||
else if(m_Format == FORMAT_RA)
|
||||
{
|
||||
m_pData[PixelStartIndex] = round_to_int((Color.r + Color.g + Color.b) / 3.0f * 255.0f);
|
||||
m_pData[PixelStartIndex + 1] = round_to_int(Color.a * 255.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pData[PixelStartIndex + 0] = round_to_int(Color.r * 255.0f);
|
||||
m_pData[PixelStartIndex + 1] = round_to_int(Color.g * 255.0f);
|
||||
m_pData[PixelStartIndex + 2] = round_to_int(Color.b * 255.0f);
|
||||
if(m_Format == FORMAT_RGBA)
|
||||
{
|
||||
m_pData[PixelStartIndex + 3] = round_to_int(Color.a * 255.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CImageInfo::CopyRectFrom(const CImageInfo &SrcImage, size_t SrcX, size_t SrcY, size_t Width, size_t Height, size_t DestX, size_t DestY) const
|
||||
{
|
||||
const size_t PixelSize = SrcImage.PixelSize();
|
||||
const size_t CopySize = Width * PixelSize;
|
||||
for(size_t Y = 0; Y < Height; ++Y)
|
||||
{
|
||||
const size_t SrcOffset = ((SrcY + Y) * SrcImage.m_Width + SrcX) * PixelSize;
|
||||
const size_t DestOffset = ((DestY + Y) * m_Width + DestX) * PixelSize;
|
||||
mem_copy(&m_pData[DestOffset], &SrcImage.m_pData[SrcOffset], CopySize);
|
||||
}
|
||||
}
|
|
@ -1,89 +1,94 @@
|
|||
#include "image_loader.h"
|
||||
|
||||
#include <base/log.h>
|
||||
#include <base/system.h>
|
||||
|
||||
#include <csetjmp>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <png.h>
|
||||
|
||||
struct SLibPNGWarningItem
|
||||
bool CByteBufferReader::Read(void *pData, size_t Size)
|
||||
{
|
||||
SImageByteBuffer *m_pByteLoader;
|
||||
const char *m_pFileName;
|
||||
std::jmp_buf m_Buf;
|
||||
};
|
||||
if(m_Error)
|
||||
return false;
|
||||
|
||||
[[noreturn]] static void LibPNGError(png_structp png_ptr, png_const_charp error_msg)
|
||||
{
|
||||
SLibPNGWarningItem *pUserStruct = (SLibPNGWarningItem *)png_get_error_ptr(png_ptr);
|
||||
pUserStruct->m_pByteLoader->m_Err = -1;
|
||||
dbg_msg("png", "error for file \"%s\": %s", pUserStruct->m_pFileName, error_msg);
|
||||
std::longjmp(pUserStruct->m_Buf, 1);
|
||||
}
|
||||
|
||||
static void LibPNGWarning(png_structp png_ptr, png_const_charp warning_msg)
|
||||
{
|
||||
SLibPNGWarningItem *pUserStruct = (SLibPNGWarningItem *)png_get_error_ptr(png_ptr);
|
||||
dbg_msg("png", "warning for file \"%s\": %s", pUserStruct->m_pFileName, warning_msg);
|
||||
}
|
||||
|
||||
static bool FileMatchesImageType(SImageByteBuffer &ByteLoader)
|
||||
{
|
||||
if(ByteLoader.m_pvLoadedImageBytes->size() >= 8)
|
||||
return png_sig_cmp((png_bytep)ByteLoader.m_pvLoadedImageBytes->data(), 0, 8) == 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ReadDataFromLoadedBytes(png_structp pPNGStruct, png_bytep pOutBytes, png_size_t ByteCountToRead)
|
||||
{
|
||||
png_voidp pIO_Ptr = png_get_io_ptr(pPNGStruct);
|
||||
|
||||
SImageByteBuffer *pByteLoader = (SImageByteBuffer *)pIO_Ptr;
|
||||
|
||||
if(pByteLoader->m_pvLoadedImageBytes->size() >= pByteLoader->m_LoadOffset + (size_t)ByteCountToRead)
|
||||
if(m_ReadOffset + Size <= m_Size)
|
||||
{
|
||||
mem_copy(pOutBytes, &(*pByteLoader->m_pvLoadedImageBytes)[pByteLoader->m_LoadOffset], (size_t)ByteCountToRead);
|
||||
|
||||
pByteLoader->m_LoadOffset += (size_t)ByteCountToRead;
|
||||
mem_copy(pData, &m_pData[m_ReadOffset], Size);
|
||||
m_ReadOffset += Size;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
pByteLoader->m_Err = -1;
|
||||
dbg_msg("png", "could not read bytes, file was too small.");
|
||||
m_Error = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static EImageFormat LibPNGGetImageFormat(int ColorChannelCount)
|
||||
void CByteBufferWriter::Write(const void *pData, size_t Size)
|
||||
{
|
||||
if(!Size)
|
||||
return;
|
||||
|
||||
const size_t WriteOffset = m_vBuffer.size();
|
||||
m_vBuffer.resize(WriteOffset + Size);
|
||||
mem_copy(&m_vBuffer[WriteOffset], pData, Size);
|
||||
}
|
||||
|
||||
class CUserErrorStruct
|
||||
{
|
||||
public:
|
||||
CByteBufferReader *m_pReader;
|
||||
const char *m_pContextName;
|
||||
std::jmp_buf m_JmpBuf;
|
||||
};
|
||||
|
||||
[[noreturn]] static void PngErrorCallback(png_structp png_ptr, png_const_charp error_msg)
|
||||
{
|
||||
CUserErrorStruct *pUserStruct = static_cast<CUserErrorStruct *>(png_get_error_ptr(png_ptr));
|
||||
log_error("png", "error for file \"%s\": %s", pUserStruct->m_pContextName, error_msg);
|
||||
std::longjmp(pUserStruct->m_JmpBuf, 1);
|
||||
}
|
||||
|
||||
static void PngWarningCallback(png_structp png_ptr, png_const_charp warning_msg)
|
||||
{
|
||||
CUserErrorStruct *pUserStruct = static_cast<CUserErrorStruct *>(png_get_error_ptr(png_ptr));
|
||||
log_warn("png", "warning for file \"%s\": %s", pUserStruct->m_pContextName, warning_msg);
|
||||
}
|
||||
|
||||
static void PngReadDataCallback(png_structp pPngStruct, png_bytep pOutBytes, png_size_t ByteCountToRead)
|
||||
{
|
||||
CByteBufferReader *pReader = static_cast<CByteBufferReader *>(png_get_io_ptr(pPngStruct));
|
||||
if(!pReader->Read(pOutBytes, ByteCountToRead))
|
||||
{
|
||||
png_error(pPngStruct, "Could not read all bytes, file was too small");
|
||||
}
|
||||
}
|
||||
|
||||
static CImageInfo::EImageFormat ImageFormatFromChannelCount(int ColorChannelCount)
|
||||
{
|
||||
switch(ColorChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return IMAGE_FORMAT_R;
|
||||
return CImageInfo::FORMAT_R;
|
||||
case 2:
|
||||
return IMAGE_FORMAT_RA;
|
||||
return CImageInfo::FORMAT_RA;
|
||||
case 3:
|
||||
return IMAGE_FORMAT_RGB;
|
||||
return CImageInfo::FORMAT_RGB;
|
||||
case 4:
|
||||
return IMAGE_FORMAT_RGBA;
|
||||
return CImageInfo::FORMAT_RGBA;
|
||||
default:
|
||||
dbg_assert(false, "ColorChannelCount invalid");
|
||||
dbg_break();
|
||||
}
|
||||
}
|
||||
|
||||
static void LibPNGDeleteReadStruct(png_structp pPNGStruct, png_infop pPNGInfo)
|
||||
static int PngliteIncompatibility(png_structp pPngStruct, png_infop pPngInfo)
|
||||
{
|
||||
if(pPNGInfo != nullptr)
|
||||
png_destroy_info_struct(pPNGStruct, &pPNGInfo);
|
||||
png_destroy_read_struct(&pPNGStruct, nullptr, nullptr);
|
||||
}
|
||||
|
||||
static int PngliteIncompatibility(png_structp pPNGStruct, png_infop pPNGInfo)
|
||||
{
|
||||
int ColorType = png_get_color_type(pPNGStruct, pPNGInfo);
|
||||
int BitDepth = png_get_bit_depth(pPNGStruct, pPNGInfo);
|
||||
int InterlaceType = png_get_interlace_type(pPNGStruct, pPNGInfo);
|
||||
int Result = 0;
|
||||
|
||||
const int ColorType = png_get_color_type(pPngStruct, pPngInfo);
|
||||
switch(ColorType)
|
||||
{
|
||||
case PNG_COLOR_TYPE_GRAY:
|
||||
|
@ -93,9 +98,10 @@ static int PngliteIncompatibility(png_structp pPNGStruct, png_infop pPNGInfo)
|
|||
break;
|
||||
default:
|
||||
log_debug("png", "color type %d unsupported by pnglite", ColorType);
|
||||
Result |= PNGLITE_COLOR_TYPE;
|
||||
Result |= CImageLoader::PNGLITE_COLOR_TYPE;
|
||||
}
|
||||
|
||||
const int BitDepth = png_get_bit_depth(pPngStruct, pPngInfo);
|
||||
switch(BitDepth)
|
||||
{
|
||||
case 8:
|
||||
|
@ -103,258 +109,294 @@ static int PngliteIncompatibility(png_structp pPNGStruct, png_infop pPNGInfo)
|
|||
break;
|
||||
default:
|
||||
log_debug("png", "bit depth %d unsupported by pnglite", BitDepth);
|
||||
Result |= PNGLITE_BIT_DEPTH;
|
||||
Result |= CImageLoader::PNGLITE_BIT_DEPTH;
|
||||
}
|
||||
|
||||
const int InterlaceType = png_get_interlace_type(pPngStruct, pPngInfo);
|
||||
if(InterlaceType != PNG_INTERLACE_NONE)
|
||||
{
|
||||
log_debug("png", "interlace type %d unsupported by pnglite", InterlaceType);
|
||||
Result |= PNGLITE_INTERLACE_TYPE;
|
||||
Result |= CImageLoader::PNGLITE_INTERLACE_TYPE;
|
||||
}
|
||||
if(png_get_compression_type(pPNGStruct, pPNGInfo) != PNG_COMPRESSION_TYPE_BASE)
|
||||
|
||||
if(png_get_compression_type(pPngStruct, pPngInfo) != PNG_COMPRESSION_TYPE_BASE)
|
||||
{
|
||||
log_debug("png", "non-default compression type unsupported by pnglite");
|
||||
Result |= PNGLITE_COMPRESSION_TYPE;
|
||||
Result |= CImageLoader::PNGLITE_COMPRESSION_TYPE;
|
||||
}
|
||||
if(png_get_filter_type(pPNGStruct, pPNGInfo) != PNG_FILTER_TYPE_BASE)
|
||||
|
||||
if(png_get_filter_type(pPngStruct, pPngInfo) != PNG_FILTER_TYPE_BASE)
|
||||
{
|
||||
log_debug("png", "non-default filter type unsupported by pnglite");
|
||||
Result |= PNGLITE_FILTER_TYPE;
|
||||
Result |= CImageLoader::PNGLITE_FILTER_TYPE;
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
bool LoadPng(SImageByteBuffer &ByteLoader, const char *pFileName, int &PngliteIncompatible, size_t &Width, size_t &Height, uint8_t *&pImageBuff, EImageFormat &ImageFormat)
|
||||
bool CImageLoader::LoadPng(CByteBufferReader &Reader, const char *pContextName, CImageInfo &Image, int &PngliteIncompatible)
|
||||
{
|
||||
SLibPNGWarningItem UserErrorStruct = {&ByteLoader, pFileName, {}};
|
||||
CUserErrorStruct UserErrorStruct = {&Reader, pContextName, {}};
|
||||
|
||||
png_structp pPNGStruct = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
|
||||
if(pPNGStruct == nullptr)
|
||||
png_structp pPngStruct = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
if(pPngStruct == nullptr)
|
||||
{
|
||||
dbg_msg("png", "libpng internal failure: png_create_read_struct failed.");
|
||||
log_error("png", "libpng internal failure: png_create_read_struct failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
png_infop pPNGInfo = nullptr;
|
||||
png_infop pPngInfo = nullptr;
|
||||
png_bytepp pRowPointers = nullptr;
|
||||
Height = 0; // ensure this is not undefined for the error handler
|
||||
if(setjmp(UserErrorStruct.m_Buf))
|
||||
{
|
||||
int Height = 0; // ensure this is not undefined for the Cleanup function
|
||||
const auto &&Cleanup = [&]() {
|
||||
if(pRowPointers != nullptr)
|
||||
{
|
||||
for(size_t i = 0; i < Height; ++i)
|
||||
for(int y = 0; y < Height; ++y)
|
||||
{
|
||||
delete[] pRowPointers[i];
|
||||
delete[] pRowPointers[y];
|
||||
}
|
||||
}
|
||||
delete[] pRowPointers;
|
||||
LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
|
||||
return false;
|
||||
}
|
||||
png_set_error_fn(pPNGStruct, &UserErrorStruct, LibPNGError, LibPNGWarning);
|
||||
|
||||
pPNGInfo = png_create_info_struct(pPNGStruct);
|
||||
|
||||
if(pPNGInfo == nullptr)
|
||||
if(pPngInfo != nullptr)
|
||||
{
|
||||
png_destroy_info_struct(pPngStruct, &pPngInfo);
|
||||
}
|
||||
png_destroy_read_struct(&pPngStruct, nullptr, nullptr);
|
||||
};
|
||||
if(setjmp(UserErrorStruct.m_JmpBuf))
|
||||
{
|
||||
png_destroy_read_struct(&pPNGStruct, nullptr, nullptr);
|
||||
dbg_msg("png", "libpng internal failure: png_create_info_struct failed.");
|
||||
Cleanup();
|
||||
return false;
|
||||
}
|
||||
png_set_error_fn(pPngStruct, &UserErrorStruct, PngErrorCallback, PngWarningCallback);
|
||||
|
||||
if(!FileMatchesImageType(ByteLoader))
|
||||
pPngInfo = png_create_info_struct(pPngStruct);
|
||||
if(pPngInfo == nullptr)
|
||||
{
|
||||
LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
|
||||
dbg_msg("png", "file does not match image type.");
|
||||
Cleanup();
|
||||
log_error("png", "libpng internal failure: png_create_info_struct failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ByteLoader.m_LoadOffset = 8;
|
||||
|
||||
png_set_read_fn(pPNGStruct, (png_bytep)&ByteLoader, ReadDataFromLoadedBytes);
|
||||
|
||||
png_set_sig_bytes(pPNGStruct, 8);
|
||||
|
||||
png_read_info(pPNGStruct, pPNGInfo);
|
||||
|
||||
if(ByteLoader.m_Err != 0)
|
||||
png_byte aSignature[8];
|
||||
if(!Reader.Read(aSignature, sizeof(aSignature)) || png_sig_cmp(aSignature, 0, sizeof(aSignature)) != 0)
|
||||
{
|
||||
LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
|
||||
dbg_msg("png", "byte loader error.");
|
||||
Cleanup();
|
||||
log_error("png", "file is not a valid PNG file (signature mismatch).");
|
||||
return false;
|
||||
}
|
||||
|
||||
Width = png_get_image_width(pPNGStruct, pPNGInfo);
|
||||
Height = png_get_image_height(pPNGStruct, pPNGInfo);
|
||||
const int ColorType = png_get_color_type(pPNGStruct, pPNGInfo);
|
||||
const png_byte BitDepth = png_get_bit_depth(pPNGStruct, pPNGInfo);
|
||||
PngliteIncompatible = PngliteIncompatibility(pPNGStruct, pPNGInfo);
|
||||
png_set_read_fn(pPngStruct, (png_bytep)&Reader, PngReadDataCallback);
|
||||
png_set_sig_bytes(pPngStruct, sizeof(aSignature));
|
||||
|
||||
png_read_info(pPngStruct, pPngInfo);
|
||||
|
||||
if(Reader.Error())
|
||||
{
|
||||
// error already logged
|
||||
Cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
const int Width = png_get_image_width(pPngStruct, pPngInfo);
|
||||
Height = png_get_image_height(pPngStruct, pPngInfo);
|
||||
const png_byte BitDepth = png_get_bit_depth(pPngStruct, pPngInfo);
|
||||
const int ColorType = png_get_color_type(pPngStruct, pPngInfo);
|
||||
|
||||
if(Width == 0 || Height == 0)
|
||||
{
|
||||
log_error("png", "image has width (%d) or height (%d) of 0.", Width, Height);
|
||||
Cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(BitDepth == 16)
|
||||
{
|
||||
png_set_strip_16(pPNGStruct);
|
||||
png_set_strip_16(pPngStruct);
|
||||
}
|
||||
else if(BitDepth > 8)
|
||||
else if(BitDepth > 8 || BitDepth == 0)
|
||||
{
|
||||
dbg_msg("png", "non supported bit depth.");
|
||||
LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(Width == 0 || Height == 0 || BitDepth == 0)
|
||||
{
|
||||
dbg_msg("png", "image had width, height or bit depth of 0.");
|
||||
LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
|
||||
log_error("png", "bit depth %d not supported.", BitDepth);
|
||||
Cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(ColorType == PNG_COLOR_TYPE_PALETTE)
|
||||
png_set_palette_to_rgb(pPNGStruct);
|
||||
{
|
||||
png_set_palette_to_rgb(pPngStruct);
|
||||
}
|
||||
|
||||
if(ColorType == PNG_COLOR_TYPE_GRAY && BitDepth < 8)
|
||||
png_set_expand_gray_1_2_4_to_8(pPNGStruct);
|
||||
{
|
||||
png_set_expand_gray_1_2_4_to_8(pPngStruct);
|
||||
}
|
||||
|
||||
if(png_get_valid(pPNGStruct, pPNGInfo, PNG_INFO_tRNS))
|
||||
png_set_tRNS_to_alpha(pPNGStruct);
|
||||
if(png_get_valid(pPngStruct, pPngInfo, PNG_INFO_tRNS))
|
||||
{
|
||||
png_set_tRNS_to_alpha(pPngStruct);
|
||||
}
|
||||
|
||||
png_read_update_info(pPNGStruct, pPNGInfo);
|
||||
png_read_update_info(pPngStruct, pPngInfo);
|
||||
|
||||
const size_t ColorChannelCount = png_get_channels(pPNGStruct, pPNGInfo);
|
||||
const size_t BytesInRow = png_get_rowbytes(pPNGStruct, pPNGInfo);
|
||||
const int ColorChannelCount = png_get_channels(pPngStruct, pPngInfo);
|
||||
const int BytesInRow = png_get_rowbytes(pPngStruct, pPngInfo);
|
||||
dbg_assert(BytesInRow == Width * ColorChannelCount, "bytes in row incorrect.");
|
||||
|
||||
pRowPointers = new png_bytep[Height];
|
||||
for(size_t y = 0; y < Height; ++y)
|
||||
for(int y = 0; y < Height; ++y)
|
||||
{
|
||||
pRowPointers[y] = new png_byte[BytesInRow];
|
||||
}
|
||||
|
||||
png_read_image(pPNGStruct, pRowPointers);
|
||||
png_read_image(pPngStruct, pRowPointers);
|
||||
|
||||
if(ByteLoader.m_Err == 0)
|
||||
pImageBuff = (uint8_t *)malloc(Height * Width * ColorChannelCount * sizeof(uint8_t));
|
||||
|
||||
for(size_t i = 0; i < Height; ++i)
|
||||
if(!Reader.Error())
|
||||
{
|
||||
if(ByteLoader.m_Err == 0)
|
||||
mem_copy(&pImageBuff[i * BytesInRow], pRowPointers[i], BytesInRow);
|
||||
delete[] pRowPointers[i];
|
||||
Image.m_Width = Width;
|
||||
Image.m_Height = Height;
|
||||
Image.m_Format = ImageFormatFromChannelCount(ColorChannelCount);
|
||||
Image.m_pData = static_cast<uint8_t *>(malloc(Image.DataSize()));
|
||||
for(int y = 0; y < Height; ++y)
|
||||
{
|
||||
mem_copy(&Image.m_pData[y * BytesInRow], pRowPointers[y], BytesInRow);
|
||||
}
|
||||
PngliteIncompatible = PngliteIncompatibility(pPngStruct, pPngInfo);
|
||||
}
|
||||
delete[] pRowPointers;
|
||||
pRowPointers = nullptr;
|
||||
|
||||
if(ByteLoader.m_Err != 0)
|
||||
Cleanup();
|
||||
|
||||
return !Reader.Error();
|
||||
}
|
||||
|
||||
bool CImageLoader::LoadPng(IOHANDLE File, const char *pFilename, CImageInfo &Image, int &PngliteIncompatible)
|
||||
{
|
||||
if(!File)
|
||||
{
|
||||
LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
|
||||
dbg_msg("png", "byte loader error.");
|
||||
log_error("png", "failed to open file for reading. filename='%s'", pFilename);
|
||||
return false;
|
||||
}
|
||||
|
||||
ImageFormat = LibPNGGetImageFormat(ColorChannelCount);
|
||||
void *pFileData;
|
||||
unsigned FileDataSize;
|
||||
io_read_all(File, &pFileData, &FileDataSize);
|
||||
io_close(File);
|
||||
|
||||
png_destroy_info_struct(pPNGStruct, &pPNGInfo);
|
||||
png_destroy_read_struct(&pPNGStruct, nullptr, nullptr);
|
||||
CByteBufferReader ImageReader(static_cast<const uint8_t *>(pFileData), FileDataSize);
|
||||
|
||||
const bool LoadResult = CImageLoader::LoadPng(ImageReader, pFilename, Image, PngliteIncompatible);
|
||||
free(pFileData);
|
||||
if(!LoadResult)
|
||||
{
|
||||
log_error("png", "failed to load image from file. filename='%s'", pFilename);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(Image.m_Format != CImageInfo::FORMAT_RGB && Image.m_Format != CImageInfo::FORMAT_RGBA)
|
||||
{
|
||||
log_error("png", "image has unsupported format. filename='%s' format='%s'", pFilename, Image.FormatName());
|
||||
Image.Free();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void WriteDataFromLoadedBytes(png_structp pPNGStruct, png_bytep pOutBytes, png_size_t ByteCountToWrite)
|
||||
static void PngWriteDataCallback(png_structp pPngStruct, png_bytep pOutBytes, png_size_t ByteCountToWrite)
|
||||
{
|
||||
if(ByteCountToWrite > 0)
|
||||
{
|
||||
png_voidp pIO_Ptr = png_get_io_ptr(pPNGStruct);
|
||||
|
||||
SImageByteBuffer *pByteLoader = (SImageByteBuffer *)pIO_Ptr;
|
||||
|
||||
size_t NewSize = pByteLoader->m_LoadOffset + (size_t)ByteCountToWrite;
|
||||
pByteLoader->m_pvLoadedImageBytes->resize(NewSize);
|
||||
|
||||
mem_copy(&(*pByteLoader->m_pvLoadedImageBytes)[pByteLoader->m_LoadOffset], pOutBytes, (size_t)ByteCountToWrite);
|
||||
pByteLoader->m_LoadOffset = NewSize;
|
||||
}
|
||||
CByteBufferWriter *pWriter = static_cast<CByteBufferWriter *>(png_get_io_ptr(pPngStruct));
|
||||
pWriter->Write(pOutBytes, ByteCountToWrite);
|
||||
}
|
||||
|
||||
static void FlushPNGWrite(png_structp png_ptr) {}
|
||||
static void PngOutputFlushCallback(png_structp png_ptr)
|
||||
{
|
||||
// no need to flush memory buffer
|
||||
}
|
||||
|
||||
static size_t ImageLoaderHelperFormatToColorChannel(EImageFormat Format)
|
||||
static int PngColorTypeFromFormat(CImageInfo::EImageFormat Format)
|
||||
{
|
||||
switch(Format)
|
||||
{
|
||||
case IMAGE_FORMAT_R:
|
||||
return 1;
|
||||
case IMAGE_FORMAT_RA:
|
||||
return 2;
|
||||
case IMAGE_FORMAT_RGB:
|
||||
return 3;
|
||||
case IMAGE_FORMAT_RGBA:
|
||||
return 4;
|
||||
case CImageInfo::FORMAT_R:
|
||||
return PNG_COLOR_TYPE_GRAY;
|
||||
case CImageInfo::FORMAT_RA:
|
||||
return PNG_COLOR_TYPE_GRAY_ALPHA;
|
||||
case CImageInfo::FORMAT_RGB:
|
||||
return PNG_COLOR_TYPE_RGB;
|
||||
case CImageInfo::FORMAT_RGBA:
|
||||
return PNG_COLOR_TYPE_RGBA;
|
||||
default:
|
||||
dbg_assert(false, "Format invalid");
|
||||
dbg_break();
|
||||
}
|
||||
}
|
||||
|
||||
bool SavePng(EImageFormat ImageFormat, const uint8_t *pRawBuffer, SImageByteBuffer &WrittenBytes, size_t Width, size_t Height)
|
||||
bool CImageLoader::SavePng(CByteBufferWriter &Writer, const CImageInfo &Image)
|
||||
{
|
||||
png_structp pPNGStruct = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
|
||||
if(pPNGStruct == nullptr)
|
||||
png_structp pPngStruct = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
if(pPngStruct == nullptr)
|
||||
{
|
||||
dbg_msg("png", "libpng internal failure: png_create_write_struct failed.");
|
||||
log_error("png", "libpng internal failure: png_create_write_struct failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
png_infop pPNGInfo = png_create_info_struct(pPNGStruct);
|
||||
|
||||
if(pPNGInfo == nullptr)
|
||||
png_infop pPngInfo = png_create_info_struct(pPngStruct);
|
||||
if(pPngInfo == nullptr)
|
||||
{
|
||||
png_destroy_read_struct(&pPNGStruct, nullptr, nullptr);
|
||||
dbg_msg("png", "libpng internal failure: png_create_info_struct failed.");
|
||||
png_destroy_read_struct(&pPngStruct, nullptr, nullptr);
|
||||
log_error("png", "libpng internal failure: png_create_info_struct failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
WrittenBytes.m_LoadOffset = 0;
|
||||
WrittenBytes.m_pvLoadedImageBytes->clear();
|
||||
png_set_write_fn(pPngStruct, (png_bytep)&Writer, PngWriteDataCallback, PngOutputFlushCallback);
|
||||
|
||||
png_set_write_fn(pPNGStruct, (png_bytep)&WrittenBytes, WriteDataFromLoadedBytes, FlushPNGWrite);
|
||||
png_set_IHDR(pPngStruct, pPngInfo, Image.m_Width, Image.m_Height, 8, PngColorTypeFromFormat(Image.m_Format), PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
|
||||
png_write_info(pPngStruct, pPngInfo);
|
||||
|
||||
int ColorType = PNG_COLOR_TYPE_RGB;
|
||||
size_t WriteBytesPerPixel = ImageLoaderHelperFormatToColorChannel(ImageFormat);
|
||||
if(ImageFormat == IMAGE_FORMAT_R)
|
||||
{
|
||||
ColorType = PNG_COLOR_TYPE_GRAY;
|
||||
}
|
||||
else if(ImageFormat == IMAGE_FORMAT_RGBA)
|
||||
{
|
||||
ColorType = PNG_COLOR_TYPE_RGBA;
|
||||
}
|
||||
|
||||
png_set_IHDR(pPNGStruct, pPNGInfo, Width, Height, 8, ColorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
|
||||
|
||||
png_write_info(pPNGStruct, pPNGInfo);
|
||||
|
||||
png_bytepp pRowPointers = new png_bytep[Height];
|
||||
size_t WidthBytes = Width * WriteBytesPerPixel;
|
||||
png_bytepp pRowPointers = new png_bytep[Image.m_Height];
|
||||
const int WidthBytes = Image.m_Width * Image.PixelSize();
|
||||
ptrdiff_t BufferOffset = 0;
|
||||
for(size_t y = 0; y < Height; ++y)
|
||||
for(size_t y = 0; y < Image.m_Height; ++y)
|
||||
{
|
||||
pRowPointers[y] = new png_byte[WidthBytes];
|
||||
mem_copy(pRowPointers[y], pRawBuffer + BufferOffset, WidthBytes);
|
||||
mem_copy(pRowPointers[y], Image.m_pData + BufferOffset, WidthBytes);
|
||||
BufferOffset += (ptrdiff_t)WidthBytes;
|
||||
}
|
||||
png_write_image(pPNGStruct, pRowPointers);
|
||||
png_write_image(pPngStruct, pRowPointers);
|
||||
png_write_end(pPngStruct, pPngInfo);
|
||||
|
||||
png_write_end(pPNGStruct, pPNGInfo);
|
||||
|
||||
for(size_t y = 0; y < Height; ++y)
|
||||
for(size_t y = 0; y < Image.m_Height; ++y)
|
||||
{
|
||||
delete[](pRowPointers[y]);
|
||||
delete[] pRowPointers[y];
|
||||
}
|
||||
delete[](pRowPointers);
|
||||
delete[] pRowPointers;
|
||||
|
||||
png_destroy_info_struct(pPNGStruct, &pPNGInfo);
|
||||
png_destroy_write_struct(&pPNGStruct, nullptr);
|
||||
png_destroy_info_struct(pPngStruct, &pPngInfo);
|
||||
png_destroy_write_struct(&pPngStruct, nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CImageLoader::SavePng(IOHANDLE File, const char *pFilename, const CImageInfo &Image)
|
||||
{
|
||||
if(!File)
|
||||
{
|
||||
log_error("png", "failed to open file for writing. filename='%s'", pFilename);
|
||||
return false;
|
||||
}
|
||||
|
||||
CByteBufferWriter Writer;
|
||||
if(!CImageLoader::SavePng(Writer, Image))
|
||||
{
|
||||
// error already logged
|
||||
io_close(File);
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool WriteSuccess = io_write(File, Writer.Data(), Writer.Size()) == Writer.Size();
|
||||
if(!WriteSuccess)
|
||||
{
|
||||
log_error("png", "failed to write PNG data to file. filename='%s'", pFilename);
|
||||
}
|
||||
io_close(File);
|
||||
return WriteSuccess;
|
||||
}
|
||||
|
|
|
@ -1,38 +1,57 @@
|
|||
#ifndef ENGINE_GFX_IMAGE_LOADER_H
|
||||
#define ENGINE_GFX_IMAGE_LOADER_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <base/types.h>
|
||||
|
||||
#include <engine/image.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
enum EImageFormat
|
||||
class CByteBufferReader
|
||||
{
|
||||
IMAGE_FORMAT_R = 0,
|
||||
IMAGE_FORMAT_RA,
|
||||
IMAGE_FORMAT_RGB,
|
||||
IMAGE_FORMAT_RGBA,
|
||||
const uint8_t *m_pData;
|
||||
size_t m_Size;
|
||||
size_t m_ReadOffset = 0;
|
||||
bool m_Error = false;
|
||||
|
||||
public:
|
||||
CByteBufferReader(const uint8_t *pData, size_t Size) :
|
||||
m_pData(pData),
|
||||
m_Size(Size) {}
|
||||
|
||||
bool Read(void *pData, size_t Size);
|
||||
bool Error() const { return m_Error; }
|
||||
};
|
||||
|
||||
typedef std::vector<uint8_t> TImageByteBuffer;
|
||||
struct SImageByteBuffer
|
||||
class CByteBufferWriter
|
||||
{
|
||||
SImageByteBuffer(std::vector<uint8_t> *pvBuff) :
|
||||
m_LoadOffset(0), m_pvLoadedImageBytes(pvBuff), m_Err(0) {}
|
||||
size_t m_LoadOffset;
|
||||
std::vector<uint8_t> *m_pvLoadedImageBytes;
|
||||
int m_Err;
|
||||
std::vector<uint8_t> m_vBuffer;
|
||||
|
||||
public:
|
||||
void Write(const void *pData, size_t Size);
|
||||
const uint8_t *Data() const { return m_vBuffer.data(); }
|
||||
size_t Size() const { return m_vBuffer.size(); }
|
||||
};
|
||||
|
||||
enum
|
||||
class CImageLoader
|
||||
{
|
||||
PNGLITE_COLOR_TYPE = 1 << 0,
|
||||
PNGLITE_BIT_DEPTH = 1 << 1,
|
||||
PNGLITE_INTERLACE_TYPE = 1 << 2,
|
||||
PNGLITE_COMPRESSION_TYPE = 1 << 3,
|
||||
PNGLITE_FILTER_TYPE = 1 << 4,
|
||||
};
|
||||
CImageLoader() = delete;
|
||||
|
||||
bool LoadPng(SImageByteBuffer &ByteLoader, const char *pFileName, int &PngliteIncompatible, size_t &Width, size_t &Height, uint8_t *&pImageBuff, EImageFormat &ImageFormat);
|
||||
bool SavePng(EImageFormat ImageFormat, const uint8_t *pRawBuffer, SImageByteBuffer &WrittenBytes, size_t Width, size_t Height);
|
||||
public:
|
||||
enum
|
||||
{
|
||||
PNGLITE_COLOR_TYPE = 1 << 0,
|
||||
PNGLITE_BIT_DEPTH = 1 << 1,
|
||||
PNGLITE_INTERLACE_TYPE = 1 << 2,
|
||||
PNGLITE_COMPRESSION_TYPE = 1 << 3,
|
||||
PNGLITE_FILTER_TYPE = 1 << 4,
|
||||
};
|
||||
|
||||
static bool LoadPng(CByteBufferReader &Reader, const char *pContextName, CImageInfo &Image, int &PngliteIncompatible);
|
||||
static bool LoadPng(IOHANDLE File, const char *pFilename, CImageInfo &Image, int &PngliteIncompatible);
|
||||
|
||||
static bool SavePng(CByteBufferWriter &Writer, const CImageInfo &Image);
|
||||
static bool SavePng(IOHANDLE File, const char *pFilename, const CImageInfo &Image);
|
||||
};
|
||||
|
||||
#endif // ENGINE_GFX_IMAGE_LOADER_H
|
||||
|
|
|
@ -1,7 +1,88 @@
|
|||
#include "image_manipulation.h"
|
||||
|
||||
#include <base/math.h>
|
||||
#include <base/system.h>
|
||||
|
||||
bool ConvertToRgba(uint8_t *pDest, const CImageInfo &SourceImage)
|
||||
{
|
||||
if(SourceImage.m_Format == CImageInfo::FORMAT_RGBA)
|
||||
{
|
||||
mem_copy(pDest, SourceImage.m_pData, SourceImage.DataSize());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
const size_t SrcChannelCount = CImageInfo::PixelSize(SourceImage.m_Format);
|
||||
const size_t DstChannelCount = CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA);
|
||||
for(size_t Y = 0; Y < SourceImage.m_Height; ++Y)
|
||||
{
|
||||
for(size_t X = 0; X < SourceImage.m_Width; ++X)
|
||||
{
|
||||
size_t ImgOffsetSrc = (Y * SourceImage.m_Width * SrcChannelCount) + (X * SrcChannelCount);
|
||||
size_t ImgOffsetDest = (Y * SourceImage.m_Width * DstChannelCount) + (X * DstChannelCount);
|
||||
if(SourceImage.m_Format == CImageInfo::FORMAT_RGB)
|
||||
{
|
||||
mem_copy(&pDest[ImgOffsetDest], &SourceImage.m_pData[ImgOffsetSrc], SrcChannelCount);
|
||||
pDest[ImgOffsetDest + 3] = 255;
|
||||
}
|
||||
else if(SourceImage.m_Format == CImageInfo::FORMAT_RA)
|
||||
{
|
||||
pDest[ImgOffsetDest + 0] = SourceImage.m_pData[ImgOffsetSrc];
|
||||
pDest[ImgOffsetDest + 1] = SourceImage.m_pData[ImgOffsetSrc];
|
||||
pDest[ImgOffsetDest + 2] = SourceImage.m_pData[ImgOffsetSrc];
|
||||
pDest[ImgOffsetDest + 3] = SourceImage.m_pData[ImgOffsetSrc + 1];
|
||||
}
|
||||
else if(SourceImage.m_Format == CImageInfo::FORMAT_R)
|
||||
{
|
||||
pDest[ImgOffsetDest + 0] = 255;
|
||||
pDest[ImgOffsetDest + 1] = 255;
|
||||
pDest[ImgOffsetDest + 2] = 255;
|
||||
pDest[ImgOffsetDest + 3] = SourceImage.m_pData[ImgOffsetSrc];
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_assert(false, "SourceImage.m_Format invalid");
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ConvertToRgbaAlloc(uint8_t *&pDest, const CImageInfo &SourceImage)
|
||||
{
|
||||
pDest = static_cast<uint8_t *>(malloc(SourceImage.m_Width * SourceImage.m_Height * CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA)));
|
||||
return ConvertToRgba(pDest, SourceImage);
|
||||
}
|
||||
|
||||
bool ConvertToRgba(CImageInfo &Image)
|
||||
{
|
||||
if(Image.m_Format == CImageInfo::FORMAT_RGBA)
|
||||
return true;
|
||||
|
||||
uint8_t *pRgbaData;
|
||||
ConvertToRgbaAlloc(pRgbaData, Image);
|
||||
free(Image.m_pData);
|
||||
Image.m_pData = pRgbaData;
|
||||
Image.m_Format = CImageInfo::FORMAT_RGBA;
|
||||
return false;
|
||||
}
|
||||
|
||||
void ConvertToGrayscale(const CImageInfo &Image)
|
||||
{
|
||||
if(Image.m_Format == CImageInfo::FORMAT_R || Image.m_Format == CImageInfo::FORMAT_RA)
|
||||
return;
|
||||
|
||||
const size_t Step = Image.PixelSize();
|
||||
for(size_t i = 0; i < Image.m_Width * Image.m_Height; ++i)
|
||||
{
|
||||
const int Average = (Image.m_pData[i * Step] + Image.m_pData[i * Step + 1] + Image.m_pData[i * Step + 2]) / 3;
|
||||
Image.m_pData[i * Step] = Average;
|
||||
Image.m_pData[i * Step + 1] = Average;
|
||||
Image.m_pData[i * Step + 2] = Average;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr int DILATE_BPP = 4; // RGBA assumed
|
||||
static constexpr uint8_t DILATE_ALPHA_THRESHOLD = 10;
|
||||
|
||||
|
@ -70,6 +151,12 @@ void DilateImage(uint8_t *pImageBuff, int w, int h)
|
|||
DilateImageSub(pImageBuff, w, h, 0, 0, w, h);
|
||||
}
|
||||
|
||||
void DilateImage(const CImageInfo &Image)
|
||||
{
|
||||
dbg_assert(Image.m_Format == CImageInfo::FORMAT_RGBA, "Dilate requires RGBA format");
|
||||
DilateImage(Image.m_pData, Image.m_Width, Image.m_Height);
|
||||
}
|
||||
|
||||
void DilateImageSub(uint8_t *pImageBuff, int w, int h, int x, int y, int sw, int sh)
|
||||
{
|
||||
uint8_t *apBuffer[2] = {nullptr, nullptr};
|
||||
|
@ -181,6 +268,15 @@ uint8_t *ResizeImage(const uint8_t *pImageData, int Width, int Height, int NewWi
|
|||
return pTmpData;
|
||||
}
|
||||
|
||||
void ResizeImage(CImageInfo &Image, int NewWidth, int NewHeight)
|
||||
{
|
||||
uint8_t *pNewData = ResizeImage(Image.m_pData, Image.m_Width, Image.m_Height, NewWidth, NewHeight, Image.PixelSize());
|
||||
free(Image.m_pData);
|
||||
Image.m_pData = pNewData;
|
||||
Image.m_Width = NewWidth;
|
||||
Image.m_Height = NewHeight;
|
||||
}
|
||||
|
||||
int HighestBit(int OfVar)
|
||||
{
|
||||
if(!OfVar)
|
||||
|
|
|
@ -1,14 +1,29 @@
|
|||
#ifndef ENGINE_GFX_IMAGE_MANIPULATION_H
|
||||
#define ENGINE_GFX_IMAGE_MANIPULATION_H
|
||||
|
||||
#include <engine/image.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// Destination must have appropriate size for RGBA data
|
||||
bool ConvertToRgba(uint8_t *pDest, const CImageInfo &SourceImage);
|
||||
// Allocates appropriate buffer with malloc, must be freed by caller
|
||||
bool ConvertToRgbaAlloc(uint8_t *&pDest, const CImageInfo &SourceImage);
|
||||
// Replaces existing image data with RGBA data (unless already RGBA)
|
||||
bool ConvertToRgba(CImageInfo &Image);
|
||||
|
||||
// Changes the image data (not the format)
|
||||
void ConvertToGrayscale(const CImageInfo &Image);
|
||||
|
||||
// These functions assume that the image data is 4 bytes per pixel RGBA
|
||||
void DilateImage(uint8_t *pImageBuff, int w, int h);
|
||||
void DilateImage(const CImageInfo &Image);
|
||||
void DilateImageSub(uint8_t *pImageBuff, int w, int h, int x, int y, int sw, int sh);
|
||||
|
||||
// returned pointer is allocated with malloc
|
||||
// Returned buffer is allocated with malloc, must be freed by caller
|
||||
uint8_t *ResizeImage(const uint8_t *pImageData, int Width, int Height, int NewWidth, int NewHeight, int BPP);
|
||||
// Replaces existing image data with resized buffer
|
||||
void ResizeImage(CImageInfo &Image, int NewWidth, int NewHeight);
|
||||
|
||||
int HighestBit(int OfVar);
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#ifndef ENGINE_GRAPHICS_H
|
||||
#define ENGINE_GRAPHICS_H
|
||||
|
||||
#include "image.h"
|
||||
#include "kernel.h"
|
||||
#include "warning.h"
|
||||
|
||||
|
@ -64,68 +65,6 @@ struct SGraphicTileTexureCoords
|
|||
ubvec4 m_TexCoordBottomLeft;
|
||||
};
|
||||
|
||||
class CImageInfo
|
||||
{
|
||||
public:
|
||||
enum EImageFormat
|
||||
{
|
||||
FORMAT_ERROR = -1,
|
||||
FORMAT_RGB = 0,
|
||||
FORMAT_RGBA = 1,
|
||||
FORMAT_SINGLE_COMPONENT = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* Contains the width of the image
|
||||
*/
|
||||
size_t m_Width = 0;
|
||||
|
||||
/**
|
||||
* Contains the height of the image
|
||||
*/
|
||||
size_t m_Height = 0;
|
||||
|
||||
/**
|
||||
* Contains the format of the image.
|
||||
*
|
||||
* @see EImageFormat
|
||||
*/
|
||||
EImageFormat m_Format = FORMAT_ERROR;
|
||||
|
||||
/**
|
||||
* Pointer to the image data.
|
||||
*/
|
||||
uint8_t *m_pData = nullptr;
|
||||
|
||||
void Free()
|
||||
{
|
||||
m_Width = 0;
|
||||
m_Height = 0;
|
||||
m_Format = FORMAT_ERROR;
|
||||
free(m_pData);
|
||||
m_pData = nullptr;
|
||||
}
|
||||
|
||||
static size_t PixelSize(EImageFormat Format)
|
||||
{
|
||||
dbg_assert(Format != FORMAT_ERROR, "Format invalid");
|
||||
static const size_t s_aSizes[] = {3, 4, 1};
|
||||
return s_aSizes[(int)Format];
|
||||
}
|
||||
|
||||
size_t PixelSize() const
|
||||
{
|
||||
return PixelSize(m_Format);
|
||||
}
|
||||
|
||||
size_t DataSize() const
|
||||
{
|
||||
return m_Width * m_Height * PixelSize(m_Format);
|
||||
}
|
||||
};
|
||||
|
||||
bool ConvertToRGBA(uint8_t *pDest, const CImageInfo &SrcImage);
|
||||
|
||||
/*
|
||||
Structure: CVideoMode
|
||||
*/
|
||||
|
@ -330,16 +269,11 @@ public:
|
|||
virtual const TTwGraphicsGpuList &GetGpus() const = 0;
|
||||
|
||||
virtual bool LoadPng(CImageInfo &Image, const char *pFilename, int StorageType) = 0;
|
||||
virtual bool LoadPng(CImageInfo &Image, const uint8_t *pData, size_t DataSize, const char *pContextName) = 0;
|
||||
|
||||
virtual bool CheckImageDivisibility(const char *pContextName, CImageInfo &Image, int DivX, int DivY, bool AllowResize) = 0;
|
||||
virtual bool IsImageFormatRgba(const char *pContextName, const CImageInfo &Image) = 0;
|
||||
|
||||
// destination and source buffer require to have the same width and height
|
||||
virtual void CopyTextureBufferSub(uint8_t *pDestBuffer, const CImageInfo &SourceImage, size_t SubOffsetX, size_t SubOffsetY, size_t SubCopyWidth, size_t SubCopyHeight) = 0;
|
||||
|
||||
// destination width must be equal to the subwidth of the source
|
||||
virtual void CopyTextureFromTextureBufferSub(uint8_t *pDestBuffer, size_t DestWidth, size_t DestHeight, const CImageInfo &SourceImage, size_t SrcSubOffsetX, size_t SrcSubOffsetY, size_t SrcSubCopyWidth, size_t SrcSubCopyHeight) = 0;
|
||||
|
||||
virtual void UnloadTexture(CTextureHandle *pIndex) = 0;
|
||||
virtual CTextureHandle LoadTextureRaw(const CImageInfo &Image, int Flags, const char *pTexName = nullptr) = 0;
|
||||
virtual CTextureHandle LoadTextureRawMove(CImageInfo &Image, int Flags, const char *pTexName = nullptr) = 0;
|
||||
|
|
137
src/engine/image.h
Normal file
137
src/engine/image.h
Normal file
|
@ -0,0 +1,137 @@
|
|||
#ifndef ENGINE_IMAGE_H
|
||||
#define ENGINE_IMAGE_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <base/color.h>
|
||||
|
||||
/**
|
||||
* Represents an image that has been loaded into main memory.
|
||||
*/
|
||||
class CImageInfo
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Defines the format of image data.
|
||||
*/
|
||||
enum EImageFormat
|
||||
{
|
||||
FORMAT_UNDEFINED = -1,
|
||||
FORMAT_RGB = 0,
|
||||
FORMAT_RGBA = 1,
|
||||
FORMAT_R = 2,
|
||||
FORMAT_RA = 3,
|
||||
};
|
||||
|
||||
/**
|
||||
* Width of the image.
|
||||
*/
|
||||
size_t m_Width = 0;
|
||||
|
||||
/**
|
||||
* Height of the image.
|
||||
*/
|
||||
size_t m_Height = 0;
|
||||
|
||||
/**
|
||||
* Format of the image.
|
||||
*
|
||||
* @see EImageFormat
|
||||
*/
|
||||
EImageFormat m_Format = FORMAT_UNDEFINED;
|
||||
|
||||
/**
|
||||
* Pointer to the image data.
|
||||
*/
|
||||
uint8_t *m_pData = nullptr;
|
||||
|
||||
/**
|
||||
* Frees the image data and clears all info.
|
||||
*/
|
||||
void Free();
|
||||
|
||||
/**
|
||||
* Returns the pixel size in bytes for the given image format.
|
||||
*
|
||||
* @param Format Image format, must not be `FORMAT_UNDEFINED`.
|
||||
*
|
||||
* @return Size of one pixel with the given image format.
|
||||
*/
|
||||
static size_t PixelSize(EImageFormat Format);
|
||||
|
||||
/**
|
||||
* Returns a readable name for the given image format.
|
||||
*
|
||||
* @param Format Image format.
|
||||
*
|
||||
* @return Readable name for the given image format.
|
||||
*/
|
||||
static const char *FormatName(EImageFormat Format);
|
||||
|
||||
/**
|
||||
* Returns the pixel size in bytes for the format of this image.
|
||||
*
|
||||
* @return Size of one pixel with the format of this image.
|
||||
*
|
||||
* @remark The format must not be `FORMAT_UNDEFINED`.
|
||||
*/
|
||||
size_t PixelSize() const;
|
||||
|
||||
/**
|
||||
* Returns a readable name for the format of this image.
|
||||
*
|
||||
* @return Readable name for the format of this image.
|
||||
*/
|
||||
const char *FormatName() const;
|
||||
|
||||
/**
|
||||
* Returns the size of the data, as derived from the width, height and pixel size.
|
||||
*
|
||||
* @return Expected size of the image data.
|
||||
*/
|
||||
size_t DataSize() const;
|
||||
|
||||
/**
|
||||
* Returns whether this image is equal to the given image
|
||||
* in width, height, format and data.
|
||||
*
|
||||
* @param Other The image to compare with.
|
||||
*
|
||||
* @return `true` if the images are identical, `false` otherwise.
|
||||
*/
|
||||
bool DataEquals(const CImageInfo &Other) const;
|
||||
|
||||
/**
|
||||
* Returns the color of the pixel at the specified position.
|
||||
*
|
||||
* @param x The x-coordinate to read from.
|
||||
* @param y The y-coordinate to read from.
|
||||
*
|
||||
* @return Pixel color converted to normalized RGBA.
|
||||
*/
|
||||
ColorRGBA PixelColor(size_t x, size_t y) const;
|
||||
|
||||
/**
|
||||
* Sets the color of the pixel at the specified position.
|
||||
*
|
||||
* @param x The x-coordinate to write to.
|
||||
* @param y The y-coordinate to write to.
|
||||
* @param Color The normalized RGBA color to write.
|
||||
*/
|
||||
void SetPixelColor(size_t x, size_t y, ColorRGBA Color) const;
|
||||
|
||||
/**
|
||||
* Copies a rectangle of image data from the given image to this image.
|
||||
*
|
||||
* @param SrcImage The image to copy data from.
|
||||
* @param SrcX The x-offset in the source image.
|
||||
* @param SrcY The y-offset in the source image.
|
||||
* @param Width The width of the rectangle to copy.
|
||||
* @param Height The height of the rectangle to copy.
|
||||
* @param DestX The x-offset in the destination image (this).
|
||||
* @param DestY The y-offset in the destination image (this).
|
||||
*/
|
||||
void CopyRectFrom(const CImageInfo &SrcImage, size_t SrcX, size_t SrcY, size_t Width, size_t Height, size_t DestX, size_t DestY) const;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -273,7 +273,8 @@ public:
|
|||
virtual bool DnsblWhite(int ClientId) = 0;
|
||||
virtual bool DnsblPending(int ClientId) = 0;
|
||||
virtual bool DnsblBlack(int ClientId) = 0;
|
||||
virtual const char *GetAnnouncementLine(const char *pFileName) = 0;
|
||||
virtual const char *GetAnnouncementLine() = 0;
|
||||
virtual void ReadAnnouncementsFile(const char *pFileName) = 0;
|
||||
virtual bool ClientPrevIngame(int ClientId) = 0;
|
||||
virtual const char *GetNetErrorString(int ClientId) = 0;
|
||||
virtual void ResetNetErrorString(int ClientId) = 0;
|
||||
|
|
|
@ -61,6 +61,7 @@ public:
|
|||
virtual void BindInt(int Idx, int Value) = 0;
|
||||
virtual void BindInt64(int Idx, int64_t Value) = 0;
|
||||
virtual void BindFloat(int Idx, float Value) = 0;
|
||||
virtual void BindNull(int Idx) = 0;
|
||||
|
||||
// Print expanded sql statement
|
||||
virtual void Print() = 0;
|
||||
|
|
|
@ -89,6 +89,7 @@ public:
|
|||
void BindInt(int Idx, int Value) override;
|
||||
void BindInt64(int Idx, int64_t Value) override;
|
||||
void BindFloat(int Idx, float Value) override;
|
||||
void BindNull(int Idx) override;
|
||||
|
||||
void Print() override {}
|
||||
bool Step(bool *pEnd, char *pError, int ErrorSize) override;
|
||||
|
@ -421,6 +422,22 @@ void CMysqlConnection::BindFloat(int Idx, float Value)
|
|||
pParam->error = nullptr;
|
||||
}
|
||||
|
||||
void CMysqlConnection::BindNull(int Idx)
|
||||
{
|
||||
m_NewQuery = true;
|
||||
Idx -= 1;
|
||||
dbg_assert(0 <= Idx && Idx < (int)m_vStmtParameters.size(), "index out of bounds");
|
||||
|
||||
MYSQL_BIND *pParam = &m_vStmtParameters[Idx];
|
||||
pParam->buffer_type = MYSQL_TYPE_NULL;
|
||||
pParam->buffer = nullptr;
|
||||
pParam->buffer_length = 0;
|
||||
pParam->length = nullptr;
|
||||
pParam->is_null = nullptr;
|
||||
pParam->is_unsigned = false;
|
||||
pParam->error = nullptr;
|
||||
}
|
||||
|
||||
bool CMysqlConnection::Step(bool *pEnd, char *pError, int ErrorSize)
|
||||
{
|
||||
if(m_NewQuery)
|
||||
|
|
|
@ -37,6 +37,7 @@ public:
|
|||
void BindInt(int Idx, int Value) override;
|
||||
void BindInt64(int Idx, int64_t Value) override;
|
||||
void BindFloat(int Idx, float Value) override;
|
||||
void BindNull(int Idx) override;
|
||||
|
||||
void Print() override;
|
||||
bool Step(bool *pEnd, char *pError, int ErrorSize) override;
|
||||
|
@ -241,6 +242,13 @@ void CSqliteConnection::BindFloat(int Idx, float Value)
|
|||
m_Done = false;
|
||||
}
|
||||
|
||||
void CSqliteConnection::BindNull(int Idx)
|
||||
{
|
||||
int Result = sqlite3_bind_null(m_pStmt, Idx);
|
||||
AssertNoError(Result);
|
||||
m_Done = false;
|
||||
}
|
||||
|
||||
// Keep support for SQLite < 3.14 on older Linux distributions. MinGW does not
|
||||
// support __attribute__((weak)): https://sourceware.org/bugzilla/show_bug.cgi?id=9687
|
||||
#if defined(__GNUC__) && !defined(__MINGW32__)
|
||||
|
|
|
@ -243,6 +243,7 @@ CServer::CServer()
|
|||
}
|
||||
|
||||
m_MapReload = false;
|
||||
m_SameMapReload = false;
|
||||
m_ReloadedWhenEmpty = false;
|
||||
m_aCurrentMap[0] = '\0';
|
||||
|
||||
|
@ -536,8 +537,7 @@ int CServer::Init()
|
|||
|
||||
m_CurrentGameTick = MIN_TICK;
|
||||
|
||||
m_AnnouncementLastLine = 0;
|
||||
m_aAnnouncementFile[0] = '\0';
|
||||
m_AnnouncementLastLine = -1;
|
||||
mem_zero(m_aPrevStates, sizeof(m_aPrevStates));
|
||||
|
||||
return 0;
|
||||
|
@ -1269,6 +1269,12 @@ void CServer::SendMapData(int ClientId, int Chunk)
|
|||
}
|
||||
}
|
||||
|
||||
void CServer::SendMapReload(int ClientId)
|
||||
{
|
||||
CMsgPacker Msg(NETMSG_MAP_RELOAD, true);
|
||||
SendMsg(&Msg, MSGFLAG_VITAL | MSGFLAG_FLUSH, ClientId);
|
||||
}
|
||||
|
||||
void CServer::SendConnectionReady(int ClientId)
|
||||
{
|
||||
CMsgPacker Msg(NETMSG_CON_READY, true);
|
||||
|
@ -2553,12 +2559,13 @@ void CServer::ChangeMap(const char *pMap)
|
|||
|
||||
void CServer::ReloadMap()
|
||||
{
|
||||
m_MapReload = true;
|
||||
m_SameMapReload = true;
|
||||
}
|
||||
|
||||
int CServer::LoadMap(const char *pMapName)
|
||||
{
|
||||
m_MapReload = false;
|
||||
m_SameMapReload = false;
|
||||
|
||||
char aBuf[IO_MAX_PATH_LENGTH];
|
||||
str_format(aBuf, sizeof(aBuf), "maps/%s.map", pMapName);
|
||||
|
@ -2775,6 +2782,8 @@ int CServer::Run()
|
|||
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
|
||||
}
|
||||
|
||||
ReadAnnouncementsFile(g_Config.m_SvAnnouncementFileName);
|
||||
|
||||
// process pending commands
|
||||
m_pConsole->StoreCommands(false);
|
||||
m_pRegister->OnConfigChange();
|
||||
|
@ -2805,8 +2814,9 @@ int CServer::Run()
|
|||
int NewTicks = 0;
|
||||
|
||||
// load new map
|
||||
if(m_MapReload || m_CurrentGameTick >= MAX_TICK) // force reload to make sure the ticks stay within a valid range
|
||||
if(m_MapReload || m_SameMapReload || m_CurrentGameTick >= MAX_TICK) // force reload to make sure the ticks stay within a valid range
|
||||
{
|
||||
const bool SameMapReload = m_SameMapReload;
|
||||
// load map
|
||||
if(LoadMap(Config()->m_SvMap))
|
||||
{
|
||||
|
@ -2831,6 +2841,9 @@ int CServer::Run()
|
|||
if(m_aClients[ClientId].m_State <= CClient::STATE_AUTH)
|
||||
continue;
|
||||
|
||||
if(SameMapReload)
|
||||
SendMapReload(ClientId);
|
||||
|
||||
SendMap(ClientId);
|
||||
bool HasPersistentData = m_aClients[ClientId].m_HasPersistentData;
|
||||
m_aClients[ClientId].Reset();
|
||||
|
@ -3509,7 +3522,7 @@ void CServer::ConStopRecord(IConsole::IResult *pResult, void *pUser)
|
|||
|
||||
void CServer::ConMapReload(IConsole::IResult *pResult, void *pUser)
|
||||
{
|
||||
((CServer *)pUser)->m_MapReload = true;
|
||||
((CServer *)pUser)->ReloadMap();
|
||||
}
|
||||
|
||||
void CServer::ConLogout(IConsole::IResult *pResult, void *pUser)
|
||||
|
@ -3797,6 +3810,17 @@ void CServer::ConchainStdoutOutputLevel(IConsole::IResult *pResult, void *pUserD
|
|||
}
|
||||
}
|
||||
|
||||
void CServer::ConchainAnnouncementFileName(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
|
||||
{
|
||||
CServer *pSelf = (CServer *)pUserData;
|
||||
bool Changed = pResult->NumArguments() && str_comp(pResult->GetString(0), g_Config.m_SvAnnouncementFileName);
|
||||
pfnCallback(pResult, pCallbackUserData);
|
||||
if(Changed)
|
||||
{
|
||||
pSelf->ReadAnnouncementsFile(g_Config.m_SvAnnouncementFileName);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(CONF_FAMILY_UNIX)
|
||||
void CServer::ConchainConnLoggingServerChange(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
|
||||
{
|
||||
|
@ -3875,6 +3899,8 @@ void CServer::RegisterCommands()
|
|||
Console()->Chain("loglevel", ConchainLoglevel, this);
|
||||
Console()->Chain("stdout_output_level", ConchainStdoutOutputLevel, this);
|
||||
|
||||
Console()->Chain("sv_announcement_filename", ConchainAnnouncementFileName, this);
|
||||
|
||||
#if defined(CONF_FAMILY_UNIX)
|
||||
Console()->Chain("sv_conn_logging_server", ConchainConnLoggingServerChange, this);
|
||||
#endif
|
||||
|
@ -3918,27 +3944,30 @@ void CServer::GetClientAddr(int ClientId, NETADDR *pAddr) const
|
|||
}
|
||||
}
|
||||
|
||||
const char *CServer::GetAnnouncementLine(const char *pFileName)
|
||||
void CServer::ReadAnnouncementsFile(const char *pFileName)
|
||||
{
|
||||
if(str_comp(pFileName, m_aAnnouncementFile) != 0)
|
||||
{
|
||||
str_copy(m_aAnnouncementFile, pFileName);
|
||||
m_vAnnouncements.clear();
|
||||
m_vAnnouncements.clear();
|
||||
|
||||
CLineReader LineReader;
|
||||
if(!LineReader.OpenFile(m_pStorage->OpenFile(pFileName, IOFLAG_READ, IStorage::TYPE_ALL)))
|
||||
if(pFileName[0] == '\0')
|
||||
return;
|
||||
|
||||
CLineReader LineReader;
|
||||
if(!LineReader.OpenFile(m_pStorage->OpenFile(pFileName, IOFLAG_READ, IStorage::TYPE_ALL)))
|
||||
{
|
||||
dbg_msg("announcements", "failed to open '%s'", pFileName);
|
||||
return;
|
||||
}
|
||||
while(const char *pLine = LineReader.Get())
|
||||
{
|
||||
if(str_length(pLine) && pLine[0] != '#')
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
while(const char *pLine = LineReader.Get())
|
||||
{
|
||||
if(str_length(pLine) && pLine[0] != '#')
|
||||
{
|
||||
m_vAnnouncements.emplace_back(pLine);
|
||||
}
|
||||
m_vAnnouncements.emplace_back(pLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char *CServer::GetAnnouncementLine()
|
||||
{
|
||||
if(m_vAnnouncements.empty())
|
||||
{
|
||||
return 0;
|
||||
|
@ -3947,7 +3976,7 @@ const char *CServer::GetAnnouncementLine(const char *pFileName)
|
|||
{
|
||||
m_AnnouncementLastLine = 0;
|
||||
}
|
||||
else if(!Config()->m_SvAnnouncementRandom)
|
||||
else if(!g_Config.m_SvAnnouncementRandom)
|
||||
{
|
||||
if(++m_AnnouncementLastLine >= m_vAnnouncements.size())
|
||||
m_AnnouncementLastLine %= m_vAnnouncements.size();
|
||||
|
|
|
@ -219,6 +219,7 @@ public:
|
|||
int m_RunServer;
|
||||
|
||||
bool m_MapReload;
|
||||
bool m_SameMapReload;
|
||||
bool m_ReloadedWhenEmpty;
|
||||
int m_RconClientId;
|
||||
int m_RconAuthLevel;
|
||||
|
@ -258,7 +259,6 @@ public:
|
|||
|
||||
size_t m_AnnouncementLastLine;
|
||||
std::vector<std::string> m_vAnnouncements;
|
||||
char m_aAnnouncementFile[IO_MAX_PATH_LENGTH];
|
||||
|
||||
std::shared_ptr<ILogger> m_pFileLogger = nullptr;
|
||||
std::shared_ptr<ILogger> m_pStdoutLogger = nullptr;
|
||||
|
@ -324,6 +324,7 @@ public:
|
|||
void SendCapabilities(int ClientId);
|
||||
void SendMap(int ClientId);
|
||||
void SendMapData(int ClientId, int Chunk);
|
||||
void SendMapReload(int ClientId);
|
||||
void SendConnectionReady(int ClientId);
|
||||
void SendRconLine(int ClientId, const char *pLine);
|
||||
// Accepts -1 as ClientId to mean "all clients with at least auth level admin"
|
||||
|
@ -425,6 +426,7 @@ public:
|
|||
static void ConchainSixupUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
|
||||
static void ConchainLoglevel(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
|
||||
static void ConchainStdoutOutputLevel(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
|
||||
static void ConchainAnnouncementFileName(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
|
||||
|
||||
#if defined(CONF_FAMILY_UNIX)
|
||||
static void ConchainConnLoggingServerChange(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
|
||||
|
@ -441,7 +443,8 @@ public:
|
|||
|
||||
void GetClientAddr(int ClientId, NETADDR *pAddr) const override;
|
||||
int m_aPrevStates[MAX_CLIENTS];
|
||||
const char *GetAnnouncementLine(const char *pFileName) override;
|
||||
const char *GetAnnouncementLine() override;
|
||||
void ReadAnnouncementsFile(const char *pFileName) override;
|
||||
|
||||
int *GetIdMap(int ClientId) override;
|
||||
|
||||
|
|
|
@ -73,6 +73,7 @@ MACRO_CONFIG_INT(ClShowfps, cl_showfps, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE,
|
|||
MACRO_CONFIG_INT(ClShowpred, cl_showpred, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame prediction time in milliseconds")
|
||||
MACRO_CONFIG_INT(ClEyeWheel, cl_eye_wheel, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show eye wheel along together with emotes")
|
||||
MACRO_CONFIG_INT(ClEyeDuration, cl_eye_duration, 999999, 1, 999999, CFGFLAG_CLIENT | CFGFLAG_SAVE, "How long the eyes emotes last")
|
||||
MACRO_CONFIG_INT(ClFreezeStars, cl_freeze_stars, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show old star particles for frozen tees")
|
||||
|
||||
MACRO_CONFIG_INT(ClAirjumpindicator, cl_airjumpindicator, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show the air jump indicator")
|
||||
MACRO_CONFIG_INT(ClThreadsoundloading, cl_threadsoundloading, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Load sound files threaded")
|
||||
|
@ -227,7 +228,7 @@ MACRO_CONFIG_INT(SvWarmup, sv_warmup, 0, 0, 0, CFGFLAG_SERVER, "Number of second
|
|||
MACRO_CONFIG_STR(SvMotd, sv_motd, 900, "", CFGFLAG_SERVER, "Message of the day to display for the clients")
|
||||
MACRO_CONFIG_STR(SvGametype, sv_gametype, 32, "ddnet", CFGFLAG_SERVER, "Game type (ddnet, mod)")
|
||||
MACRO_CONFIG_INT(SvTournamentMode, sv_tournament_mode, 0, 0, 1, CFGFLAG_SERVER, "Tournament mode. When enabled, players joins the server as spectator")
|
||||
MACRO_CONFIG_INT(SvSpamprotection, sv_spamprotection, 1, 0, 1, CFGFLAG_SERVER, "Spam protection")
|
||||
MACRO_CONFIG_INT(SvSpamprotection, sv_spamprotection, 1, 0, 1, CFGFLAG_SERVER, "Spam protection for: team change, chat, skin change, emotes and votes")
|
||||
|
||||
MACRO_CONFIG_INT(SvSpectatorSlots, sv_spectator_slots, 0, 0, MAX_CLIENTS, CFGFLAG_SERVER, "Number of slots to reserve for spectators")
|
||||
MACRO_CONFIG_INT(SvInactiveKickTime, sv_inactivekick_time, 0, 0, 1000, CFGFLAG_SERVER, "How many minutes to wait before taking care of inactive players")
|
||||
|
@ -547,8 +548,8 @@ MACRO_CONFIG_INT(SvMinTeamSize, sv_min_team_size, 2, 1, MAX_CLIENTS, CFGFLAG_SER
|
|||
MACRO_CONFIG_INT(SvMaxTeamSize, sv_max_team_size, MAX_CLIENTS, 1, MAX_CLIENTS, CFGFLAG_SERVER | CFGFLAG_GAME, "Maximum team size")
|
||||
MACRO_CONFIG_INT(SvMapVote, sv_map_vote, 1, 0, 1, CFGFLAG_SERVER, "Whether to allow /map")
|
||||
|
||||
MACRO_CONFIG_STR(SvAnnouncementFileName, sv_announcement_filename, 24, "announcement.txt", CFGFLAG_SERVER, "file which will have the announcement, each one at a line")
|
||||
MACRO_CONFIG_INT(SvAnnouncementInterval, sv_announcement_interval, 300, 1, 9999, CFGFLAG_SERVER, "time(minutes) in which the announcement will be displayed from the announcement file")
|
||||
MACRO_CONFIG_STR(SvAnnouncementFileName, sv_announcement_filename, IO_MAX_PATH_LENGTH, "announcement.txt", CFGFLAG_SERVER, "File which contains the announcements, one on each line")
|
||||
MACRO_CONFIG_INT(SvAnnouncementInterval, sv_announcement_interval, 120, 1, 9999, CFGFLAG_SERVER, "The time (minutes) for how often an announcement will be displayed from the announcement file")
|
||||
MACRO_CONFIG_INT(SvAnnouncementRandom, sv_announcement_random, 1, 0, 1, CFGFLAG_SERVER, "Whether announcements are sequential or random")
|
||||
|
||||
MACRO_CONFIG_INT(SvOldLaser, sv_old_laser, 0, 0, 1, CFGFLAG_SERVER | CFGFLAG_GAME, "Whether lasers can hit you if you shot them and that they pull you towards the bounce origin (0 for all new maps) or lasers can't hit you if you shot them, and they pull others towards the shooter")
|
||||
|
|
|
@ -70,18 +70,18 @@ void CEcon::Init(CConfig *pConfig, IConsole *pConsole, CNetBan *pNetBan)
|
|||
}
|
||||
|
||||
NETADDR BindAddr;
|
||||
if(g_Config.m_EcBindaddr[0] == '\0')
|
||||
if(g_Config.m_EcBindaddr[0] && net_host_lookup(g_Config.m_EcBindaddr, &BindAddr, NETTYPE_ALL) == 0)
|
||||
{
|
||||
mem_zero(&BindAddr, sizeof(BindAddr));
|
||||
// got bindaddr
|
||||
BindAddr.port = g_Config.m_EcPort;
|
||||
}
|
||||
else if(net_host_lookup(g_Config.m_EcBindaddr, &BindAddr, NETTYPE_ALL) != 0)
|
||||
else
|
||||
{
|
||||
char aBuf[256];
|
||||
str_format(aBuf, sizeof(aBuf), "The configured bindaddr '%s' cannot be resolved.", g_Config.m_Bindaddr);
|
||||
str_format(aBuf, sizeof(aBuf), "The configured bindaddr '%s' cannot be resolved.", g_Config.m_EcBindaddr);
|
||||
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "econ", aBuf);
|
||||
return;
|
||||
}
|
||||
BindAddr.type = NETTYPE_ALL;
|
||||
BindAddr.port = g_Config.m_EcPort;
|
||||
|
||||
if(m_NetConsole.Open(BindAddr, pNetBan))
|
||||
{
|
||||
|
|
|
@ -35,3 +35,4 @@ UUID(NETMSG_CHECKSUM_ERROR, "checksum-error@ddnet.tw")
|
|||
UUID(NETMSG_REDIRECT, "redirect@ddnet.org")
|
||||
UUID(NETMSG_RCON_CMD_GROUP_START, "rcon-cmd-group-start@ddnet.org")
|
||||
UUID(NETMSG_RCON_CMD_GROUP_END, "rcon-cmd-group-end@ddnet.org")
|
||||
UUID(NETMSG_MAP_RELOAD, "map-reload@ddnet.org")
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#include <engine/config.h>
|
||||
#include <engine/shared/config.h>
|
||||
|
||||
#include <game/client/components/chat.h>
|
||||
#include <game/client/components/console.h>
|
||||
#include <game/client/gameclient.h>
|
||||
|
||||
static const ColorRGBA gs_BindPrintColor{1.0f, 1.0f, 0.8f, 1.0f};
|
||||
|
@ -126,7 +128,7 @@ bool CBinds::OnInput(const IInput::CEvent &Event)
|
|||
});
|
||||
if(ActiveBind == m_vActiveBinds.end())
|
||||
{
|
||||
const auto &&OnPress = [&](int Mask) {
|
||||
const auto &&OnKeyPress = [&](int Mask) {
|
||||
const char *pBind = m_aapKeyBindings[Mask][Event.m_Key];
|
||||
if(g_Config.m_ClSubTickAiming)
|
||||
{
|
||||
|
@ -141,14 +143,14 @@ bool CBinds::OnInput(const IInput::CEvent &Event)
|
|||
|
||||
if(m_aapKeyBindings[ModifierMask][Event.m_Key])
|
||||
{
|
||||
OnPress(ModifierMask);
|
||||
OnKeyPress(ModifierMask);
|
||||
Handled = true;
|
||||
}
|
||||
else if(m_aapKeyBindings[MODIFIER_NONE][Event.m_Key] &&
|
||||
ModifierMask != ((1 << MODIFIER_CTRL) | (1 << MODIFIER_SHIFT)) &&
|
||||
ModifierMask != ((1 << MODIFIER_GUI) | (1 << MODIFIER_SHIFT)))
|
||||
{
|
||||
OnPress(MODIFIER_NONE);
|
||||
OnKeyPress(MODIFIER_NONE);
|
||||
Handled = true;
|
||||
}
|
||||
}
|
||||
|
@ -166,17 +168,30 @@ bool CBinds::OnInput(const IInput::CEvent &Event)
|
|||
|
||||
if(Event.m_Flags & IInput::FLAG_RELEASE)
|
||||
{
|
||||
const auto &&OnKeyRelease = [&](const CBindSlot &Bind) {
|
||||
// Prevent binds from being deactivated while chat, console and menus are open, as these components will
|
||||
// still allow key release events to be forwarded to this component, so the active binds can be cleared.
|
||||
if(GameClient()->m_Chat.IsActive() ||
|
||||
!GameClient()->m_GameConsole.IsClosed() ||
|
||||
GameClient()->m_Menus.IsActive())
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Have to check for nullptr again because the previous execute can unbind itself
|
||||
if(!m_aapKeyBindings[Bind.m_ModifierMask][Bind.m_Key])
|
||||
{
|
||||
return;
|
||||
}
|
||||
Console()->ExecuteLineStroked(0, m_aapKeyBindings[Bind.m_ModifierMask][Bind.m_Key]);
|
||||
};
|
||||
|
||||
// Release active bind that uses this primary key
|
||||
auto ActiveBind = std::find_if(m_vActiveBinds.begin(), m_vActiveBinds.end(), [&](const CBindSlot &Bind) {
|
||||
return Event.m_Key == Bind.m_Key;
|
||||
});
|
||||
if(ActiveBind != m_vActiveBinds.end())
|
||||
{
|
||||
// Have to check for nullptr again because the previous execute can unbind itself
|
||||
if(m_aapKeyBindings[ActiveBind->m_ModifierMask][ActiveBind->m_Key])
|
||||
{
|
||||
Console()->ExecuteLineStroked(0, m_aapKeyBindings[ActiveBind->m_ModifierMask][ActiveBind->m_Key]);
|
||||
}
|
||||
OnKeyRelease(*ActiveBind);
|
||||
m_vActiveBinds.erase(ActiveBind);
|
||||
Handled = true;
|
||||
}
|
||||
|
@ -191,11 +206,7 @@ bool CBinds::OnInput(const IInput::CEvent &Event)
|
|||
});
|
||||
if(ActiveModifierBind == m_vActiveBinds.end())
|
||||
break;
|
||||
// Have to check for nullptr again because the previous execute can unbind itself
|
||||
if(m_aapKeyBindings[ActiveModifierBind->m_ModifierMask][ActiveModifierBind->m_Key])
|
||||
{
|
||||
Console()->ExecuteLineStroked(0, m_aapKeyBindings[ActiveModifierBind->m_ModifierMask][ActiveModifierBind->m_Key]);
|
||||
}
|
||||
OnKeyRelease(*ActiveModifierBind);
|
||||
m_vActiveBinds.erase(ActiveModifierBind);
|
||||
Handled = true;
|
||||
}
|
||||
|
|
|
@ -355,8 +355,6 @@ bool CChat::OnInput(const IInput::CEvent &Event)
|
|||
// add separator
|
||||
const char *pSeparator = pCompletionCommand->m_aParams[0] == '\0' ? "" : " ";
|
||||
str_append(aBuf, pSeparator);
|
||||
if(*pSeparator)
|
||||
str_append(aBuf, pSeparator);
|
||||
|
||||
// add part after the name
|
||||
str_append(aBuf, m_Input.GetString() + m_PlaceholderOffset + m_PlaceholderLength);
|
||||
|
@ -552,14 +550,16 @@ void CChat::OnMessage(int MsgType, void *pRawMsg)
|
|||
|
||||
bool CChat::LineShouldHighlight(const char *pLine, const char *pName)
|
||||
{
|
||||
const char *pHL = str_utf8_find_nocase(pLine, pName);
|
||||
const char *pHit = str_utf8_find_nocase(pLine, pName);
|
||||
|
||||
if(pHL)
|
||||
while(pHit)
|
||||
{
|
||||
int Length = str_length(pName);
|
||||
|
||||
if(Length > 0 && (pLine == pHL || pHL[-1] == ' ') && (pHL[Length] == 0 || pHL[Length] == ' ' || pHL[Length] == '.' || pHL[Length] == '!' || pHL[Length] == ',' || pHL[Length] == '?' || pHL[Length] == ':'))
|
||||
if(Length > 0 && (pLine == pHit || pHit[-1] == ' ') && (pHit[Length] == 0 || pHit[Length] == ' ' || pHit[Length] == '.' || pHit[Length] == '!' || pHit[Length] == ',' || pHit[Length] == '?' || pHit[Length] == ':'))
|
||||
return true;
|
||||
|
||||
pHit = str_utf8_find_nocase(pHit + 1, pName);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -805,50 +805,9 @@ void CChat::AddLine(int ClientId, int Team, const char *pLine)
|
|||
{
|
||||
if(!g_Config.m_ClChatOld)
|
||||
{
|
||||
pCurrentLine->m_CustomColoredSkin = LineAuthor.m_RenderInfo.m_CustomColoredSkin;
|
||||
if(pCurrentLine->m_CustomColoredSkin)
|
||||
pCurrentLine->m_RenderSkin = LineAuthor.m_RenderInfo.m_ColorableRenderSkin;
|
||||
else
|
||||
pCurrentLine->m_RenderSkin = LineAuthor.m_RenderInfo.m_OriginalRenderSkin;
|
||||
|
||||
str_copy(pCurrentLine->m_aSkinName, LineAuthor.m_aSkinName);
|
||||
pCurrentLine->m_ColorBody = LineAuthor.m_RenderInfo.m_ColorBody;
|
||||
pCurrentLine->m_ColorFeet = LineAuthor.m_RenderInfo.m_ColorFeet;
|
||||
|
||||
pCurrentLine->m_RenderSkinMetrics = LineAuthor.m_RenderInfo.m_SkinMetrics;
|
||||
pCurrentLine->m_TeeRenderInfo = LineAuthor.m_RenderInfo;
|
||||
pCurrentLine->m_HasRenderTee = true;
|
||||
|
||||
// 0.7
|
||||
if(Client()->IsSixup())
|
||||
{
|
||||
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
|
||||
{
|
||||
const char *pPartName = LineAuthor.m_aSixup[g_Config.m_ClDummy].m_aaSkinPartNames[Part];
|
||||
int Id = m_pClient->m_Skins7.FindSkinPart(Part, pPartName, false);
|
||||
const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.GetSkinPart(Part, Id);
|
||||
if(LineAuthor.m_aSixup[g_Config.m_ClDummy].m_aUseCustomColors[Part])
|
||||
{
|
||||
pCurrentLine->m_Sixup.m_aTextures[Part] = pSkinPart->m_ColorTexture;
|
||||
pCurrentLine->m_Sixup.m_aColors[Part] = m_pClient->m_Skins7.GetColor(
|
||||
LineAuthor.m_aSixup[g_Config.m_ClDummy].m_aSkinPartColors[Part],
|
||||
Part == protocol7::SKINPART_MARKING);
|
||||
}
|
||||
else
|
||||
{
|
||||
pCurrentLine->m_Sixup.m_aTextures[Part] = pSkinPart->m_OrgTexture;
|
||||
pCurrentLine->m_Sixup.m_aColors[Part] = vec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
if(LineAuthor.m_SkinInfo.m_aSixup[g_Config.m_ClDummy].m_HatTexture.IsValid())
|
||||
{
|
||||
if(Part == protocol7::SKINPART_BODY && str_comp(pPartName, "standard"))
|
||||
pCurrentLine->m_Sixup.m_HatSpriteIndex = CSkins7::HAT_OFFSET_SIDE + (ClientId % CSkins7::HAT_NUM);
|
||||
if(Part == protocol7::SKINPART_DECORATION && str_comp(pPartName, "twinbopp"))
|
||||
pCurrentLine->m_Sixup.m_HatSpriteIndex = CSkins7::HAT_OFFSET_SIDE + (ClientId % CSkins7::HAT_NUM);
|
||||
pCurrentLine->m_Sixup.m_HatTexture = LineAuthor.m_SkinInfo.m_aSixup[g_Config.m_ClDummy].m_HatTexture;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -917,17 +876,11 @@ void CChat::OnRefreshSkins()
|
|||
{
|
||||
if(Line.m_HasRenderTee)
|
||||
{
|
||||
const CSkin *pSkin = m_pClient->m_Skins.Find(Line.m_aSkinName);
|
||||
if(Line.m_CustomColoredSkin)
|
||||
Line.m_RenderSkin = pSkin->m_ColorableSkin;
|
||||
else
|
||||
Line.m_RenderSkin = pSkin->m_OriginalSkin;
|
||||
|
||||
Line.m_RenderSkinMetrics = pSkin->m_Metrics;
|
||||
Line.m_TeeRenderInfo.Apply(m_pClient->m_Skins.Find(Line.m_aSkinName));
|
||||
}
|
||||
else
|
||||
{
|
||||
Line.m_RenderSkin.Reset();
|
||||
Line.m_TeeRenderInfo.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1233,6 +1186,23 @@ void CChat::OnRender()
|
|||
|
||||
m_Input.SetScrollOffset(ScrollOffset);
|
||||
m_Input.SetScrollOffsetChange(ScrollOffsetChange);
|
||||
|
||||
// Autocompletion hint
|
||||
if(m_Input.GetString()[0] == '/' && m_Input.GetString()[1] != '\0' && !m_vCommands.empty())
|
||||
{
|
||||
for(const auto &Command : m_vCommands)
|
||||
{
|
||||
if(str_startswith_nocase(Command.m_aName, m_Input.GetString() + 1))
|
||||
{
|
||||
Cursor.m_X = m_Input.GetCaretPosition().x;
|
||||
Cursor.m_Y = m_Input.GetCaretPosition().y;
|
||||
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 0.5f);
|
||||
TextRender()->TextEx(&Cursor, Command.m_aName + str_length(m_Input.GetString() + 1));
|
||||
TextRender()->TextColor(TextRender()->DefaultTextColor());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(CONF_VIDEORECORDER)
|
||||
|
@ -1291,28 +1261,7 @@ void CChat::OnRender()
|
|||
if(!g_Config.m_ClChatOld && Line.m_HasRenderTee)
|
||||
{
|
||||
const int TeeSize = MessageTeeSize();
|
||||
CTeeRenderInfo RenderInfo;
|
||||
RenderInfo.m_CustomColoredSkin = Line.m_CustomColoredSkin;
|
||||
if(Line.m_CustomColoredSkin)
|
||||
RenderInfo.m_ColorableRenderSkin = Line.m_RenderSkin;
|
||||
else
|
||||
RenderInfo.m_OriginalRenderSkin = Line.m_RenderSkin;
|
||||
RenderInfo.m_SkinMetrics = Line.m_RenderSkinMetrics;
|
||||
|
||||
RenderInfo.m_ColorBody = Line.m_ColorBody;
|
||||
RenderInfo.m_ColorFeet = Line.m_ColorFeet;
|
||||
RenderInfo.m_Size = TeeSize;
|
||||
|
||||
if(Client()->IsSixup())
|
||||
{
|
||||
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
|
||||
{
|
||||
RenderInfo.m_aSixup[g_Config.m_ClDummy].m_aColors[Part] = Line.m_Sixup.m_aColors[Part];
|
||||
RenderInfo.m_aSixup[g_Config.m_ClDummy].m_aTextures[Part] = Line.m_Sixup.m_aTextures[Part];
|
||||
RenderInfo.m_aSixup[g_Config.m_ClDummy].m_HatSpriteIndex = Line.m_Sixup.m_HatSpriteIndex;
|
||||
RenderInfo.m_aSixup[g_Config.m_ClDummy].m_HatTexture = Line.m_Sixup.m_HatTexture;
|
||||
}
|
||||
}
|
||||
Line.m_TeeRenderInfo.m_Size = TeeSize;
|
||||
|
||||
float RowHeight = FontSize() + RealMsgPaddingY;
|
||||
float OffsetTeeY = TeeSize / 2.0f;
|
||||
|
@ -1320,9 +1269,9 @@ void CChat::OnRender()
|
|||
|
||||
const CAnimState *pIdleState = CAnimState::GetIdle();
|
||||
vec2 OffsetToMid;
|
||||
CRenderTools::GetRenderTeeOffsetToRenderedTee(pIdleState, &RenderInfo, OffsetToMid);
|
||||
CRenderTools::GetRenderTeeOffsetToRenderedTee(pIdleState, &Line.m_TeeRenderInfo, OffsetToMid);
|
||||
vec2 TeeRenderPos(x + (RealMsgPaddingX + TeeSize) / 2.0f, y + OffsetTeeY + FullHeightMinusTee / 2.0f + OffsetToMid.y);
|
||||
RenderTools()->RenderTee(pIdleState, &RenderInfo, EMOTE_NORMAL, vec2(1, 0.1f), TeeRenderPos, Blend);
|
||||
RenderTools()->RenderTee(pIdleState, &Line.m_TeeRenderInfo, EMOTE_NORMAL, vec2(1, 0.1f), TeeRenderPos, Blend);
|
||||
}
|
||||
|
||||
const ColorRGBA TextColor = TextRender()->DefaultTextColor().WithMultipliedAlpha(Blend);
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <game/client/component.h>
|
||||
#include <game/client/lineinput.h>
|
||||
#include <game/client/render.h>
|
||||
#include <game/client/skin.h>
|
||||
#include <game/generated/protocol7.h>
|
||||
|
||||
|
@ -45,30 +46,12 @@ class CChat : public CComponent
|
|||
int m_QuadContainerIndex;
|
||||
|
||||
char m_aSkinName[std::size(g_Config.m_ClPlayerSkin)];
|
||||
CSkin::SSkinTextures m_RenderSkin;
|
||||
CSkin::SSkinMetrics m_RenderSkinMetrics;
|
||||
bool m_CustomColoredSkin;
|
||||
ColorRGBA m_ColorBody;
|
||||
ColorRGBA m_ColorFeet;
|
||||
|
||||
bool m_HasRenderTee;
|
||||
CTeeRenderInfo m_TeeRenderInfo;
|
||||
|
||||
float m_TextYOffset;
|
||||
|
||||
int m_TimesRepeated;
|
||||
|
||||
class CSixup
|
||||
{
|
||||
public:
|
||||
IGraphics::CTextureHandle m_aTextures[protocol7::NUM_SKINPARTS];
|
||||
IGraphics::CTextureHandle m_HatTexture;
|
||||
IGraphics::CTextureHandle m_BotTexture;
|
||||
int m_HatSpriteIndex;
|
||||
ColorRGBA m_BotColor;
|
||||
ColorRGBA m_aColors[protocol7::NUM_SKINPARTS];
|
||||
};
|
||||
|
||||
// 0.7 Skin
|
||||
CSixup m_Sixup;
|
||||
};
|
||||
|
||||
bool m_PrevScoreBoardShowed;
|
||||
|
|
|
@ -366,10 +366,7 @@ void CGhost::OnRender()
|
|||
IsTeamplay = (m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS) != 0;
|
||||
|
||||
GhostNinjaRenderInfo = Ghost.m_RenderInfo;
|
||||
GhostNinjaRenderInfo.m_OriginalRenderSkin = pSkin->m_OriginalSkin;
|
||||
GhostNinjaRenderInfo.m_ColorableRenderSkin = pSkin->m_ColorableSkin;
|
||||
GhostNinjaRenderInfo.m_BloodColor = pSkin->m_BloodColor;
|
||||
GhostNinjaRenderInfo.m_SkinMetrics = pSkin->m_Metrics;
|
||||
GhostNinjaRenderInfo.Apply(pSkin);
|
||||
GhostNinjaRenderInfo.m_CustomColoredSkin = IsTeamplay;
|
||||
if(!IsTeamplay)
|
||||
{
|
||||
|
@ -392,11 +389,7 @@ void CGhost::InitRenderInfos(CGhostItem *pGhost)
|
|||
IntsToStr(&pGhost->m_Skin.m_Skin0, 6, aSkinName, std::size(aSkinName));
|
||||
CTeeRenderInfo *pRenderInfo = &pGhost->m_RenderInfo;
|
||||
|
||||
const CSkin *pSkin = m_pClient->m_Skins.Find(aSkinName);
|
||||
pRenderInfo->m_OriginalRenderSkin = pSkin->m_OriginalSkin;
|
||||
pRenderInfo->m_ColorableRenderSkin = pSkin->m_ColorableSkin;
|
||||
pRenderInfo->m_BloodColor = pSkin->m_BloodColor;
|
||||
pRenderInfo->m_SkinMetrics = pSkin->m_Metrics;
|
||||
pRenderInfo->Apply(m_pClient->m_Skins.Find(aSkinName));
|
||||
pRenderInfo->m_CustomColoredSkin = pGhost->m_Skin.m_UseCustomColor;
|
||||
if(pGhost->m_Skin.m_UseCustomColor)
|
||||
{
|
||||
|
@ -697,11 +690,7 @@ void CGhost::OnRefreshSkins()
|
|||
CTeeRenderInfo *pRenderInfo = &Ghost.m_RenderInfo;
|
||||
if(aSkinName[0] != '\0')
|
||||
{
|
||||
const CSkin *pSkin = m_pClient->m_Skins.Find(aSkinName);
|
||||
pRenderInfo->m_OriginalRenderSkin = pSkin->m_OriginalSkin;
|
||||
pRenderInfo->m_ColorableRenderSkin = pSkin->m_ColorableSkin;
|
||||
pRenderInfo->m_BloodColor = pSkin->m_BloodColor;
|
||||
pRenderInfo->m_SkinMetrics = pSkin->m_Metrics;
|
||||
pRenderInfo->Apply(m_pClient->m_Skins.Find(aSkinName));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -76,6 +76,8 @@ void CHud::OnReset()
|
|||
m_ServerRecord = -1.0f;
|
||||
m_aPlayerRecord[0] = -1.0f;
|
||||
m_aPlayerRecord[1] = -1.0f;
|
||||
m_aLastPlayerSpeedChange[0] = ESpeedChange::NONE;
|
||||
m_aLastPlayerSpeedChange[1] = ESpeedChange::NONE;
|
||||
|
||||
ResetHudContainers();
|
||||
}
|
||||
|
@ -1275,11 +1277,11 @@ void CHud::UpdateMovementInformationTextContainer(STextContainerIndex &TextConta
|
|||
}
|
||||
}
|
||||
|
||||
void CHud::RenderMovementInformationTextContainer(STextContainerIndex &TextContainer, float X, float Y)
|
||||
void CHud::RenderMovementInformationTextContainer(STextContainerIndex &TextContainer, const ColorRGBA &Color, float X, float Y)
|
||||
{
|
||||
if(TextContainer.Valid())
|
||||
{
|
||||
TextRender()->RenderTextContainer(TextContainer, TextRender()->DefaultTextColor(), TextRender()->DefaultTextOutlineColor(), X - TextRender()->GetBoundingBoxTextContainer(TextContainer).m_W, Y);
|
||||
TextRender()->RenderTextContainer(TextContainer, Color, TextRender()->DefaultTextOutlineColor(), X - TextRender()->GetBoundingBoxTextContainer(TextContainer).m_W, Y);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1352,12 +1354,12 @@ void CHud::RenderMovementInformation(const int ClientId)
|
|||
|
||||
TextRender()->Text(xl, y, Fontsize, "X:", -1.0f);
|
||||
UpdateMovementInformationTextContainer(m_aPlayerPositionContainers[0], Fontsize, Pos.x, m_aaPlayerPositionText[0], sizeof(m_aaPlayerPositionText[0]));
|
||||
RenderMovementInformationTextContainer(m_aPlayerPositionContainers[0], xr, y);
|
||||
RenderMovementInformationTextContainer(m_aPlayerPositionContainers[0], TextRender()->DefaultTextColor(), xr, y);
|
||||
y += MOVEMENT_INFORMATION_LINE_HEIGHT;
|
||||
|
||||
TextRender()->Text(xl, y, Fontsize, "Y:", -1.0f);
|
||||
UpdateMovementInformationTextContainer(m_aPlayerPositionContainers[1], Fontsize, Pos.y, m_aaPlayerPositionText[1], sizeof(m_aaPlayerPositionText[1]));
|
||||
RenderMovementInformationTextContainer(m_aPlayerPositionContainers[1], xr, y);
|
||||
RenderMovementInformationTextContainer(m_aPlayerPositionContainers[1], TextRender()->DefaultTextColor(), xr, y);
|
||||
y += MOVEMENT_INFORMATION_LINE_HEIGHT;
|
||||
}
|
||||
|
||||
|
@ -1369,14 +1371,14 @@ void CHud::RenderMovementInformation(const int ClientId)
|
|||
const char aaCoordinates[][4] = {"X:", "Y:"};
|
||||
for(int i = 0; i < 2; i++)
|
||||
{
|
||||
TextRender()->TextColor(ColorRGBA(1, 1, 1, 1));
|
||||
ColorRGBA Color(1, 1, 1, 1);
|
||||
if(m_aLastPlayerSpeedChange[i] == ESpeedChange::INCREASE)
|
||||
TextRender()->TextColor(ColorRGBA(0, 1, 0, 1));
|
||||
Color = ColorRGBA(0, 1, 0, 1);
|
||||
if(m_aLastPlayerSpeedChange[i] == ESpeedChange::DECREASE)
|
||||
TextRender()->TextColor(ColorRGBA(1, 0.5f, 0.5f, 1));
|
||||
Color = ColorRGBA(1, 0.5f, 0.5f, 1);
|
||||
TextRender()->Text(xl, y, Fontsize, aaCoordinates[i], -1.0f);
|
||||
UpdateMovementInformationTextContainer(m_aPlayerSpeedTextContainers[i], Fontsize, i == 0 ? DisplaySpeedX : DisplaySpeedY, m_aaPlayerSpeedText[i], sizeof(m_aaPlayerSpeedText[i]));
|
||||
RenderMovementInformationTextContainer(m_aPlayerSpeedTextContainers[i], xr, y);
|
||||
RenderMovementInformationTextContainer(m_aPlayerSpeedTextContainers[i], Color, xr, y);
|
||||
y += MOVEMENT_INFORMATION_LINE_HEIGHT;
|
||||
}
|
||||
|
||||
|
@ -1389,7 +1391,7 @@ void CHud::RenderMovementInformation(const int ClientId)
|
|||
y += MOVEMENT_INFORMATION_LINE_HEIGHT;
|
||||
|
||||
UpdateMovementInformationTextContainer(m_PlayerAngleTextContainerIndex, Fontsize, DisplayAngle, m_aPlayerAngleText, sizeof(m_aPlayerAngleText));
|
||||
RenderMovementInformationTextContainer(m_PlayerAngleTextContainerIndex, xr, y);
|
||||
RenderMovementInformationTextContainer(m_PlayerAngleTextContainerIndex, TextRender()->DefaultTextColor(), xr, y);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ class CHud : public CComponent
|
|||
void RenderMovementInformation(const int ClientId);
|
||||
|
||||
void UpdateMovementInformationTextContainer(STextContainerIndex &TextContainer, float FontSize, float Value, char *pPrevValue, size_t Size);
|
||||
void RenderMovementInformationTextContainer(STextContainerIndex &TextContainer, float X, float Y);
|
||||
void RenderMovementInformationTextContainer(STextContainerIndex &TextContainer, const ColorRGBA &Color, float X, float Y);
|
||||
|
||||
void RenderGameTimer();
|
||||
void RenderPauseNotification();
|
||||
|
|
|
@ -301,7 +301,7 @@ IGraphics::CTextureHandle CMapImages::GetEntities(EMapImageEntityLayerType Entit
|
|||
const size_t CopyHeight = ImgInfo.m_Height / 16;
|
||||
const size_t OffsetX = (size_t)(TileIndex % 16) * CopyWidth;
|
||||
const size_t OffsetY = (size_t)(TileIndex / 16) * CopyHeight;
|
||||
Graphics()->CopyTextureBufferSub(BuildImageInfo.m_pData, ImgInfo, OffsetX, OffsetY, CopyWidth, CopyHeight);
|
||||
BuildImageInfo.CopyRectFrom(ImgInfo, OffsetX, OffsetY, CopyWidth, CopyHeight, OffsetX, OffsetY);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <engine/config.h>
|
||||
#include <engine/editor.h>
|
||||
#include <engine/friends.h>
|
||||
#include <engine/gfx/image_manipulation.h>
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/keys.h>
|
||||
#include <engine/serverbrowser.h>
|
||||
|
@ -2396,16 +2397,7 @@ int CMenus::MenuImageScan(const char *pName, int IsDir, int DirType, void *pUser
|
|||
|
||||
MenuImage.m_OrgTexture = pSelf->Graphics()->LoadTextureRaw(Info, 0, aPath);
|
||||
|
||||
// create gray scale version
|
||||
unsigned char *pData = static_cast<unsigned char *>(Info.m_pData);
|
||||
const size_t Step = Info.PixelSize();
|
||||
for(size_t i = 0; i < Info.m_Width * Info.m_Height; i++)
|
||||
{
|
||||
int v = (pData[i * Step] + pData[i * Step + 1] + pData[i * Step + 2]) / 3;
|
||||
pData[i * Step] = v;
|
||||
pData[i * Step + 1] = v;
|
||||
pData[i * Step + 2] = v;
|
||||
}
|
||||
ConvertToGrayscale(Info);
|
||||
MenuImage.m_GreyTexture = pSelf->Graphics()->LoadTextureRawMove(Info, 0, aPath);
|
||||
|
||||
str_truncate(MenuImage.m_aName, sizeof(MenuImage.m_aName), pName, str_length(pName) - str_length(pExtension));
|
||||
|
|
|
@ -248,9 +248,11 @@ protected:
|
|||
int m_SettingPlayerPage;
|
||||
|
||||
// 0.7 skins
|
||||
bool m_CustomSkinMenu = false;
|
||||
int m_TeePartSelected = protocol7::SKINPART_BODY;
|
||||
const CSkins7::CSkin *m_pSelectedSkin = nullptr;
|
||||
CLineInputBuffered<protocol7::MAX_SKIN_ARRAY_SIZE, protocol7::MAX_SKIN_LENGTH> m_SkinNameInput;
|
||||
bool m_SkinPartListNeedsUpdate = false;
|
||||
void PopupConfirmDeleteSkin7();
|
||||
|
||||
// for map download popup
|
||||
|
@ -596,7 +598,6 @@ protected:
|
|||
void RenderSettingsTee(CUIRect MainView);
|
||||
void RenderSettingsTee7(CUIRect MainView);
|
||||
void RenderSettingsTeeCustom7(CUIRect MainView);
|
||||
void RenderSettingsTeeBasic7(CUIRect MainView);
|
||||
void RenderSkinSelection7(CUIRect MainView);
|
||||
void RenderSkinPartSelection7(CUIRect MainView);
|
||||
void RenderSettingsControls(CUIRect MainView);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <engine/engine.h>
|
||||
#include <engine/favorites.h>
|
||||
#include <engine/friends.h>
|
||||
#include <engine/gfx/image_manipulation.h>
|
||||
#include <engine/keys.h>
|
||||
#include <engine/serverbrowser.h>
|
||||
#include <engine/shared/config.h>
|
||||
|
@ -1822,12 +1823,8 @@ bool CMenus::PrintHighlighted(const char *pName, F &&PrintFn)
|
|||
|
||||
CTeeRenderInfo CMenus::GetTeeRenderInfo(vec2 Size, const char *pSkinName, bool CustomSkinColors, int CustomSkinColorBody, int CustomSkinColorFeet) const
|
||||
{
|
||||
const CSkin *pSkin = m_pClient->m_Skins.Find(pSkinName);
|
||||
|
||||
CTeeRenderInfo TeeInfo;
|
||||
TeeInfo.m_OriginalRenderSkin = pSkin->m_OriginalSkin;
|
||||
TeeInfo.m_ColorableRenderSkin = pSkin->m_ColorableSkin;
|
||||
TeeInfo.m_SkinMetrics = pSkin->m_Metrics;
|
||||
TeeInfo.Apply(m_pClient->m_Skins.Find(pSkinName));
|
||||
TeeInfo.m_CustomColoredSkin = CustomSkinColors;
|
||||
if(CustomSkinColors)
|
||||
{
|
||||
|
@ -1996,16 +1993,7 @@ void CMenus::LoadCommunityIconFinish(const char *pCommunityId, CImageInfo &Info,
|
|||
CommunityIcon.m_Sha256 = Sha256;
|
||||
CommunityIcon.m_OrgTexture = Graphics()->LoadTextureRaw(Info, 0, pCommunityId);
|
||||
|
||||
// create gray scale version
|
||||
unsigned char *pData = static_cast<unsigned char *>(Info.m_pData);
|
||||
const size_t Step = Info.PixelSize();
|
||||
for(size_t i = 0; i < Info.m_Width * Info.m_Height; i++)
|
||||
{
|
||||
int v = (pData[i * Step] + pData[i * Step + 1] + pData[i * Step + 2]) / 3;
|
||||
pData[i * Step] = v;
|
||||
pData[i * Step + 1] = v;
|
||||
pData[i * Step + 2] = v;
|
||||
}
|
||||
ConvertToGrayscale(Info);
|
||||
CommunityIcon.m_GreyTexture = Graphics()->LoadTextureRawMove(Info, 0, pCommunityId);
|
||||
|
||||
auto ExistingIcon = std::find_if(m_vCommunityIcons.begin(), m_vCommunityIcons.end(), [pCommunityId](const SCommunityIcon &Element) {
|
||||
|
|
|
@ -1397,22 +1397,10 @@ void CMenus::RenderDemoBrowserButtons(CUIRect ButtonsView, bool WasListboxItemAc
|
|||
|
||||
// quick search
|
||||
{
|
||||
SetIconMode(true);
|
||||
CUIRect DemoSearch, SearchIcon;
|
||||
CUIRect DemoSearch;
|
||||
ButtonBarTop.VSplitLeft(ButtonBarBottom.h * 21.0f, &DemoSearch, &ButtonBarTop);
|
||||
ButtonBarTop.VSplitLeft(ButtonBarTop.h / 2.0f, nullptr, &ButtonBarTop);
|
||||
DemoSearch.VSplitLeft(TextRender()->TextWidth(14.0f, FONT_ICON_MAGNIFYING_GLASS), &SearchIcon, &DemoSearch);
|
||||
DemoSearch.VSplitLeft(5.0f, nullptr, &DemoSearch);
|
||||
Ui()->DoLabel(&SearchIcon, FONT_ICON_MAGNIFYING_GLASS, 14.0f, TEXTALIGN_ML);
|
||||
SetIconMode(false);
|
||||
m_DemoSearchInput.SetEmptyText(Localize("Search"));
|
||||
|
||||
if(Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed())
|
||||
{
|
||||
Ui()->SetActiveItem(&m_DemoSearchInput);
|
||||
m_DemoSearchInput.SelectAll();
|
||||
}
|
||||
if(Ui()->DoClearableEditBox(&m_DemoSearchInput, &DemoSearch, 12.0f))
|
||||
if(Ui()->DoEditBox_Search(&m_DemoSearchInput, &DemoSearch, 14.0f, !Ui()->IsPopupOpen() && m_pClient->m_GameConsole.IsClosed()))
|
||||
{
|
||||
RefreshFilteredDemos();
|
||||
DemolistOnUpdate(false);
|
||||
|
|
|
@ -683,26 +683,15 @@ void CMenus::RenderServerControl(CUIRect MainView)
|
|||
|
||||
// render quick search
|
||||
CUIRect QuickSearch;
|
||||
Bottom.VSplitLeft(5.0f, 0, &Bottom);
|
||||
Bottom.VSplitLeft(5.0f, nullptr, &Bottom);
|
||||
Bottom.VSplitLeft(250.0f, &QuickSearch, &Bottom);
|
||||
TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
|
||||
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
|
||||
|
||||
Ui()->DoLabel(&QuickSearch, FONT_ICON_MAGNIFYING_GLASS, 14.0f, TEXTALIGN_ML);
|
||||
float SearchWidth = TextRender()->TextWidth(14.0f, FONT_ICON_MAGNIFYING_GLASS, -1, -1.0f);
|
||||
TextRender()->SetRenderFlags(0);
|
||||
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
|
||||
QuickSearch.VSplitLeft(SearchWidth, 0, &QuickSearch);
|
||||
QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch);
|
||||
|
||||
if(m_ControlPageOpening || (Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed()))
|
||||
if(m_ControlPageOpening)
|
||||
{
|
||||
Ui()->SetActiveItem(&m_FilterInput);
|
||||
m_ControlPageOpening = false;
|
||||
Ui()->SetActiveItem(&m_FilterInput);
|
||||
m_FilterInput.SelectAll();
|
||||
}
|
||||
m_FilterInput.SetEmptyText(Localize("Search"));
|
||||
Ui()->DoClearableEditBox(&m_FilterInput, &QuickSearch, 14.0f);
|
||||
Ui()->DoEditBox_Search(&m_FilterInput, &QuickSearch, 14.0f, !Ui()->IsPopupOpen() && m_pClient->m_GameConsole.IsClosed());
|
||||
|
||||
// call vote
|
||||
Bottom.VSplitRight(10.0f, &Bottom, 0);
|
||||
|
|
|
@ -257,7 +257,7 @@ void CMenus::SetNeedSendInfo()
|
|||
|
||||
void CMenus::RenderSettingsPlayer(CUIRect MainView)
|
||||
{
|
||||
CUIRect TabBar, PlayerTab, DummyTab, ChangeInfo, QuickSearch, QuickSearchClearButton;
|
||||
CUIRect TabBar, PlayerTab, DummyTab, ChangeInfo, QuickSearch;
|
||||
MainView.HSplitTop(20.0f, &TabBar, &MainView);
|
||||
TabBar.VSplitMid(&TabBar, &ChangeInfo, 20.f);
|
||||
TabBar.VSplitMid(&PlayerTab, &DummyTab);
|
||||
|
@ -340,7 +340,10 @@ void CMenus::RenderSettingsPlayer(CUIRect MainView)
|
|||
}
|
||||
|
||||
MainView.HSplitTop(10.0f, nullptr, &MainView);
|
||||
MainView.HSplitBottom(25.0f, &MainView, &QuickSearch);
|
||||
MainView.HSplitBottom(20.0f, &MainView, &QuickSearch);
|
||||
MainView.HSplitBottom(5.0f, &MainView, nullptr);
|
||||
QuickSearch.VSplitLeft(220.0f, &QuickSearch, nullptr);
|
||||
|
||||
int OldSelected = -1;
|
||||
static CListBox s_ListBox;
|
||||
s_ListBox.DoStart(48.0f, vpFilteredFlags.size(), 10, 3, OldSelected, &MainView);
|
||||
|
@ -378,30 +381,7 @@ void CMenus::RenderSettingsPlayer(CUIRect MainView)
|
|||
SetNeedSendInfo();
|
||||
}
|
||||
|
||||
// render quick search
|
||||
QuickSearch.VSplitLeft(240.0f, &QuickSearch, nullptr);
|
||||
QuickSearch.HSplitTop(5.0f, nullptr, &QuickSearch);
|
||||
|
||||
TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
|
||||
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
|
||||
Ui()->DoLabel(&QuickSearch, FONT_ICON_MAGNIFYING_GLASS, 14.0f, TEXTALIGN_ML);
|
||||
TextRender()->SetRenderFlags(0);
|
||||
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
|
||||
|
||||
float SearchWidth = TextRender()->TextWidth(14.0f, FONT_ICON_MAGNIFYING_GLASS, -1, -1.0f);
|
||||
QuickSearch.VSplitLeft(SearchWidth - 1.5f, nullptr, &QuickSearch);
|
||||
QuickSearch.VSplitLeft(5.0f, nullptr, &QuickSearch);
|
||||
QuickSearch.VSplitLeft(QuickSearch.w - 10.0f, &QuickSearch, &QuickSearchClearButton);
|
||||
|
||||
TextRender()->SetRenderFlags(0);
|
||||
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
|
||||
if(Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed())
|
||||
{
|
||||
Ui()->SetActiveItem(&s_FlagFilterInput);
|
||||
s_FlagFilterInput.SelectAll();
|
||||
}
|
||||
s_FlagFilterInput.SetEmptyText(Localize("Search"));
|
||||
Ui()->DoClearableEditBox(&s_FlagFilterInput, &QuickSearch, 14.0f);
|
||||
Ui()->DoEditBox_Search(&s_FlagFilterInput, &QuickSearch, 14.0f, !Ui()->IsPopupOpen() && m_pClient->m_GameConsole.IsClosed());
|
||||
}
|
||||
|
||||
struct CUISkin
|
||||
|
@ -653,11 +633,8 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
|
|||
|
||||
// Note: get the skin info after the settings buttons, because they can trigger a refresh
|
||||
// which invalidates the skin.
|
||||
const CSkin *pSkin = m_pClient->m_Skins.Find(pSkinName);
|
||||
CTeeRenderInfo OwnSkinInfo;
|
||||
OwnSkinInfo.m_OriginalRenderSkin = pSkin->m_OriginalSkin;
|
||||
OwnSkinInfo.m_ColorableRenderSkin = pSkin->m_ColorableSkin;
|
||||
OwnSkinInfo.m_SkinMetrics = pSkin->m_Metrics;
|
||||
OwnSkinInfo.Apply(m_pClient->m_Skins.Find(pSkinName));
|
||||
OwnSkinInfo.m_CustomColoredSkin = *pUseCustomColor;
|
||||
if(*pUseCustomColor)
|
||||
{
|
||||
|
@ -770,8 +747,8 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
|
|||
CUIRect QuickSearch, DatabaseButton, DirectoryButton, RefreshButton;
|
||||
MainView.HSplitBottom(20.0f, &MainView, &QuickSearch);
|
||||
MainView.HSplitBottom(5.0f, &MainView, nullptr);
|
||||
QuickSearch.VSplitLeft(240.0f, &QuickSearch, &DatabaseButton);
|
||||
QuickSearch.VSplitRight(10.0f, &QuickSearch, nullptr);
|
||||
QuickSearch.VSplitLeft(220.0f, &QuickSearch, &DatabaseButton);
|
||||
DatabaseButton.VSplitLeft(10.0f, nullptr, &DatabaseButton);
|
||||
DatabaseButton.VSplitLeft(150.0f, &DatabaseButton, &DirectoryButton);
|
||||
DirectoryButton.VSplitRight(175.0f, nullptr, &DirectoryButton);
|
||||
DirectoryButton.VSplitRight(25.0f, &DirectoryButton, &RefreshButton);
|
||||
|
@ -904,24 +881,10 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
|
|||
SetNeedSendInfo();
|
||||
}
|
||||
|
||||
// Quick search
|
||||
static CLineInput s_SkinFilterInput(g_Config.m_ClSkinFilterString, sizeof(g_Config.m_ClSkinFilterString));
|
||||
if(Ui()->DoEditBox_Search(&s_SkinFilterInput, &QuickSearch, 14.0f, !Ui()->IsPopupOpen() && m_pClient->m_GameConsole.IsClosed()))
|
||||
{
|
||||
TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
|
||||
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
|
||||
Ui()->DoLabel(&QuickSearch, FONT_ICON_MAGNIFYING_GLASS, 14.0f, TEXTALIGN_ML);
|
||||
float SearchWidth = TextRender()->TextWidth(14.0f, FONT_ICON_MAGNIFYING_GLASS, -1, -1.0f);
|
||||
TextRender()->SetRenderFlags(0);
|
||||
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
|
||||
QuickSearch.VSplitLeft(SearchWidth + 5.0f, nullptr, &QuickSearch);
|
||||
static CLineInput s_SkinFilterInput(g_Config.m_ClSkinFilterString, sizeof(g_Config.m_ClSkinFilterString));
|
||||
if(Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed())
|
||||
{
|
||||
Ui()->SetActiveItem(&s_SkinFilterInput);
|
||||
s_SkinFilterInput.SelectAll();
|
||||
}
|
||||
s_SkinFilterInput.SetEmptyText(Localize("Search"));
|
||||
if(Ui()->DoClearableEditBox(&s_SkinFilterInput, &QuickSearch, 14.0f))
|
||||
m_SkinListNeedsUpdate = true;
|
||||
m_SkinListNeedsUpdate = true;
|
||||
}
|
||||
|
||||
static CButtonContainer s_SkinDatabaseButton;
|
||||
|
|
|
@ -31,246 +31,62 @@
|
|||
|
||||
#include <vector>
|
||||
|
||||
using namespace FontIcons;
|
||||
|
||||
void CMenus::RenderSettingsTee7(CUIRect MainView)
|
||||
{
|
||||
static bool s_CustomSkinMenu = false;
|
||||
// static int s_PlayerCountry = 0;
|
||||
// static char s_aPlayerName[64] = {0};
|
||||
// static char s_aPlayerClan[64] = {0};
|
||||
CUIRect SkinPreview, NormalSkinPreview, RedTeamSkinPreview, BlueTeamSkinPreview, Buttons, QuickSearch, DirectoryButton, RefreshButton, SaveDeleteButton, TabBars, TabBar, LeftTab, RightTab;
|
||||
MainView.HSplitBottom(20.0f, &MainView, &Buttons);
|
||||
MainView.HSplitBottom(5.0f, &MainView, nullptr);
|
||||
Buttons.VSplitRight(25.0f, &Buttons, &RefreshButton);
|
||||
Buttons.VSplitRight(10.0f, &Buttons, nullptr);
|
||||
Buttons.VSplitRight(140.0f, &Buttons, &DirectoryButton);
|
||||
Buttons.VSplitLeft(220.0f, &QuickSearch, &Buttons);
|
||||
Buttons.VSplitLeft(10.0f, nullptr, &Buttons);
|
||||
Buttons.VSplitLeft(120.0f, &SaveDeleteButton, &Buttons);
|
||||
MainView.HSplitTop(50.0f, &TabBars, &MainView);
|
||||
MainView.HSplitTop(10.0f, nullptr, &MainView);
|
||||
TabBars.VSplitMid(&TabBars, &SkinPreview, 20.0f);
|
||||
|
||||
// if(m_pClient->m_IdentityState < 0)
|
||||
// {
|
||||
// s_PlayerCountry = Config()->m_PlayerCountry;
|
||||
// str_copy(s_aPlayerName, Config()->m_PlayerName, sizeof(s_aPlayerName));
|
||||
// str_copy(s_aPlayerClan, Config()->m_PlayerClan, sizeof(s_aPlayerClan));
|
||||
// m_pClient->m_IdentityState = 0;
|
||||
// }
|
||||
TabBars.HSplitTop(20.0f, &TabBar, &TabBars);
|
||||
TabBar.VSplitMid(&LeftTab, &RightTab);
|
||||
TabBars.HSplitTop(10.0f, nullptr, &TabBars);
|
||||
|
||||
CUIRect Label, TopView, BottomView, Left, Right;
|
||||
SkinPreview.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f), IGraphics::CORNER_ALL, 5.0f);
|
||||
SkinPreview.VMargin(10.0f, &SkinPreview);
|
||||
SkinPreview.VSplitRight(50.0f, &SkinPreview, &BlueTeamSkinPreview);
|
||||
SkinPreview.VSplitRight(10.0f, &SkinPreview, nullptr);
|
||||
SkinPreview.VSplitRight(50.0f, &SkinPreview, &RedTeamSkinPreview);
|
||||
SkinPreview.VSplitRight(10.0f, &SkinPreview, nullptr);
|
||||
SkinPreview.VSplitRight(50.0f, &SkinPreview, &NormalSkinPreview);
|
||||
SkinPreview.VSplitRight(10.0f, &SkinPreview, nullptr);
|
||||
|
||||
// cut view
|
||||
MainView.HSplitBottom(40.0f, &MainView, &BottomView);
|
||||
BottomView.HSplitTop(20.f, 0, &BottomView);
|
||||
|
||||
CUIRect QuickSearch, DirectoryButton, Buttons;
|
||||
CUIRect ButtonLeft, ButtonMiddle, ButtonRight;
|
||||
|
||||
BottomView.VSplitMid(&QuickSearch, &Buttons, 10.0f);
|
||||
QuickSearch.VSplitLeft(240.0f, &QuickSearch, &DirectoryButton);
|
||||
QuickSearch.VSplitRight(10.0f, &QuickSearch, nullptr);
|
||||
|
||||
const float ButtonSize = Buttons.w / 3;
|
||||
Buttons.VSplitLeft(ButtonSize, &ButtonLeft, &Buttons);
|
||||
Buttons.VSplitLeft(ButtonSize, &ButtonMiddle, &Buttons);
|
||||
Buttons.VSplitLeft(ButtonSize, &ButtonRight, &Buttons);
|
||||
|
||||
// render skin preview background
|
||||
const float SpacingH = 2.0f;
|
||||
const float SpacingW = 3.0f;
|
||||
const float ButtonHeight = 20.0f;
|
||||
const float SkinHeight = 50.0f;
|
||||
const float BackgroundHeight = (ButtonHeight + SpacingH) + SkinHeight * 2;
|
||||
|
||||
MainView.HSplitTop(20.0f, 0, &MainView);
|
||||
MainView.HSplitTop(BackgroundHeight, &TopView, &MainView);
|
||||
TopView.VSplitMid(&Left, &Right, 3.0f);
|
||||
Left.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F);
|
||||
Right.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F);
|
||||
|
||||
Left.HSplitTop(ButtonHeight, &Label, &Left);
|
||||
Ui()->DoLabel(&Label, Localize("Tee"), ButtonHeight * CUi::ms_FontmodHeight * 0.8f, TEXTALIGN_MC);
|
||||
|
||||
// Preview
|
||||
static CButtonContainer s_PlayerTabButton;
|
||||
if(DoButton_MenuTab(&s_PlayerTabButton, Localize("Player"), !m_Dummy, &LeftTab, IGraphics::CORNER_L, nullptr, nullptr, nullptr, nullptr, 4.0f))
|
||||
{
|
||||
CUIRect Top, Bottom, TeeLeft, TeeRight;
|
||||
|
||||
Left.HSplitTop(SpacingH, 0, &Left);
|
||||
Left.HSplitTop(SkinHeight * 2, &Top, &Left);
|
||||
|
||||
// split the menu in 2 parts
|
||||
Top.HSplitMid(&Top, &Bottom, SpacingH);
|
||||
|
||||
// handle left
|
||||
|
||||
// validate skin parts for solo mode
|
||||
CTeeRenderInfo OwnSkinInfo;
|
||||
OwnSkinInfo.m_Size = 50.0f;
|
||||
|
||||
char aSkinParts[protocol7::NUM_SKINPARTS][protocol7::MAX_SKIN_ARRAY_SIZE];
|
||||
char *apSkinPartsPtr[protocol7::NUM_SKINPARTS];
|
||||
int aUCCVars[protocol7::NUM_SKINPARTS];
|
||||
int aColorVars[protocol7::NUM_SKINPARTS];
|
||||
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
|
||||
{
|
||||
str_copy(aSkinParts[Part], CSkins7::ms_apSkinVariables[(int)m_Dummy][Part], protocol7::MAX_SKIN_ARRAY_SIZE);
|
||||
apSkinPartsPtr[Part] = aSkinParts[Part];
|
||||
aUCCVars[Part] = *CSkins7::ms_apUCCVariables[(int)m_Dummy][Part];
|
||||
aColorVars[Part] = *CSkins7::ms_apColorVariables[(int)m_Dummy][Part];
|
||||
}
|
||||
|
||||
m_pClient->m_Skins7.ValidateSkinParts(apSkinPartsPtr, aUCCVars, aColorVars, 0);
|
||||
|
||||
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
|
||||
{
|
||||
int SkinPart = m_pClient->m_Skins7.FindSkinPart(Part, apSkinPartsPtr[Part], false);
|
||||
const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.GetSkinPart(Part, SkinPart);
|
||||
if(aUCCVars[Part])
|
||||
{
|
||||
OwnSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aTextures[Part] = pSkinPart->m_ColorTexture;
|
||||
OwnSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aColors[Part] = m_pClient->m_Skins7.GetColor(aColorVars[Part], Part == protocol7::SKINPART_MARKING);
|
||||
}
|
||||
else
|
||||
{
|
||||
OwnSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aTextures[Part] = pSkinPart->m_OrgTexture;
|
||||
OwnSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aColors[Part] = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// draw preview
|
||||
Top.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F);
|
||||
|
||||
Top.VSplitLeft(Top.w / 3.0f + SpacingW / 2.0f, &Label, &Top);
|
||||
Label.y += 17.0f;
|
||||
Ui()->DoLabel(&Label, Localize("Normal:"), ButtonHeight * CUi::ms_FontmodHeight * 0.8f, TEXTALIGN_CENTER);
|
||||
|
||||
Top.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F);
|
||||
|
||||
{
|
||||
// interactive tee: tee looking towards cursor, and it is happy when you touch it
|
||||
const vec2 TeePosition = vec2(Top.x + Top.w / 2.0f, Top.y + Top.h / 2.0f + 6.0f);
|
||||
const vec2 DeltaPosition = Ui()->MousePos() - TeePosition;
|
||||
const float Distance = length(DeltaPosition);
|
||||
const float InteractionDistance = 20.0f;
|
||||
const vec2 TeeDirection = Distance < InteractionDistance ? normalize(vec2(DeltaPosition.x, maximum(DeltaPosition.y, 0.5f))) : normalize(DeltaPosition);
|
||||
const int TeeEmote = Distance < InteractionDistance ? EMOTE_HAPPY : EMOTE_NORMAL;
|
||||
RenderTools()->RenderTee(CAnimState::GetIdle(), &OwnSkinInfo, TeeEmote, TeeDirection, TeePosition);
|
||||
static char s_InteractiveTeeButtonId;
|
||||
if(Distance < InteractionDistance && Ui()->DoButtonLogic(&s_InteractiveTeeButtonId, 0, &Top))
|
||||
{
|
||||
m_pClient->m_Sounds.Play(CSounds::CHN_GUI, SOUND_PLAYER_SPAWN, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// handle right (team skins)
|
||||
|
||||
// validate skin parts for team game mode
|
||||
CTeeRenderInfo TeamSkinInfo = OwnSkinInfo;
|
||||
|
||||
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
|
||||
{
|
||||
str_copy(aSkinParts[Part], CSkins7::ms_apSkinVariables[(int)m_Dummy][Part], protocol7::MAX_SKIN_ARRAY_SIZE);
|
||||
apSkinPartsPtr[Part] = aSkinParts[Part];
|
||||
aUCCVars[Part] = *CSkins7::ms_apUCCVariables[(int)m_Dummy][Part];
|
||||
aColorVars[Part] = *CSkins7::ms_apColorVariables[(int)m_Dummy][Part];
|
||||
}
|
||||
|
||||
m_pClient->m_Skins7.ValidateSkinParts(apSkinPartsPtr, aUCCVars, aColorVars, GAMEFLAG_TEAMS);
|
||||
|
||||
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
|
||||
{
|
||||
int SkinPart = m_pClient->m_Skins7.FindSkinPart(Part, apSkinPartsPtr[Part], false);
|
||||
const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.GetSkinPart(Part, SkinPart);
|
||||
if(aUCCVars[Part])
|
||||
{
|
||||
TeamSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aTextures[Part] = pSkinPart->m_ColorTexture;
|
||||
TeamSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aColors[Part] = m_pClient->m_Skins7.GetColor(aColorVars[Part], Part == protocol7::SKINPART_MARKING);
|
||||
}
|
||||
else
|
||||
{
|
||||
TeamSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aTextures[Part] = pSkinPart->m_OrgTexture;
|
||||
TeamSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aColors[Part] = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// draw preview
|
||||
Bottom.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F);
|
||||
|
||||
Bottom.VSplitLeft(Bottom.w / 3.0f + SpacingW / 2.0f, &Label, &Bottom);
|
||||
Label.y += 17.0f;
|
||||
Ui()->DoLabel(&Label, Localize("Team:"), ButtonHeight * CUi::ms_FontmodHeight * 0.8f, TEXTALIGN_CENTER);
|
||||
|
||||
Bottom.VSplitMid(&TeeLeft, &TeeRight, SpacingW);
|
||||
|
||||
TeeLeft.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F);
|
||||
|
||||
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
|
||||
{
|
||||
ColorRGBA TeamColor = m_pClient->m_Skins7.GetTeamColor(aUCCVars[Part], aColorVars[Part], TEAM_RED, Part);
|
||||
TeamSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aColors[Part] = TeamColor;
|
||||
}
|
||||
RenderTools()->RenderTee(CAnimState::GetIdle(), &TeamSkinInfo, 0, vec2(1, 0), vec2(TeeLeft.x + TeeLeft.w / 2.0f, TeeLeft.y + TeeLeft.h / 2.0f + 6.0f));
|
||||
|
||||
TeeRight.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F);
|
||||
|
||||
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
|
||||
{
|
||||
ColorRGBA TeamColor = m_pClient->m_Skins7.GetTeamColor(aUCCVars[Part], aColorVars[Part], TEAM_BLUE, Part);
|
||||
TeamSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aColors[Part] = TeamColor;
|
||||
}
|
||||
RenderTools()->RenderTee(CAnimState::GetIdle(), &TeamSkinInfo, 0, vec2(-1, 0), vec2(TeeRight.x + TeeRight.w / 2.0f, TeeRight.y + TeeRight.h / 2.0f + 6.0f));
|
||||
m_Dummy = false;
|
||||
}
|
||||
|
||||
Right.HSplitTop(ButtonHeight, &Label, &Right);
|
||||
Ui()->DoLabel(&Label, Localize("Settings"), ButtonHeight * CUi::ms_FontmodHeight * 0.8f, TEXTALIGN_MC);
|
||||
|
||||
// Settings
|
||||
static CButtonContainer s_DummyTabButton;
|
||||
if(DoButton_MenuTab(&s_DummyTabButton, Localize("Dummy"), m_Dummy, &RightTab, IGraphics::CORNER_R, nullptr, nullptr, nullptr, nullptr, 4.0f))
|
||||
{
|
||||
CUIRect Top, Bottom, Dummy, DummyLabel;
|
||||
Right.HSplitTop(SpacingH, 0, &Right);
|
||||
Right.HSplitMid(&Top, &Bottom, SpacingH);
|
||||
|
||||
Right.HSplitTop(20.0f, &Dummy, &Right);
|
||||
Dummy.HSplitTop(20.0f, &DummyLabel, &Dummy);
|
||||
|
||||
if(DoButton_CheckBox(&m_Dummy, Localize("Dummy settings"), m_Dummy, &DummyLabel))
|
||||
{
|
||||
m_Dummy ^= 1;
|
||||
}
|
||||
GameClient()->m_Tooltips.DoToolTip(&m_Dummy, &DummyLabel, Localize("Toggle to edit your dummy settings"));
|
||||
m_Dummy = true;
|
||||
}
|
||||
|
||||
MainView.HSplitTop(10.0f, 0, &MainView);
|
||||
TabBars.HSplitTop(20.0f, &TabBar, &TabBars);
|
||||
TabBar.VSplitMid(&LeftTab, &RightTab);
|
||||
|
||||
if(s_CustomSkinMenu)
|
||||
RenderSettingsTeeCustom7(MainView);
|
||||
else
|
||||
RenderSettingsTeeBasic7(MainView);
|
||||
|
||||
// bottom buttons
|
||||
if(s_CustomSkinMenu)
|
||||
static CButtonContainer s_BasicTabButton;
|
||||
if(DoButton_MenuTab(&s_BasicTabButton, Localize("Basic"), !m_CustomSkinMenu, &LeftTab, IGraphics::CORNER_L, nullptr, nullptr, nullptr, nullptr, 4.0f))
|
||||
{
|
||||
static CButtonContainer s_CustomSkinSaveButton;
|
||||
if(DoButton_Menu(&s_CustomSkinSaveButton, Localize("Save"), 0, &ButtonLeft))
|
||||
{
|
||||
m_Popup = POPUP_SAVE_SKIN;
|
||||
m_SkinNameInput.SelectAll();
|
||||
Ui()->SetActiveItem(&m_SkinNameInput);
|
||||
}
|
||||
|
||||
static CButtonContainer s_RandomizeSkinButton;
|
||||
if(DoButton_Menu(&s_RandomizeSkinButton, Localize("Randomize"), 0, &ButtonMiddle))
|
||||
{
|
||||
m_pClient->m_Skins7.RandomizeSkin(m_Dummy);
|
||||
Config()->m_ClPlayer7Skin[0] = 0;
|
||||
SetNeedSendInfo();
|
||||
}
|
||||
}
|
||||
else if(m_pSelectedSkin && (m_pSelectedSkin->m_Flags & CSkins7::SKINFLAG_STANDARD) == 0)
|
||||
{
|
||||
static CButtonContainer s_CustomSkinDeleteButton;
|
||||
if(DoButton_Menu(&s_CustomSkinDeleteButton, Localize("Delete"), 0, &ButtonMiddle) || Ui()->ConsumeHotkey(CUi::HOTKEY_DELETE))
|
||||
{
|
||||
char aBuf[128];
|
||||
str_format(aBuf, sizeof(aBuf), Localize("Are you sure that you want to delete '%s'?"), m_pSelectedSkin->m_aName);
|
||||
PopupConfirm(Localize("Delete skin"), aBuf, Localize("Yes"), Localize("No"), &CMenus::PopupConfirmDeleteSkin7);
|
||||
}
|
||||
m_CustomSkinMenu = false;
|
||||
}
|
||||
|
||||
static CButtonContainer s_CustomSwitchButton;
|
||||
if(DoButton_Menu(&s_CustomSwitchButton, s_CustomSkinMenu ? Localize("Basic") : Localize("Custom"), 0, &ButtonRight))
|
||||
static CButtonContainer s_CustomTabButton;
|
||||
if(DoButton_MenuTab(&s_CustomTabButton, Localize("Custom"), m_CustomSkinMenu, &RightTab, IGraphics::CORNER_R, nullptr, nullptr, nullptr, nullptr, 4.0f))
|
||||
{
|
||||
s_CustomSkinMenu = !s_CustomSkinMenu;
|
||||
if(s_CustomSkinMenu && m_pSelectedSkin)
|
||||
m_CustomSkinMenu = true;
|
||||
if(m_CustomSkinMenu && m_pSelectedSkin)
|
||||
{
|
||||
if(m_pSelectedSkin->m_Flags & CSkins7::SKINFLAG_STANDARD)
|
||||
{
|
||||
|
@ -282,35 +98,151 @@ void CMenus::RenderSettingsTee7(CUIRect MainView)
|
|||
}
|
||||
}
|
||||
|
||||
// Quick search
|
||||
// validate skin parts for solo mode
|
||||
char aSkinParts[protocol7::NUM_SKINPARTS][protocol7::MAX_SKIN_ARRAY_SIZE];
|
||||
char *apSkinPartsPtr[protocol7::NUM_SKINPARTS];
|
||||
int aUCCVars[protocol7::NUM_SKINPARTS];
|
||||
int aColorVars[protocol7::NUM_SKINPARTS];
|
||||
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
|
||||
{
|
||||
TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
|
||||
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
|
||||
Ui()->DoLabel(&QuickSearch, FontIcons::FONT_ICON_MAGNIFYING_GLASS, 14.0f, TEXTALIGN_ML);
|
||||
float SearchWidth = TextRender()->TextWidth(14.0f, FontIcons::FONT_ICON_MAGNIFYING_GLASS, -1, -1.0f);
|
||||
TextRender()->SetRenderFlags(0);
|
||||
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
|
||||
QuickSearch.VSplitLeft(SearchWidth + 5.0f, nullptr, &QuickSearch);
|
||||
static CLineInput s_SkinFilterInput(g_Config.m_ClSkinFilterString, sizeof(g_Config.m_ClSkinFilterString));
|
||||
if(Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed())
|
||||
str_copy(aSkinParts[Part], CSkins7::ms_apSkinVariables[(int)m_Dummy][Part], protocol7::MAX_SKIN_ARRAY_SIZE);
|
||||
apSkinPartsPtr[Part] = aSkinParts[Part];
|
||||
aUCCVars[Part] = *CSkins7::ms_apUCCVariables[(int)m_Dummy][Part];
|
||||
aColorVars[Part] = *CSkins7::ms_apColorVariables[(int)m_Dummy][Part];
|
||||
}
|
||||
m_pClient->m_Skins7.ValidateSkinParts(apSkinPartsPtr, aUCCVars, aColorVars, 0);
|
||||
|
||||
CTeeRenderInfo OwnSkinInfo;
|
||||
OwnSkinInfo.m_Size = 50.0f;
|
||||
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
|
||||
{
|
||||
int SkinPart = m_pClient->m_Skins7.FindSkinPart(Part, apSkinPartsPtr[Part], false);
|
||||
const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.GetSkinPart(Part, SkinPart);
|
||||
if(aUCCVars[Part])
|
||||
{
|
||||
Ui()->SetActiveItem(&s_SkinFilterInput);
|
||||
s_SkinFilterInput.SelectAll();
|
||||
OwnSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aTextures[Part] = pSkinPart->m_ColorTexture;
|
||||
OwnSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aColors[Part] = m_pClient->m_Skins7.GetColor(aColorVars[Part], Part == protocol7::SKINPART_MARKING);
|
||||
}
|
||||
s_SkinFilterInput.SetEmptyText(Localize("Search"));
|
||||
if(Ui()->DoClearableEditBox(&s_SkinFilterInput, &QuickSearch, 14.0f))
|
||||
m_SkinListNeedsUpdate = true;
|
||||
else
|
||||
{
|
||||
OwnSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aTextures[Part] = pSkinPart->m_OrgTexture;
|
||||
OwnSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aColors[Part] = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
char aBuf[128 + IO_MAX_PATH_LENGTH];
|
||||
str_format(aBuf, sizeof(aBuf), "%s:", Localize("Your skin"));
|
||||
Ui()->DoLabel(&SkinPreview, aBuf, 14.0f, TEXTALIGN_ML);
|
||||
|
||||
{
|
||||
// interactive tee: tee looking towards cursor, and it is happy when you touch it
|
||||
const vec2 TeePosition = NormalSkinPreview.Center() + vec2(0.0f, 6.0f);
|
||||
const vec2 DeltaPosition = Ui()->MousePos() - TeePosition;
|
||||
const float Distance = length(DeltaPosition);
|
||||
const float InteractionDistance = 20.0f;
|
||||
const vec2 TeeDirection = Distance < InteractionDistance ? normalize(vec2(DeltaPosition.x, maximum(DeltaPosition.y, 0.5f))) : normalize(DeltaPosition);
|
||||
const int TeeEmote = Distance < InteractionDistance ? EMOTE_HAPPY : EMOTE_NORMAL;
|
||||
RenderTools()->RenderTee(CAnimState::GetIdle(), &OwnSkinInfo, TeeEmote, TeeDirection, TeePosition);
|
||||
static char s_InteractiveTeeButtonId;
|
||||
if(Distance < InteractionDistance && Ui()->DoButtonLogic(&s_InteractiveTeeButtonId, 0, &NormalSkinPreview))
|
||||
{
|
||||
m_pClient->m_Sounds.Play(CSounds::CHN_GUI, SOUND_PLAYER_SPAWN, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// validate skin parts for team game mode
|
||||
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
|
||||
{
|
||||
str_copy(aSkinParts[Part], CSkins7::ms_apSkinVariables[(int)m_Dummy][Part], protocol7::MAX_SKIN_ARRAY_SIZE);
|
||||
apSkinPartsPtr[Part] = aSkinParts[Part];
|
||||
aUCCVars[Part] = *CSkins7::ms_apUCCVariables[(int)m_Dummy][Part];
|
||||
aColorVars[Part] = *CSkins7::ms_apColorVariables[(int)m_Dummy][Part];
|
||||
}
|
||||
m_pClient->m_Skins7.ValidateSkinParts(apSkinPartsPtr, aUCCVars, aColorVars, GAMEFLAG_TEAMS);
|
||||
|
||||
CTeeRenderInfo TeamSkinInfo = OwnSkinInfo;
|
||||
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
|
||||
{
|
||||
int SkinPart = m_pClient->m_Skins7.FindSkinPart(Part, apSkinPartsPtr[Part], false);
|
||||
const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.GetSkinPart(Part, SkinPart);
|
||||
if(aUCCVars[Part])
|
||||
{
|
||||
TeamSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aTextures[Part] = pSkinPart->m_ColorTexture;
|
||||
TeamSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aColors[Part] = m_pClient->m_Skins7.GetColor(aColorVars[Part], Part == protocol7::SKINPART_MARKING);
|
||||
}
|
||||
else
|
||||
{
|
||||
TeamSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aTextures[Part] = pSkinPart->m_OrgTexture;
|
||||
TeamSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aColors[Part] = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
|
||||
{
|
||||
TeamSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aColors[Part] = m_pClient->m_Skins7.GetTeamColor(aUCCVars[Part], aColorVars[Part], TEAM_RED, Part);
|
||||
}
|
||||
RenderTools()->RenderTee(CAnimState::GetIdle(), &TeamSkinInfo, 0, vec2(1, 0), RedTeamSkinPreview.Center() + vec2(0.0f, 6.0f));
|
||||
|
||||
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
|
||||
{
|
||||
TeamSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aColors[Part] = m_pClient->m_Skins7.GetTeamColor(aUCCVars[Part], aColorVars[Part], TEAM_BLUE, Part);
|
||||
}
|
||||
RenderTools()->RenderTee(CAnimState::GetIdle(), &TeamSkinInfo, 0, vec2(-1, 0), BlueTeamSkinPreview.Center() + vec2(0.0f, 6.0f));
|
||||
|
||||
if(m_CustomSkinMenu)
|
||||
RenderSettingsTeeCustom7(MainView);
|
||||
else
|
||||
RenderSkinSelection7(MainView);
|
||||
|
||||
if(m_CustomSkinMenu)
|
||||
{
|
||||
static CButtonContainer s_CustomSkinSaveButton;
|
||||
if(DoButton_Menu(&s_CustomSkinSaveButton, Localize("Save"), 0, &SaveDeleteButton))
|
||||
{
|
||||
m_Popup = POPUP_SAVE_SKIN;
|
||||
m_SkinNameInput.SelectAll();
|
||||
Ui()->SetActiveItem(&m_SkinNameInput);
|
||||
}
|
||||
}
|
||||
else if(m_pSelectedSkin && (m_pSelectedSkin->m_Flags & CSkins7::SKINFLAG_STANDARD) == 0)
|
||||
{
|
||||
static CButtonContainer s_CustomSkinDeleteButton;
|
||||
if(DoButton_Menu(&s_CustomSkinDeleteButton, Localize("Delete"), 0, &SaveDeleteButton) || Ui()->ConsumeHotkey(CUi::HOTKEY_DELETE))
|
||||
{
|
||||
str_format(aBuf, sizeof(aBuf), Localize("Are you sure that you want to delete '%s'?"), m_pSelectedSkin->m_aName);
|
||||
PopupConfirm(Localize("Delete skin"), aBuf, Localize("Yes"), Localize("No"), &CMenus::PopupConfirmDeleteSkin7);
|
||||
}
|
||||
}
|
||||
|
||||
static CLineInput s_SkinFilterInput(g_Config.m_ClSkinFilterString, sizeof(g_Config.m_ClSkinFilterString));
|
||||
if(Ui()->DoEditBox_Search(&s_SkinFilterInput, &QuickSearch, 14.0f, !Ui()->IsPopupOpen() && m_pClient->m_GameConsole.IsClosed()))
|
||||
{
|
||||
m_SkinListNeedsUpdate = true;
|
||||
m_SkinPartListNeedsUpdate = true;
|
||||
}
|
||||
|
||||
static CButtonContainer s_DirectoryButton;
|
||||
if(DoButton_Menu(&s_DirectoryButton, Localize("Skins directory"), 0, &DirectoryButton))
|
||||
{
|
||||
char aBuf[128 + IO_MAX_PATH_LENGTH];
|
||||
Storage()->GetCompletePath(IStorage::TYPE_SAVE, "skins7", aBuf, sizeof(aBuf));
|
||||
Storage()->CreateFolder("skins7", IStorage::TYPE_SAVE);
|
||||
Client()->ViewFile(aBuf);
|
||||
}
|
||||
GameClient()->m_Tooltips.DoToolTip(&s_DirectoryButton, &DirectoryButton, Localize("Open the directory to add custom skins"));
|
||||
|
||||
TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
|
||||
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
|
||||
static CButtonContainer s_SkinRefreshButton;
|
||||
if(DoButton_Menu(&s_SkinRefreshButton, FONT_ICON_ARROW_ROTATE_RIGHT, 0, &RefreshButton) ||
|
||||
(!Ui()->IsPopupOpen() && m_pClient->m_GameConsole.IsClosed() && (Input()->KeyPress(KEY_F5) || (Input()->ModifierIsPressed() && Input()->KeyPress(KEY_R)))))
|
||||
{
|
||||
// reset render flags for possible loading screen
|
||||
TextRender()->SetRenderFlags(0);
|
||||
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
|
||||
// TODO: m_pClient->RefreshSkins();
|
||||
}
|
||||
TextRender()->SetRenderFlags(0);
|
||||
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
|
||||
}
|
||||
|
||||
void CMenus::PopupConfirmDeleteSkin7()
|
||||
|
@ -325,51 +257,34 @@ void CMenus::PopupConfirmDeleteSkin7()
|
|||
m_pSelectedSkin = nullptr;
|
||||
}
|
||||
|
||||
void CMenus::RenderSettingsTeeBasic7(CUIRect MainView)
|
||||
{
|
||||
RenderSkinSelection7(MainView); // yes thats all here ^^
|
||||
}
|
||||
|
||||
void CMenus::RenderSettingsTeeCustom7(CUIRect MainView)
|
||||
{
|
||||
CUIRect Label, Patterns, Button, Left, Right;
|
||||
CUIRect ButtonBar, SkinPartSelection, CustomColors;
|
||||
|
||||
// render skin preview background
|
||||
float SpacingH = 2.0f;
|
||||
float SpacingW = 3.0f;
|
||||
float ButtonHeight = 20.0f;
|
||||
MainView.HSplitTop(20.0f, &ButtonBar, &MainView);
|
||||
MainView.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f), IGraphics::CORNER_B, 5.0f);
|
||||
MainView.VSplitMid(&SkinPartSelection, &CustomColors, 10.0f);
|
||||
CustomColors.Margin(5.0f, &CustomColors);
|
||||
CUIRect CustomColorsButton, RandomSkinButton;
|
||||
CustomColors.HSplitTop(20.0f, &CustomColorsButton, &CustomColors);
|
||||
CustomColorsButton.VSplitRight(30.0f, &CustomColorsButton, &RandomSkinButton);
|
||||
CustomColorsButton.VSplitRight(20.0f, &CustomColorsButton, nullptr);
|
||||
|
||||
MainView.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F);
|
||||
const float ButtonWidth = ButtonBar.w / protocol7::NUM_SKINPARTS;
|
||||
|
||||
MainView.HSplitTop(ButtonHeight, &Label, &MainView);
|
||||
Ui()->DoLabel(&Label, Localize("Customize"), ButtonHeight * CUi::ms_FontmodHeight * 0.8f, TEXTALIGN_MC);
|
||||
|
||||
// skin part selection
|
||||
MainView.HSplitTop(SpacingH, 0, &MainView);
|
||||
MainView.HSplitTop(ButtonHeight, &Patterns, &MainView);
|
||||
Patterns.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F);
|
||||
|
||||
float ButtonWidth = (Patterns.w / 6.0f) - (SpacingW * 5.0) / 6.0f;
|
||||
|
||||
static CButtonContainer s_aPatternButtons[protocol7::NUM_SKINPARTS];
|
||||
static CButtonContainer s_aSkinPartButtons[protocol7::NUM_SKINPARTS];
|
||||
for(int i = 0; i < protocol7::NUM_SKINPARTS; i++)
|
||||
{
|
||||
Patterns.VSplitLeft(ButtonWidth, &Button, &Patterns);
|
||||
if(DoButton_MenuTab(&s_aPatternButtons[i], Localize(CSkins7::ms_apSkinPartNames[i], "skins"), m_TeePartSelected == i, &Button, IGraphics::CORNER_ALL))
|
||||
CUIRect Button;
|
||||
ButtonBar.VSplitLeft(ButtonWidth, &Button, &ButtonBar);
|
||||
const int Corners = i == 0 ? IGraphics::CORNER_TL : (i == (protocol7::NUM_SKINPARTS - 1) ? IGraphics::CORNER_TR : IGraphics::CORNER_NONE);
|
||||
if(DoButton_MenuTab(&s_aSkinPartButtons[i], Localize(CSkins7::ms_apSkinPartNamesLocalized[i], "skins"), m_TeePartSelected == i, &Button, Corners, nullptr, nullptr, nullptr, nullptr, 4.0f))
|
||||
{
|
||||
m_TeePartSelected = i;
|
||||
}
|
||||
Patterns.VSplitLeft(SpacingW, 0, &Patterns);
|
||||
}
|
||||
|
||||
MainView.HSplitTop(SpacingH, 0, &MainView);
|
||||
MainView.VSplitMid(&Left, &Right, SpacingW);
|
||||
Right.Margin(5.0f, &Right);
|
||||
|
||||
RenderSkinPartSelection7(Left);
|
||||
|
||||
CUIRect CustomColorsButton;
|
||||
Right.HSplitTop(20.0f, &CustomColorsButton, &Right);
|
||||
RenderSkinPartSelection7(SkinPartSelection);
|
||||
|
||||
int *pUseCustomColor = CSkins7::ms_apUCCVariables[(int)m_Dummy][m_TeePartSelected];
|
||||
if(DoButton_CheckBox(pUseCustomColor, Localize("Custom colors"), *pUseCustomColor, &CustomColorsButton))
|
||||
|
@ -380,15 +295,31 @@ void CMenus::RenderSettingsTeeCustom7(CUIRect MainView)
|
|||
|
||||
if(*pUseCustomColor)
|
||||
{
|
||||
CUIRect CustomColors;
|
||||
Right.HSplitTop(5.0f, nullptr, &Right);
|
||||
Right.HSplitTop(95.0f, &CustomColors, &Right);
|
||||
CUIRect CustomColorScrollbars;
|
||||
CustomColors.HSplitTop(5.0f, nullptr, &CustomColors);
|
||||
CustomColors.HSplitTop(95.0f, &CustomColorScrollbars, &CustomColors);
|
||||
|
||||
if(RenderHslaScrollbars(&CustomColors, CSkins7::ms_apColorVariables[(int)m_Dummy][m_TeePartSelected], m_TeePartSelected == protocol7::SKINPART_MARKING, CSkins7::DARKEST_COLOR_LGT))
|
||||
if(RenderHslaScrollbars(&CustomColorScrollbars, CSkins7::ms_apColorVariables[(int)m_Dummy][m_TeePartSelected], m_TeePartSelected == protocol7::SKINPART_MARKING, CSkins7::DARKEST_COLOR_LGT))
|
||||
{
|
||||
SetNeedSendInfo();
|
||||
}
|
||||
}
|
||||
|
||||
// Random skin button
|
||||
static CButtonContainer s_RandomSkinButton;
|
||||
static const char *s_apDice[] = {FONT_ICON_DICE_ONE, FONT_ICON_DICE_TWO, FONT_ICON_DICE_THREE, FONT_ICON_DICE_FOUR, FONT_ICON_DICE_FIVE, FONT_ICON_DICE_SIX};
|
||||
static int s_CurrentDie = rand() % std::size(s_apDice);
|
||||
TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
|
||||
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
|
||||
if(DoButton_Menu(&s_RandomSkinButton, s_apDice[s_CurrentDie], 0, &RandomSkinButton, nullptr, IGraphics::CORNER_ALL, 5.0f, -0.2f))
|
||||
{
|
||||
m_pClient->m_Skins7.RandomizeSkin(m_Dummy);
|
||||
SetNeedSendInfo();
|
||||
s_CurrentDie = rand() % std::size(s_apDice);
|
||||
}
|
||||
TextRender()->SetRenderFlags(0);
|
||||
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
|
||||
GameClient()->m_Tooltips.DoToolTip(&s_RandomSkinButton, &RandomSkinButton, Localize("Create a random skin"));
|
||||
}
|
||||
|
||||
void CMenus::RenderSkinSelection7(CUIRect MainView)
|
||||
|
@ -403,76 +334,74 @@ void CMenus::RenderSkinSelection7(CUIRect MainView)
|
|||
s_SkinCount = m_pClient->m_Skins7.Num();
|
||||
for(int i = 0; i < s_SkinCount; ++i)
|
||||
{
|
||||
const CSkins7::CSkin *s = m_pClient->m_Skins7.Get(i);
|
||||
|
||||
if(g_Config.m_ClSkinFilterString[0] != '\0' && !str_utf8_find_nocase(s->m_aName, g_Config.m_ClSkinFilterString))
|
||||
const CSkins7::CSkin *pSkin = m_pClient->m_Skins7.Get(i);
|
||||
if(g_Config.m_ClSkinFilterString[0] != '\0' && !str_utf8_find_nocase(pSkin->m_aName, g_Config.m_ClSkinFilterString))
|
||||
continue;
|
||||
|
||||
// no special skins
|
||||
if((s->m_Flags & CSkins7::SKINFLAG_SPECIAL) == 0)
|
||||
if((pSkin->m_Flags & CSkins7::SKINFLAG_SPECIAL) == 0)
|
||||
{
|
||||
s_vpSkinList.emplace_back(s);
|
||||
s_vpSkinList.emplace_back(pSkin);
|
||||
}
|
||||
}
|
||||
m_SkinListNeedsUpdate = false;
|
||||
}
|
||||
|
||||
m_pSelectedSkin = 0;
|
||||
m_pSelectedSkin = nullptr;
|
||||
int s_OldSelected = -1;
|
||||
s_ListBox.DoStart(60.0f, s_vpSkinList.size(), 10, 1, s_OldSelected, &MainView);
|
||||
s_ListBox.DoStart(50.0f, s_vpSkinList.size(), 4, 1, s_OldSelected, &MainView);
|
||||
|
||||
for(int i = 0; i < (int)s_vpSkinList.size(); ++i)
|
||||
{
|
||||
const CSkins7::CSkin *s = s_vpSkinList[i];
|
||||
if(s == 0)
|
||||
const CSkins7::CSkin *pSkin = s_vpSkinList[i];
|
||||
if(pSkin == nullptr)
|
||||
continue;
|
||||
if(!str_comp(s->m_aName, Config()->m_ClPlayer7Skin))
|
||||
if(!str_comp(pSkin->m_aName, CSkins7::ms_apSkinNameVariables[m_Dummy]))
|
||||
{
|
||||
m_pSelectedSkin = s;
|
||||
m_pSelectedSkin = pSkin;
|
||||
s_OldSelected = i;
|
||||
}
|
||||
|
||||
CListboxItem Item = s_ListBox.DoNextItem(&s_vpSkinList[i], s_OldSelected == i);
|
||||
if(Item.m_Visible)
|
||||
const CListboxItem Item = s_ListBox.DoNextItem(&s_vpSkinList[i], s_OldSelected == i);
|
||||
if(!Item.m_Visible)
|
||||
continue;
|
||||
|
||||
CUIRect TeePreview, Label;
|
||||
Item.m_Rect.VSplitLeft(60.0f, &TeePreview, &Label);
|
||||
|
||||
CTeeRenderInfo Info;
|
||||
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
|
||||
{
|
||||
CTeeRenderInfo Info;
|
||||
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
|
||||
if(pSkin->m_aUseCustomColors[Part])
|
||||
{
|
||||
if(s->m_aUseCustomColors[Part])
|
||||
{
|
||||
Info.m_aSixup[g_Config.m_ClDummy].m_aTextures[Part] = s->m_apParts[Part]->m_ColorTexture;
|
||||
Info.m_aSixup[g_Config.m_ClDummy].m_aColors[Part] = m_pClient->m_Skins7.GetColor(s->m_aPartColors[Part], Part == protocol7::SKINPART_MARKING);
|
||||
}
|
||||
else
|
||||
{
|
||||
Info.m_aSixup[g_Config.m_ClDummy].m_aTextures[Part] = s->m_apParts[Part]->m_OrgTexture;
|
||||
Info.m_aSixup[g_Config.m_ClDummy].m_aColors[Part] = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
Info.m_aSixup[g_Config.m_ClDummy].m_aTextures[Part] = pSkin->m_apParts[Part]->m_ColorTexture;
|
||||
Info.m_aSixup[g_Config.m_ClDummy].m_aColors[Part] = m_pClient->m_Skins7.GetColor(pSkin->m_aPartColors[Part], Part == protocol7::SKINPART_MARKING);
|
||||
}
|
||||
|
||||
Info.m_Size = 50.0f;
|
||||
Item.m_Rect.HSplitTop(5.0f, 0, &Item.m_Rect); // some margin from the top
|
||||
|
||||
else
|
||||
{
|
||||
// interactive tee: tee is happy to be selected
|
||||
int TeeEmote = (Item.m_Selected && s_LastSelectionTime + 0.75f > Client()->LocalTime()) ? EMOTE_HAPPY : EMOTE_NORMAL;
|
||||
RenderTools()->RenderTee(CAnimState::GetIdle(), &Info, TeeEmote, vec2(1.0f, 0.0f), vec2(Item.m_Rect.x + Item.m_Rect.w / 2, Item.m_Rect.y + Item.m_Rect.h / 2));
|
||||
Info.m_aSixup[g_Config.m_ClDummy].m_aTextures[Part] = pSkin->m_apParts[Part]->m_OrgTexture;
|
||||
Info.m_aSixup[g_Config.m_ClDummy].m_aColors[Part] = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
CUIRect Label;
|
||||
Item.m_Rect.Margin(5.0f, &Item.m_Rect);
|
||||
Item.m_Rect.HSplitBottom(10.0f, &Item.m_Rect, &Label);
|
||||
|
||||
Ui()->DoLabel(&Label, s->m_aName, 10.0f, TEXTALIGN_MC);
|
||||
}
|
||||
Info.m_Size = 50.0f;
|
||||
|
||||
{
|
||||
// interactive tee: tee is happy to be selected
|
||||
int TeeEmote = (Item.m_Selected && s_LastSelectionTime + 0.75f > Client()->GlobalTime()) ? EMOTE_HAPPY : EMOTE_NORMAL;
|
||||
RenderTools()->RenderTee(CAnimState::GetIdle(), &Info, TeeEmote, vec2(1.0f, 0.0f), TeePreview.Center() + vec2(0.0f, 6.0f));
|
||||
}
|
||||
|
||||
SLabelProperties Props;
|
||||
Props.m_MaxWidth = Label.w - 5.0f;
|
||||
Ui()->DoLabel(&Label, pSkin->m_aName, 12.0f, TEXTALIGN_ML, Props);
|
||||
}
|
||||
|
||||
const int NewSelected = s_ListBox.DoEnd();
|
||||
if(NewSelected != -1 && NewSelected != s_OldSelected)
|
||||
{
|
||||
s_LastSelectionTime = Client()->LocalTime();
|
||||
s_LastSelectionTime = Client()->GlobalTime();
|
||||
m_pSelectedSkin = s_vpSkinList[NewSelected];
|
||||
str_copy(Config()->m_ClPlayer7Skin, m_pSelectedSkin->m_aName);
|
||||
str_copy(CSkins7::ms_apSkinNameVariables[m_Dummy], m_pSelectedSkin->m_aName, protocol7::MAX_SKIN_ARRAY_SIZE);
|
||||
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
|
||||
{
|
||||
str_copy(CSkins7::ms_apSkinVariables[(int)m_Dummy][Part], m_pSelectedSkin->m_apParts[Part]->m_aName, protocol7::MAX_SKIN_ARRAY_SIZE);
|
||||
|
@ -485,94 +414,89 @@ void CMenus::RenderSkinSelection7(CUIRect MainView)
|
|||
|
||||
void CMenus::RenderSkinPartSelection7(CUIRect MainView)
|
||||
{
|
||||
static bool s_InitSkinPartList = true;
|
||||
static std::vector<const CSkins7::CSkinPart *> s_paList[protocol7::NUM_SKINPARTS];
|
||||
static CListBox s_ListBox;
|
||||
if(s_InitSkinPartList)
|
||||
static int s_aSkinPartCount[protocol7::NUM_SKINPARTS] = {0};
|
||||
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
|
||||
{
|
||||
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
|
||||
if(m_SkinPartListNeedsUpdate || m_pClient->m_Skins7.NumSkinPart(Part) != s_aSkinPartCount[Part])
|
||||
{
|
||||
s_paList[Part].clear();
|
||||
for(int i = 0; i < m_pClient->m_Skins7.NumSkinPart(Part); ++i)
|
||||
s_aSkinPartCount[Part] = m_pClient->m_Skins7.NumSkinPart(Part);
|
||||
for(int i = 0; i < s_aSkinPartCount[Part]; ++i)
|
||||
{
|
||||
const CSkins7::CSkinPart *s = m_pClient->m_Skins7.GetSkinPart(Part, i);
|
||||
const CSkins7::CSkinPart *pPart = m_pClient->m_Skins7.GetSkinPart(Part, i);
|
||||
if(g_Config.m_ClSkinFilterString[0] != '\0' && !str_utf8_find_nocase(pPart->m_aName, g_Config.m_ClSkinFilterString))
|
||||
continue;
|
||||
|
||||
// no special skins
|
||||
if((s->m_Flags & CSkins7::SKINFLAG_SPECIAL) == 0)
|
||||
if((pPart->m_Flags & CSkins7::SKINFLAG_SPECIAL) == 0)
|
||||
{
|
||||
s_paList[Part].emplace_back(s);
|
||||
s_paList[Part].emplace_back(pPart);
|
||||
}
|
||||
}
|
||||
}
|
||||
s_InitSkinPartList = false;
|
||||
}
|
||||
m_SkinPartListNeedsUpdate = false;
|
||||
|
||||
static int s_OldSelected = -1;
|
||||
s_ListBox.DoBegin(&MainView);
|
||||
s_ListBox.DoStart(60.0f, s_paList[m_TeePartSelected].size(), 5, 1, s_OldSelected);
|
||||
s_ListBox.DoStart(72.0f, s_paList[m_TeePartSelected].size(), 4, 1, s_OldSelected, nullptr, false, IGraphics::CORNER_NONE, true);
|
||||
|
||||
for(int i = 0; i < (int)s_paList[m_TeePartSelected].size(); ++i)
|
||||
{
|
||||
const CSkins7::CSkinPart *s = s_paList[m_TeePartSelected][i];
|
||||
if(s == 0)
|
||||
const CSkins7::CSkinPart *pPart = s_paList[m_TeePartSelected][i];
|
||||
if(pPart == nullptr)
|
||||
continue;
|
||||
if(!str_comp(s->m_aName, CSkins7::ms_apSkinVariables[(int)m_Dummy][m_TeePartSelected]))
|
||||
if(!str_comp(pPart->m_aName, CSkins7::ms_apSkinVariables[(int)m_Dummy][m_TeePartSelected]))
|
||||
s_OldSelected = i;
|
||||
|
||||
CListboxItem Item = s_ListBox.DoNextItem(&s_paList[m_TeePartSelected][i], s_OldSelected == i);
|
||||
if(Item.m_Visible)
|
||||
if(!Item.m_Visible)
|
||||
continue;
|
||||
|
||||
CUIRect Label;
|
||||
Item.m_Rect.Margin(5.0f, &Item.m_Rect);
|
||||
Item.m_Rect.HSplitBottom(12.0f, &Item.m_Rect, &Label);
|
||||
|
||||
CTeeRenderInfo Info;
|
||||
for(int j = 0; j < protocol7::NUM_SKINPARTS; j++)
|
||||
{
|
||||
CTeeRenderInfo Info;
|
||||
for(int j = 0; j < protocol7::NUM_SKINPARTS; j++)
|
||||
int SkinPart = m_pClient->m_Skins7.FindSkinPart(j, CSkins7::ms_apSkinVariables[(int)m_Dummy][j], false);
|
||||
const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.GetSkinPart(j, SkinPart);
|
||||
if(*CSkins7::ms_apUCCVariables[(int)m_Dummy][j])
|
||||
{
|
||||
int SkinPart = m_pClient->m_Skins7.FindSkinPart(j, CSkins7::ms_apSkinVariables[(int)m_Dummy][j], false);
|
||||
const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.GetSkinPart(j, SkinPart);
|
||||
if(*CSkins7::ms_apUCCVariables[(int)m_Dummy][j])
|
||||
{
|
||||
if(m_TeePartSelected == j)
|
||||
Info.m_aSixup[g_Config.m_ClDummy].m_aTextures[j] = s->m_ColorTexture;
|
||||
else
|
||||
Info.m_aSixup[g_Config.m_ClDummy].m_aTextures[j] = pSkinPart->m_ColorTexture;
|
||||
Info.m_aSixup[g_Config.m_ClDummy].m_aColors[j] = m_pClient->m_Skins7.GetColor(*CSkins7::ms_apColorVariables[(int)m_Dummy][j], j == protocol7::SKINPART_MARKING);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(m_TeePartSelected == j)
|
||||
Info.m_aSixup[g_Config.m_ClDummy].m_aTextures[j] = s->m_OrgTexture;
|
||||
else
|
||||
Info.m_aSixup[g_Config.m_ClDummy].m_aTextures[j] = pSkinPart->m_OrgTexture;
|
||||
Info.m_aSixup[0].m_aColors[j] = vec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
Info.m_aSixup[g_Config.m_ClDummy].m_aTextures[j] = m_TeePartSelected == j ? pPart->m_ColorTexture : pSkinPart->m_ColorTexture;
|
||||
Info.m_aSixup[g_Config.m_ClDummy].m_aColors[j] = m_pClient->m_Skins7.GetColor(*CSkins7::ms_apColorVariables[(int)m_Dummy][j], j == protocol7::SKINPART_MARKING);
|
||||
}
|
||||
Info.m_Size = 50.0f;
|
||||
Item.m_Rect.HSplitTop(5.0f, 0, &Item.m_Rect); // some margin from the top
|
||||
const vec2 TeePos(Item.m_Rect.x + Item.m_Rect.w / 2, Item.m_Rect.y + Item.m_Rect.h / 2);
|
||||
|
||||
if(m_TeePartSelected == protocol7::SKINPART_HANDS)
|
||||
else
|
||||
{
|
||||
// RenderTools()->RenderTeeHand(&Info, TeePos, vec2(1.0f, 0.0f), -pi*0.5f, vec2(18, 0));
|
||||
Info.m_aSixup[g_Config.m_ClDummy].m_aTextures[j] = m_TeePartSelected == j ? pPart->m_OrgTexture : pSkinPart->m_OrgTexture;
|
||||
Info.m_aSixup[0].m_aColors[j] = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
int TeePartEmote = EMOTE_NORMAL;
|
||||
if(m_TeePartSelected == protocol7::SKINPART_EYES)
|
||||
{
|
||||
float LocalTime = Client()->LocalTime();
|
||||
TeePartEmote = (int)(LocalTime * 0.5f) % NUM_EMOTES;
|
||||
}
|
||||
RenderTools()->RenderTee(CAnimState::GetIdle(), &Info, TeePartEmote, vec2(1.0f, 0.0f), TeePos);
|
||||
|
||||
CUIRect Label;
|
||||
Item.m_Rect.Margin(5.0f, &Item.m_Rect);
|
||||
Item.m_Rect.HSplitBottom(10.0f, &Item.m_Rect, &Label);
|
||||
|
||||
Ui()->DoLabel(&Label, s->m_aName, 10.0f, TEXTALIGN_MC);
|
||||
}
|
||||
Info.m_Size = 50.0f;
|
||||
|
||||
const vec2 TeePos = Item.m_Rect.Center() + vec2(0.0f, 6.0f);
|
||||
if(m_TeePartSelected == protocol7::SKINPART_HANDS)
|
||||
{
|
||||
// RenderTools()->RenderTeeHand(&Info, TeePos, vec2(1.0f, 0.0f), -pi*0.5f, vec2(18, 0));
|
||||
}
|
||||
int TeePartEmote = EMOTE_NORMAL;
|
||||
if(m_TeePartSelected == protocol7::SKINPART_EYES)
|
||||
{
|
||||
TeePartEmote = (int)(Client()->GlobalTime() * 0.5f) % NUM_EMOTES;
|
||||
}
|
||||
RenderTools()->RenderTee(CAnimState::GetIdle(), &Info, TeePartEmote, vec2(1.0f, 0.0f), TeePos);
|
||||
|
||||
Ui()->DoLabel(&Label, pPart->m_aName, 12.0f, TEXTALIGN_MC);
|
||||
}
|
||||
|
||||
const int NewSelected = s_ListBox.DoEnd();
|
||||
if(NewSelected != -1 && NewSelected != s_OldSelected)
|
||||
{
|
||||
const CSkins7::CSkinPart *s = s_paList[m_TeePartSelected][NewSelected];
|
||||
str_copy(CSkins7::ms_apSkinVariables[(int)m_Dummy][m_TeePartSelected], s->m_aName, protocol7::MAX_SKIN_ARRAY_SIZE);
|
||||
Config()->m_ClPlayer7Skin[0] = 0;
|
||||
str_copy(CSkins7::ms_apSkinVariables[(int)m_Dummy][m_TeePartSelected], s_paList[m_TeePartSelected][NewSelected]->m_aName, protocol7::MAX_SKIN_ARRAY_SIZE);
|
||||
CSkins7::ms_apSkinNameVariables[m_Dummy][0] = '\0';
|
||||
SetNeedSendInfo();
|
||||
}
|
||||
s_OldSelected = NewSelected;
|
||||
|
|
|
@ -352,7 +352,7 @@ int InitSearchList(std::vector<const TName *> &vpSearchList, std::vector<TName>
|
|||
|
||||
void CMenus::RenderSettingsCustom(CUIRect MainView)
|
||||
{
|
||||
CUIRect TabBar, CustomList, QuickSearch, QuickSearchClearButton, DirectoryButton, ReloadButton;
|
||||
CUIRect TabBar, CustomList, QuickSearch, DirectoryButton, ReloadButton;
|
||||
|
||||
MainView.HSplitTop(20.0f, &TabBar, &MainView);
|
||||
const float TabWidth = TabBar.w / NUMBER_OF_ASSETS_TABS;
|
||||
|
@ -599,29 +599,13 @@ void CMenus::RenderSettingsCustom(CUIRect MainView)
|
|||
}
|
||||
}
|
||||
|
||||
// render quick search
|
||||
// Quick search
|
||||
MainView.HSplitBottom(ms_ButtonHeight, &MainView, &QuickSearch);
|
||||
QuickSearch.VSplitLeft(220.0f, &QuickSearch, &DirectoryButton);
|
||||
QuickSearch.HSplitTop(5.0f, nullptr, &QuickSearch);
|
||||
if(Ui()->DoEditBox_Search(&s_aFilterInputs[s_CurCustomTab], &QuickSearch, 14.0f, !Ui()->IsPopupOpen() && m_pClient->m_GameConsole.IsClosed()))
|
||||
{
|
||||
MainView.HSplitBottom(ms_ButtonHeight, &MainView, &QuickSearch);
|
||||
QuickSearch.VSplitLeft(240.0f, &QuickSearch, &DirectoryButton);
|
||||
QuickSearch.HSplitTop(5.0f, 0, &QuickSearch);
|
||||
TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
|
||||
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
|
||||
|
||||
Ui()->DoLabel(&QuickSearch, FONT_ICON_MAGNIFYING_GLASS, 14.0f, TEXTALIGN_ML);
|
||||
float SearchWidth = TextRender()->TextWidth(14.0f, FONT_ICON_MAGNIFYING_GLASS, -1, -1.0f);
|
||||
TextRender()->SetRenderFlags(0);
|
||||
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
|
||||
QuickSearch.VSplitLeft(SearchWidth, 0, &QuickSearch);
|
||||
QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch);
|
||||
QuickSearch.VSplitLeft(QuickSearch.w - 10.0f, &QuickSearch, &QuickSearchClearButton);
|
||||
if(Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed())
|
||||
{
|
||||
Ui()->SetActiveItem(&s_aFilterInputs[s_CurCustomTab]);
|
||||
s_aFilterInputs[s_CurCustomTab].SelectAll();
|
||||
}
|
||||
s_aFilterInputs[s_CurCustomTab].SetEmptyText(Localize("Search"));
|
||||
if(Ui()->DoClearableEditBox(&s_aFilterInputs[s_CurCustomTab], &QuickSearch, 14.0f))
|
||||
gs_aInitCustomList[s_CurCustomTab] = true;
|
||||
gs_aInitCustomList[s_CurCustomTab] = true;
|
||||
}
|
||||
|
||||
DirectoryButton.HSplitTop(5.0f, 0, &DirectoryButton);
|
||||
|
|
|
@ -844,10 +844,7 @@ void CPlayers::OnRender()
|
|||
{
|
||||
aRenderInfo[i].m_aSixup[g_Config.m_ClDummy].Reset();
|
||||
|
||||
aRenderInfo[i].m_OriginalRenderSkin = pSkin->m_OriginalSkin;
|
||||
aRenderInfo[i].m_ColorableRenderSkin = pSkin->m_ColorableSkin;
|
||||
aRenderInfo[i].m_BloodColor = pSkin->m_BloodColor;
|
||||
aRenderInfo[i].m_SkinMetrics = pSkin->m_Metrics;
|
||||
aRenderInfo[i].Apply(pSkin);
|
||||
aRenderInfo[i].m_CustomColoredSkin = IsTeamplay;
|
||||
if(!IsTeamplay)
|
||||
{
|
||||
|
@ -857,12 +854,8 @@ void CPlayers::OnRender()
|
|||
}
|
||||
}
|
||||
}
|
||||
const CSkin *pSkin = m_pClient->m_Skins.Find("x_spec");
|
||||
CTeeRenderInfo RenderInfoSpec;
|
||||
RenderInfoSpec.m_OriginalRenderSkin = pSkin->m_OriginalSkin;
|
||||
RenderInfoSpec.m_ColorableRenderSkin = pSkin->m_ColorableSkin;
|
||||
RenderInfoSpec.m_BloodColor = pSkin->m_BloodColor;
|
||||
RenderInfoSpec.m_SkinMetrics = pSkin->m_Metrics;
|
||||
RenderInfoSpec.Apply(m_pClient->m_Skins.Find("x_spec"));
|
||||
RenderInfoSpec.m_CustomColoredSkin = false;
|
||||
RenderInfoSpec.m_Size = 64.0f;
|
||||
const int LocalClientId = m_pClient->m_Snap.m_LocalClientId;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <base/system.h>
|
||||
|
||||
#include <engine/engine.h>
|
||||
#include <engine/gfx/image_manipulation.h>
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/shared/config.h>
|
||||
#include <engine/storage.h>
|
||||
|
@ -237,14 +238,7 @@ const CSkin *CSkins::LoadSkin(const char *pName, CImageInfo &Info)
|
|||
// get feet outline size
|
||||
CheckMetrics(Skin.m_Metrics.m_Feet, pData, Pitch, FeetOutlineOffsetX, FeetOutlineOffsetY, FeetOutlineWidth, FeetOutlineHeight);
|
||||
|
||||
// make the texture gray scale
|
||||
for(size_t i = 0; i < Info.m_Width * Info.m_Height; i++)
|
||||
{
|
||||
int v = (pData[i * PixelStep] + pData[i * PixelStep + 1] + pData[i * PixelStep + 2]) / 3;
|
||||
pData[i * PixelStep] = v;
|
||||
pData[i * PixelStep + 1] = v;
|
||||
pData[i * PixelStep + 2] = v;
|
||||
}
|
||||
ConvertToGrayscale(Info);
|
||||
|
||||
int aFreq[256] = {0};
|
||||
int OrgWeight = 0;
|
||||
|
|
|
@ -6,9 +6,11 @@
|
|||
#include <base/system.h>
|
||||
|
||||
#include <engine/external/json-parser/json.h>
|
||||
#include <engine/gfx/image_manipulation.h>
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/shared/config.h>
|
||||
#include <engine/shared/jsonwriter.h>
|
||||
#include <engine/shared/localization.h>
|
||||
#include <engine/shared/protocol7.h>
|
||||
#include <engine/storage.h>
|
||||
|
||||
|
@ -19,8 +21,10 @@
|
|||
#include "skins7.h"
|
||||
|
||||
const char *const CSkins7::ms_apSkinPartNames[protocol7::NUM_SKINPARTS] = {"body", "marking", "decoration", "hands", "feet", "eyes"};
|
||||
const char *const CSkins7::ms_apSkinPartNamesLocalized[protocol7::NUM_SKINPARTS] = {Localizable("Body", "skins"), Localizable("Marking", "skins"), Localizable("Decoration", "skins"), Localizable("Hands", "skins"), Localizable("Feet", "skins"), Localizable("Eyes", "skins")};
|
||||
const char *const CSkins7::ms_apColorComponents[NUM_COLOR_COMPONENTS] = {"hue", "sat", "lgt", "alp"};
|
||||
|
||||
char *CSkins7::ms_apSkinNameVariables[NUM_DUMMIES] = {0};
|
||||
char *CSkins7::ms_apSkinVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS] = {{0}};
|
||||
int *CSkins7::ms_apUCCVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS] = {{0}};
|
||||
int unsigned *CSkins7::ms_apColorVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS] = {{0}};
|
||||
|
@ -89,14 +93,7 @@ int CSkins7::SkinPartScan(const char *pName, int IsDir, int DirType, void *pUser
|
|||
Part.m_BloodColor = ColorRGBA(normalize(vec3(aColors[0], aColors[1], aColors[2])));
|
||||
}
|
||||
|
||||
// create colorless version
|
||||
for(size_t i = 0; i < Info.m_Width * Info.m_Height; i++)
|
||||
{
|
||||
const int Average = (pData[i * Step] + pData[i * Step + 1] + pData[i * Step + 2]) / 3;
|
||||
pData[i * Step] = Average;
|
||||
pData[i * Step + 1] = Average;
|
||||
pData[i * Step + 2] = Average;
|
||||
}
|
||||
ConvertToGrayscale(Info);
|
||||
|
||||
Part.m_ColorTexture = pSelf->Graphics()->LoadTextureRawMove(Info, 0, aFilename);
|
||||
|
||||
|
@ -229,6 +226,7 @@ int CSkins7::GetInitAmount() const
|
|||
void CSkins7::OnInit()
|
||||
{
|
||||
int Dummy = 0;
|
||||
ms_apSkinNameVariables[Dummy] = Config()->m_ClPlayer7Skin;
|
||||
ms_apSkinVariables[Dummy][protocol7::SKINPART_BODY] = Config()->m_ClPlayer7SkinBody;
|
||||
ms_apSkinVariables[Dummy][protocol7::SKINPART_MARKING] = Config()->m_ClPlayer7SkinMarking;
|
||||
ms_apSkinVariables[Dummy][protocol7::SKINPART_DECORATION] = Config()->m_ClPlayer7SkinDecoration;
|
||||
|
@ -249,6 +247,7 @@ void CSkins7::OnInit()
|
|||
ms_apColorVariables[Dummy][protocol7::SKINPART_EYES] = &Config()->m_ClPlayer7ColorEyes;
|
||||
|
||||
Dummy = 1;
|
||||
ms_apSkinNameVariables[Dummy] = Config()->m_ClDummy7Skin;
|
||||
ms_apSkinVariables[Dummy][protocol7::SKINPART_BODY] = Config()->m_ClDummy7SkinBody;
|
||||
ms_apSkinVariables[Dummy][protocol7::SKINPART_MARKING] = Config()->m_ClDummy7SkinMarking;
|
||||
ms_apSkinVariables[Dummy][protocol7::SKINPART_DECORATION] = Config()->m_ClDummy7SkinDecoration;
|
||||
|
@ -461,17 +460,19 @@ void CSkins7::RandomizeSkin(int Dummy)
|
|||
if(Part == protocol7::SKINPART_MARKING)
|
||||
Alp = rand() % 255;
|
||||
int ColorVariable = (Alp << 24) | (Hue << 16) | (Sat << 8) | Lgt;
|
||||
*CSkins7::ms_apUCCVariables[Dummy][Part] = true;
|
||||
*CSkins7::ms_apColorVariables[Dummy][Part] = ColorVariable;
|
||||
*ms_apUCCVariables[Dummy][Part] = true;
|
||||
*ms_apColorVariables[Dummy][Part] = ColorVariable;
|
||||
}
|
||||
|
||||
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
|
||||
{
|
||||
const CSkins7::CSkinPart *pSkinPart = GetSkinPart(Part, rand() % NumSkinPart(Part));
|
||||
while(pSkinPart->m_Flags & CSkins7::SKINFLAG_SPECIAL)
|
||||
const CSkinPart *pSkinPart = GetSkinPart(Part, rand() % NumSkinPart(Part));
|
||||
while(pSkinPart->m_Flags & SKINFLAG_SPECIAL)
|
||||
pSkinPart = GetSkinPart(Part, rand() % NumSkinPart(Part));
|
||||
mem_copy(CSkins7::ms_apSkinVariables[Dummy][Part], pSkinPart->m_aName, protocol7::MAX_SKIN_ARRAY_SIZE);
|
||||
str_copy(ms_apSkinVariables[Dummy][Part], pSkinPart->m_aName, protocol7::MAX_SKIN_ARRAY_SIZE);
|
||||
}
|
||||
|
||||
ms_apSkinNameVariables[Dummy][0] = '\0';
|
||||
}
|
||||
|
||||
ColorRGBA CSkins7::GetColor(int Value, bool UseAlpha) const
|
||||
|
|
|
@ -52,8 +52,10 @@ public:
|
|||
};
|
||||
|
||||
static const char *const ms_apSkinPartNames[protocol7::NUM_SKINPARTS];
|
||||
static const char *const ms_apSkinPartNamesLocalized[protocol7::NUM_SKINPARTS];
|
||||
static const char *const ms_apColorComponents[NUM_COLOR_COMPONENTS];
|
||||
|
||||
static char *ms_apSkinNameVariables[NUM_DUMMIES];
|
||||
static char *ms_apSkinVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS];
|
||||
static int *ms_apUCCVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS]; // use custom color
|
||||
static unsigned int *ms_apColorVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS];
|
||||
|
|
|
@ -102,7 +102,7 @@ void CStatboard::OnMessage(int MsgType, void *pRawMsg)
|
|||
|
||||
if(t <= p)
|
||||
return;
|
||||
str_utf8_truncate(aName, sizeof(aName), p, t - p);
|
||||
str_truncate(aName, sizeof(aName), p, t - p);
|
||||
|
||||
for(int i = 0; i < MAX_CLIENTS; i++)
|
||||
{
|
||||
|
|
|
@ -447,7 +447,9 @@ void CGameClient::OnUpdate()
|
|||
Input()->ConsumeEvents([&](const IInput::CEvent &Event) {
|
||||
for(auto &pComponent : m_vpInput)
|
||||
{
|
||||
if(pComponent->OnInput(Event))
|
||||
// Events with flag `FLAG_RELEASE` must always be forwarded to all components so keys being
|
||||
// released can be handled in all components also after some components have been disabled.
|
||||
if(pComponent->OnInput(Event) && (Event.m_Flags & ~IInput::FLAG_RELEASE) != 0)
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
@ -1554,11 +1556,7 @@ void CGameClient::OnNewSnapshot()
|
|||
pClient->m_SkinInfo.m_Size = 64;
|
||||
|
||||
// find new skin
|
||||
const CSkin *pSkin = m_Skins.Find(pClient->m_aSkinName);
|
||||
pClient->m_SkinInfo.m_OriginalRenderSkin = pSkin->m_OriginalSkin;
|
||||
pClient->m_SkinInfo.m_ColorableRenderSkin = pSkin->m_ColorableSkin;
|
||||
pClient->m_SkinInfo.m_SkinMetrics = pSkin->m_Metrics;
|
||||
pClient->m_SkinInfo.m_BloodColor = pSkin->m_BloodColor;
|
||||
pClient->m_SkinInfo.Apply(m_Skins.Find(pClient->m_aSkinName));
|
||||
pClient->m_SkinInfo.m_CustomColoredSkin = pClient->m_UseCustomColor;
|
||||
|
||||
if(!pClient->m_UseCustomColor)
|
||||
|
@ -1567,7 +1565,7 @@ void CGameClient::OnNewSnapshot()
|
|||
pClient->m_SkinInfo.m_ColorFeet = ColorRGBA(1, 1, 1);
|
||||
}
|
||||
|
||||
pClient->UpdateRenderInfo(IsTeamPlay(), g_Config.m_ClDummy);
|
||||
pClient->UpdateRenderInfo(IsTeamPlay());
|
||||
}
|
||||
}
|
||||
else if(Item.m_Type == NETOBJTYPE_PLAYERINFO)
|
||||
|
@ -2071,6 +2069,32 @@ void CGameClient::OnNewSnapshot()
|
|||
m_Effects.AirJump(Pos, Alpha);
|
||||
}
|
||||
|
||||
if(g_Config.m_ClFreezeStars && !m_SuppressEvents)
|
||||
{
|
||||
for(auto &Character : m_Snap.m_aCharacters)
|
||||
{
|
||||
if(Character.m_Active && Character.m_HasExtendedData && Character.m_PrevExtendedData)
|
||||
{
|
||||
int FreezeTimeNow = Character.m_ExtendedData.m_FreezeEnd - Client()->GameTick(g_Config.m_ClDummy);
|
||||
int FreezeTimePrev = Character.m_PrevExtendedData->m_FreezeEnd - Client()->PrevGameTick(g_Config.m_ClDummy);
|
||||
vec2 Pos = vec2(Character.m_Cur.m_X, Character.m_Cur.m_Y);
|
||||
int StarsNow = (FreezeTimeNow + 1) / Client()->GameTickSpeed();
|
||||
int StarsPrev = (FreezeTimePrev + 1) / Client()->GameTickSpeed();
|
||||
if(StarsNow < StarsPrev || (StarsPrev == 0 && StarsNow > 0))
|
||||
{
|
||||
int Amount = StarsNow + 1;
|
||||
float Mid = 3 * pi / 2;
|
||||
float Min = Mid - pi / 3;
|
||||
float Max = Mid + pi / 3;
|
||||
for(int j = 0; j < Amount; j++)
|
||||
{
|
||||
float Angle = mix(Min, Max, (j + 1) / (float)(Amount + 2));
|
||||
m_Effects.DamageIndicator(Pos, direction(Angle));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(m_Snap.m_LocalClientId != m_PrevLocalId)
|
||||
m_PredictedDummyId = m_PrevLocalId;
|
||||
m_PrevLocalId = m_Snap.m_LocalClientId;
|
||||
|
@ -2364,7 +2388,7 @@ void CGameClient::CClientStats::Reset()
|
|||
m_FlagCaptures = 0;
|
||||
}
|
||||
|
||||
void CGameClient::CClientData::UpdateRenderInfo(bool IsTeamPlay, int Conn)
|
||||
void CGameClient::CClientData::UpdateRenderInfo(bool IsTeamPlay)
|
||||
{
|
||||
m_RenderInfo = m_SkinInfo;
|
||||
|
||||
|
@ -2379,6 +2403,7 @@ void CGameClient::CClientData::UpdateRenderInfo(bool IsTeamPlay, int Conn)
|
|||
m_RenderInfo.m_ColorFeet = color_cast<ColorRGBA>(ColorHSLA(aTeamColors[m_Team]));
|
||||
|
||||
// 0.7
|
||||
for(auto &Sixup : m_RenderInfo.m_aSixup)
|
||||
{
|
||||
const ColorRGBA aTeamColorsSixup[2] = {
|
||||
ColorRGBA(0.753f, 0.318f, 0.318f, 1.0f),
|
||||
|
@ -2386,19 +2411,20 @@ void CGameClient::CClientData::UpdateRenderInfo(bool IsTeamPlay, int Conn)
|
|||
const ColorRGBA aMarkingColorsSixup[2] = {
|
||||
ColorRGBA(0.824f, 0.345f, 0.345f, 1.0f),
|
||||
ColorRGBA(0.345f, 0.514f, 0.824f, 1.0f)};
|
||||
float MarkingAlpha = m_RenderInfo.m_aSixup[Conn].m_aColors[protocol7::SKINPART_MARKING].a;
|
||||
for(auto &Color : m_RenderInfo.m_aSixup[Conn].m_aColors)
|
||||
float MarkingAlpha = Sixup.m_aColors[protocol7::SKINPART_MARKING].a;
|
||||
for(auto &Color : Sixup.m_aColors)
|
||||
Color = aTeamColorsSixup[m_Team];
|
||||
if(MarkingAlpha > 0.1f)
|
||||
m_RenderInfo.m_aSixup[Conn].m_aColors[protocol7::SKINPART_MARKING] = aMarkingColorsSixup[m_Team];
|
||||
Sixup.m_aColors[protocol7::SKINPART_MARKING] = aMarkingColorsSixup[m_Team];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_RenderInfo.m_ColorBody = color_cast<ColorRGBA>(ColorHSLA(12829350));
|
||||
m_RenderInfo.m_ColorFeet = color_cast<ColorRGBA>(ColorHSLA(12829350));
|
||||
for(auto &Color : m_RenderInfo.m_aSixup[Conn].m_aColors)
|
||||
Color = color_cast<ColorRGBA>(ColorHSLA(12829350));
|
||||
for(auto &Sixup : m_RenderInfo.m_aSixup)
|
||||
for(auto &Color : Sixup.m_aColors)
|
||||
Color = color_cast<ColorRGBA>(ColorHSLA(12829350));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3724,21 +3750,16 @@ void CGameClient::RefreshSkins()
|
|||
|
||||
for(auto &Client : m_aClients)
|
||||
{
|
||||
Client.m_SkinInfo.m_OriginalRenderSkin.Reset();
|
||||
Client.m_SkinInfo.m_ColorableRenderSkin.Reset();
|
||||
if(Client.m_aSkinName[0] != '\0')
|
||||
{
|
||||
const CSkin *pSkin = m_Skins.Find(Client.m_aSkinName);
|
||||
Client.m_SkinInfo.m_OriginalRenderSkin = pSkin->m_OriginalSkin;
|
||||
Client.m_SkinInfo.m_ColorableRenderSkin = pSkin->m_ColorableSkin;
|
||||
Client.m_SkinInfo.Apply(m_Skins.Find(Client.m_aSkinName));
|
||||
}
|
||||
else
|
||||
{
|
||||
Client.m_SkinInfo.m_OriginalRenderSkin.Reset();
|
||||
Client.m_SkinInfo.m_ColorableRenderSkin.Reset();
|
||||
}
|
||||
for(int Dummy = 0; Dummy < NUM_DUMMIES; Dummy++)
|
||||
Client.UpdateRenderInfo(IsTeamPlay(), Dummy);
|
||||
Client.UpdateRenderInfo(IsTeamPlay());
|
||||
}
|
||||
|
||||
for(auto &pComponent : m_vpAll)
|
||||
|
|
|
@ -438,7 +438,7 @@ public:
|
|||
bool m_SpecCharPresent;
|
||||
vec2 m_SpecChar;
|
||||
|
||||
void UpdateRenderInfo(bool IsTeamPlay, int Conn);
|
||||
void UpdateRenderInfo(bool IsTeamPlay);
|
||||
void Reset();
|
||||
|
||||
class CSixup
|
||||
|
|
|
@ -56,6 +56,14 @@ public:
|
|||
Sixup.Reset();
|
||||
}
|
||||
|
||||
void Apply(const CSkin *pSkin)
|
||||
{
|
||||
m_OriginalRenderSkin = pSkin->m_OriginalSkin;
|
||||
m_ColorableRenderSkin = pSkin->m_ColorableSkin;
|
||||
m_BloodColor = pSkin->m_BloodColor;
|
||||
m_SkinMetrics = pSkin->m_Metrics;
|
||||
}
|
||||
|
||||
CSkin::SSkinTextures m_OriginalRenderSkin;
|
||||
CSkin::SSkinTextures m_ColorableRenderSkin;
|
||||
|
||||
|
|
|
@ -196,7 +196,7 @@ void *CGameClient::TranslateGameMsg(int *pMsgId, CUnpacker *pUnpacker, int Conn)
|
|||
{
|
||||
m_aClients[pMsg7->m_ClientId].m_Team = pMsg7->m_Team;
|
||||
m_pClient->m_TranslationContext.m_aClients[pMsg7->m_ClientId].m_Team = pMsg7->m_Team;
|
||||
m_aClients[pMsg7->m_ClientId].UpdateRenderInfo(IsTeamPlay(), Conn);
|
||||
m_aClients[pMsg7->m_ClientId].UpdateRenderInfo(IsTeamPlay());
|
||||
|
||||
// if(pMsg7->m_ClientId == m_LocalClientId)
|
||||
// {
|
||||
|
@ -620,10 +620,12 @@ void *CGameClient::TranslateGameMsg(int *pMsgId, CUnpacker *pUnpacker, int Conn)
|
|||
switch(GameMsgId)
|
||||
{
|
||||
case protocol7::GAMEMSG_CTF_DROP:
|
||||
m_Sounds.Enqueue(CSounds::CHN_GLOBAL, SOUND_CTF_DROP);
|
||||
if(Conn == g_Config.m_ClDummy)
|
||||
m_Sounds.Enqueue(CSounds::CHN_GLOBAL, SOUND_CTF_DROP);
|
||||
break;
|
||||
case protocol7::GAMEMSG_CTF_RETURN:
|
||||
m_Sounds.Enqueue(CSounds::CHN_GLOBAL, SOUND_CTF_RETURN);
|
||||
if(Conn == g_Config.m_ClDummy)
|
||||
m_Sounds.Enqueue(CSounds::CHN_GLOBAL, SOUND_CTF_RETURN);
|
||||
break;
|
||||
case protocol7::GAMEMSG_TEAM_ALL:
|
||||
{
|
||||
|
@ -650,7 +652,8 @@ void *CGameClient::TranslateGameMsg(int *pMsgId, CUnpacker *pUnpacker, int Conn)
|
|||
}
|
||||
break;
|
||||
case protocol7::GAMEMSG_CTF_GRAB:
|
||||
m_Sounds.Enqueue(CSounds::CHN_GLOBAL, SOUND_CTF_GRAB_EN);
|
||||
if(Conn == g_Config.m_ClDummy)
|
||||
m_Sounds.Enqueue(CSounds::CHN_GLOBAL, SOUND_CTF_GRAB_EN);
|
||||
break;
|
||||
case protocol7::GAMEMSG_GAME_PAUSED:
|
||||
{
|
||||
|
@ -660,7 +663,8 @@ void *CGameClient::TranslateGameMsg(int *pMsgId, CUnpacker *pUnpacker, int Conn)
|
|||
}
|
||||
break;
|
||||
case protocol7::GAMEMSG_CTF_CAPTURE:
|
||||
m_Sounds.Enqueue(CSounds::CHN_GLOBAL, SOUND_CTF_CAPTURE);
|
||||
if(Conn == g_Config.m_ClDummy)
|
||||
m_Sounds.Enqueue(CSounds::CHN_GLOBAL, SOUND_CTF_CAPTURE);
|
||||
int ClientId = clamp(aParaI[1], 0, MAX_CLIENTS - 1);
|
||||
m_aStats[ClientId].m_FlagCaptures++;
|
||||
|
||||
|
|
|
@ -1004,6 +1004,25 @@ bool CUi::DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float
|
|||
return ReturnValue;
|
||||
}
|
||||
|
||||
bool CUi::DoEditBox_Search(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, bool HotkeyEnabled)
|
||||
{
|
||||
CUIRect QuickSearch = *pRect;
|
||||
TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
|
||||
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
|
||||
DoLabel(&QuickSearch, FONT_ICON_MAGNIFYING_GLASS, FontSize, TEXTALIGN_ML);
|
||||
const float SearchWidth = TextRender()->TextWidth(FontSize, FONT_ICON_MAGNIFYING_GLASS);
|
||||
TextRender()->SetRenderFlags(0);
|
||||
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
|
||||
QuickSearch.VSplitLeft(SearchWidth + 5.0f, nullptr, &QuickSearch);
|
||||
if(HotkeyEnabled && Input()->ModifierIsPressed() && Input()->KeyPress(KEY_F))
|
||||
{
|
||||
SetActiveItem(pLineInput);
|
||||
pLineInput->SelectAll();
|
||||
}
|
||||
pLineInput->SetEmptyText(Localize("Search"));
|
||||
return DoClearableEditBox(pLineInput, &QuickSearch, FontSize);
|
||||
}
|
||||
|
||||
int CUi::DoButton_Menu(CUIElement &UIElement, const CButtonContainer *pId, const std::function<const char *()> &GetTextLambda, const CUIRect *pRect, const SMenuButtonProperties &Props)
|
||||
{
|
||||
CUIRect Text = *pRect, DropDownIcon;
|
||||
|
@ -1282,21 +1301,24 @@ float CUi::DoScrollbarV(const void *pId, const CUIRect *pRect, float Current)
|
|||
}
|
||||
else if(HotItem() == pId)
|
||||
{
|
||||
if(MouseButton(0))
|
||||
if(InsideHandle)
|
||||
{
|
||||
if(MouseButton(0))
|
||||
{
|
||||
SetActiveItem(pId);
|
||||
m_ActiveScrollbarOffset = MouseY() - Handle.y;
|
||||
Grabbed = true;
|
||||
}
|
||||
}
|
||||
else if(MouseButtonClicked(0))
|
||||
{
|
||||
SetActiveItem(pId);
|
||||
m_ActiveScrollbarOffset = MouseY() - Handle.y;
|
||||
m_ActiveScrollbarOffset = Handle.h / 2.0f;
|
||||
Grabbed = true;
|
||||
}
|
||||
}
|
||||
else if(MouseButtonClicked(0) && !InsideHandle && InsideRail)
|
||||
{
|
||||
SetActiveItem(pId);
|
||||
m_ActiveScrollbarOffset = Handle.h / 2.0f;
|
||||
Grabbed = true;
|
||||
}
|
||||
|
||||
if(InsideHandle && !MouseButton(0))
|
||||
if(InsideRail && !MouseButton(0))
|
||||
{
|
||||
SetHotItem(pId);
|
||||
}
|
||||
|
@ -1361,19 +1383,22 @@ float CUi::DoScrollbarH(const void *pId, const CUIRect *pRect, float Current, co
|
|||
}
|
||||
else if(HotItem() == pId)
|
||||
{
|
||||
if(MouseButton(0))
|
||||
if(InsideHandle)
|
||||
{
|
||||
if(MouseButton(0))
|
||||
{
|
||||
SetActiveItem(pId);
|
||||
m_ActiveScrollbarOffset = MouseX() - Handle.x;
|
||||
Grabbed = true;
|
||||
}
|
||||
}
|
||||
else if(MouseButtonClicked(0))
|
||||
{
|
||||
SetActiveItem(pId);
|
||||
m_ActiveScrollbarOffset = MouseX() - Handle.x;
|
||||
m_ActiveScrollbarOffset = Handle.w / 2.0f;
|
||||
Grabbed = true;
|
||||
}
|
||||
}
|
||||
else if(MouseButtonClicked(0) && !InsideHandle && InsideRail)
|
||||
{
|
||||
SetActiveItem(pId);
|
||||
m_ActiveScrollbarOffset = Handle.w / 2.0f;
|
||||
Grabbed = true;
|
||||
}
|
||||
|
||||
if(!pColorInner && (InsideHandle || Grabbed) && (CheckActiveItem(pId) || HotItem() == pId))
|
||||
{
|
||||
|
@ -1381,7 +1406,7 @@ float CUi::DoScrollbarH(const void *pId, const CUIRect *pRect, float Current, co
|
|||
Handle.y -= 1.5f;
|
||||
}
|
||||
|
||||
if(InsideHandle && !MouseButton(0))
|
||||
if(InsideRail && !MouseButton(0))
|
||||
{
|
||||
SetHotItem(pId);
|
||||
}
|
||||
|
|
|
@ -603,6 +603,24 @@ public:
|
|||
*/
|
||||
bool DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners = IGraphics::CORNER_ALL, const std::vector<STextColorSplit> &vColorSplits = {});
|
||||
|
||||
/**
|
||||
* Creates an input field with a search icon and a clear [x] button attached to it.
|
||||
* The input will have default text "Search" and the hotkey Ctrl+F can be used to activate the input.
|
||||
*
|
||||
* @see DoEditBox
|
||||
*
|
||||
* @param pLineInput This pointer will be stored and written to on next user input.
|
||||
* So you can not pass in a pointer that goes out of scope such as a local variable.
|
||||
* Pass in either a member variable of the current class or a static variable.
|
||||
* For example ```static CLineInputBuffered<IO_MAX_PATH_LENGTH> s_MyInput;```
|
||||
* @param pRect the UI rect it will attach to
|
||||
* @param FontSize Size of the font (`10.0f`, `12.0f` and `14.0f` are commonly used here)
|
||||
* @param HotkeyEnabled Whether the hotkey to enable this editbox is currently enabled.
|
||||
*
|
||||
* @return true if the value of the input field changed since the last call.
|
||||
*/
|
||||
bool DoEditBox_Search(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, bool HotkeyEnabled);
|
||||
|
||||
int DoButton_Menu(CUIElement &UIElement, const CButtonContainer *pId, const std::function<const char *()> &GetTextLambda, const CUIRect *pRect, const SMenuButtonProperties &Props = {});
|
||||
// only used for popup menus
|
||||
int DoButton_PopupMenu(CButtonContainer *pButtonContainer, const char *pText, const CUIRect *pRect, float Size, int Align, float Padding = 0.0f, bool TransparentInactive = false, bool Enabled = true);
|
||||
|
|
|
@ -850,41 +850,14 @@ bool CEditor::CallbackSaveImage(const char *pFileName, int StorageType, void *pU
|
|||
|
||||
std::shared_ptr<CEditorImage> pImg = pEditor->m_Map.m_vpImages[pEditor->m_SelectedImage];
|
||||
|
||||
EImageFormat OutputFormat;
|
||||
switch(pImg->m_Format)
|
||||
if(CImageLoader::SavePng(pEditor->Storage()->OpenFile(pFileName, IOFLAG_WRITE, StorageType), pFileName, *pImg))
|
||||
{
|
||||
case CImageInfo::FORMAT_RGB:
|
||||
OutputFormat = IMAGE_FORMAT_RGB;
|
||||
break;
|
||||
case CImageInfo::FORMAT_RGBA:
|
||||
OutputFormat = IMAGE_FORMAT_RGBA;
|
||||
break;
|
||||
case CImageInfo::FORMAT_SINGLE_COMPONENT:
|
||||
OutputFormat = IMAGE_FORMAT_R;
|
||||
break;
|
||||
default:
|
||||
dbg_assert(false, "Image has invalid format.");
|
||||
return false;
|
||||
};
|
||||
|
||||
TImageByteBuffer ByteBuffer;
|
||||
SImageByteBuffer ImageByteBuffer(&ByteBuffer);
|
||||
if(SavePng(OutputFormat, pImg->m_pData, ImageByteBuffer, pImg->m_Width, pImg->m_Height))
|
||||
{
|
||||
IOHANDLE File = pEditor->Storage()->OpenFile(pFileName, IOFLAG_WRITE, StorageType);
|
||||
if(File)
|
||||
{
|
||||
io_write(File, &ByteBuffer.front(), ByteBuffer.size());
|
||||
io_close(File);
|
||||
pEditor->m_Dialog = DIALOG_NONE;
|
||||
return true;
|
||||
}
|
||||
pEditor->ShowFileDialogError("Failed to open file '%s'.", pFileName);
|
||||
return false;
|
||||
pEditor->m_Dialog = DIALOG_NONE;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
pEditor->ShowFileDialogError("Failed to write image to file.");
|
||||
pEditor->ShowFileDialogError("Failed to write image to file '%s'.", pFileName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -4401,18 +4374,13 @@ bool CEditor::ReplaceImage(const char *pFileName, int StorageType, bool CheckDup
|
|||
str_copy(pImg->m_aName, aBuf);
|
||||
pImg->m_External = IsVanillaImage(pImg->m_aName);
|
||||
|
||||
if(!pImg->m_External && pImg->m_Format != CImageInfo::FORMAT_RGBA)
|
||||
if(!pImg->m_External)
|
||||
{
|
||||
uint8_t *pRgbaData = static_cast<uint8_t *>(malloc((size_t)pImg->m_Width * pImg->m_Height * CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA)));
|
||||
ConvertToRGBA(pRgbaData, *pImg);
|
||||
free(pImg->m_pData);
|
||||
pImg->m_pData = pRgbaData;
|
||||
pImg->m_Format = CImageInfo::FORMAT_RGBA;
|
||||
}
|
||||
|
||||
if(!pImg->m_External && g_Config.m_ClEditorDilate == 1)
|
||||
{
|
||||
DilateImage(pImg->m_pData, pImg->m_Width, pImg->m_Height);
|
||||
ConvertToRgba(*pImg);
|
||||
if(g_Config.m_ClEditorDilate == 1)
|
||||
{
|
||||
DilateImage(*pImg);
|
||||
}
|
||||
}
|
||||
|
||||
pImg->m_AutoMapper.Load(pImg->m_aName);
|
||||
|
@ -4473,18 +4441,13 @@ bool CEditor::AddImage(const char *pFileName, int StorageType, void *pUser)
|
|||
pImg->m_pData = ImgInfo.m_pData;
|
||||
pImg->m_External = IsVanillaImage(aBuf);
|
||||
|
||||
if(pImg->m_Format != CImageInfo::FORMAT_RGBA)
|
||||
if(!pImg->m_External)
|
||||
{
|
||||
uint8_t *pRgbaData = static_cast<uint8_t *>(malloc((size_t)pImg->m_Width * pImg->m_Height * CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA)));
|
||||
ConvertToRGBA(pRgbaData, *pImg);
|
||||
free(pImg->m_pData);
|
||||
pImg->m_pData = pRgbaData;
|
||||
pImg->m_Format = CImageInfo::FORMAT_RGBA;
|
||||
}
|
||||
|
||||
if(!pImg->m_External && g_Config.m_ClEditorDilate == 1)
|
||||
{
|
||||
DilateImage(pImg->m_pData, pImg->m_Width, pImg->m_Height);
|
||||
ConvertToRgba(*pImg);
|
||||
if(g_Config.m_ClEditorDilate == 1)
|
||||
{
|
||||
DilateImage(*pImg);
|
||||
}
|
||||
}
|
||||
|
||||
int TextureLoadFlag = pEditor->Graphics()->Uses2DTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE;
|
||||
|
@ -8667,7 +8630,9 @@ void CEditor::OnUpdate()
|
|||
Input()->ConsumeEvents([&](const IInput::CEvent &Event) {
|
||||
for(CEditorComponent &Component : m_vComponents)
|
||||
{
|
||||
if(Component.OnInput(Event))
|
||||
// Events with flag `FLAG_RELEASE` must always be forwarded to all components so keys being
|
||||
// released can be handled in all components also after some components have been disabled.
|
||||
if(Component.OnInput(Event) && (Event.m_Flags & ~IInput::FLAG_RELEASE) != 0)
|
||||
return;
|
||||
}
|
||||
Ui()->OnInput(Event);
|
||||
|
|
|
@ -539,6 +539,22 @@ void CEditorActionAddLayer::Undo()
|
|||
{
|
||||
// Undo: remove layer from vector but keep it in case we want to add it back
|
||||
auto &vLayers = m_pEditor->m_Map.m_vpGroups[m_GroupIndex]->m_vpLayers;
|
||||
|
||||
if(m_pLayer->m_Type == LAYERTYPE_TILES)
|
||||
{
|
||||
std::shared_ptr<CLayerTiles> pLayerTiles = std::static_pointer_cast<CLayerTiles>(m_pLayer);
|
||||
if(pLayerTiles->m_Front)
|
||||
m_pEditor->m_Map.m_pFrontLayer = nullptr;
|
||||
else if(pLayerTiles->m_Tele)
|
||||
m_pEditor->m_Map.m_pTeleLayer = nullptr;
|
||||
else if(pLayerTiles->m_Speedup)
|
||||
m_pEditor->m_Map.m_pSpeedupLayer = nullptr;
|
||||
else if(pLayerTiles->m_Switch)
|
||||
m_pEditor->m_Map.m_pSwitchLayer = nullptr;
|
||||
else if(pLayerTiles->m_Tune)
|
||||
m_pEditor->m_Map.m_pTuneLayer = nullptr;
|
||||
}
|
||||
|
||||
vLayers.erase(vLayers.begin() + m_LayerIndex);
|
||||
|
||||
m_pEditor->m_Map.m_vpGroups[m_GroupIndex]->m_Collapse = false;
|
||||
|
@ -552,6 +568,22 @@ void CEditorActionAddLayer::Redo()
|
|||
{
|
||||
// Redo: add back the removed layer contained in this class
|
||||
auto &vLayers = m_pEditor->m_Map.m_vpGroups[m_GroupIndex]->m_vpLayers;
|
||||
|
||||
if(m_pLayer->m_Type == LAYERTYPE_TILES)
|
||||
{
|
||||
std::shared_ptr<CLayerTiles> pLayerTiles = std::static_pointer_cast<CLayerTiles>(m_pLayer);
|
||||
if(pLayerTiles->m_Front)
|
||||
m_pEditor->m_Map.m_pFrontLayer = std::static_pointer_cast<CLayerFront>(m_pLayer);
|
||||
else if(pLayerTiles->m_Tele)
|
||||
m_pEditor->m_Map.m_pTeleLayer = std::static_pointer_cast<CLayerTele>(m_pLayer);
|
||||
else if(pLayerTiles->m_Speedup)
|
||||
m_pEditor->m_Map.m_pSpeedupLayer = std::static_pointer_cast<CLayerSpeedup>(m_pLayer);
|
||||
else if(pLayerTiles->m_Switch)
|
||||
m_pEditor->m_Map.m_pSwitchLayer = std::static_pointer_cast<CLayerSwitch>(m_pLayer);
|
||||
else if(pLayerTiles->m_Tune)
|
||||
m_pEditor->m_Map.m_pTuneLayer = std::static_pointer_cast<CLayerTune>(m_pLayer);
|
||||
}
|
||||
|
||||
vLayers.insert(vLayers.begin() + m_LayerIndex, m_pLayer);
|
||||
|
||||
m_pEditor->m_Map.m_vpGroups[m_GroupIndex]->m_Collapse = false;
|
||||
|
|
|
@ -52,29 +52,3 @@ void CEditorImage::AnalyseTileFlags()
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CEditorImage::DataEquals(const CEditorImage &Other) const
|
||||
{
|
||||
// If height, width or pixel size don't match, then data cannot be equal
|
||||
const size_t ImgPixelSize = PixelSize();
|
||||
|
||||
if(Other.m_Height != m_Height || Other.m_Width != m_Width || Other.PixelSize() != ImgPixelSize)
|
||||
return false;
|
||||
|
||||
const auto &&GetPixel = [&](uint8_t *pData, int x, int y, size_t p) -> uint8_t {
|
||||
return pData[x * ImgPixelSize + (m_Width * ImgPixelSize * y) + p];
|
||||
};
|
||||
|
||||
// Look through every pixel and check if there are any difference
|
||||
for(size_t y = 0; y < m_Height; y += ImgPixelSize)
|
||||
{
|
||||
for(size_t x = 0; x < m_Width; x += ImgPixelSize)
|
||||
{
|
||||
for(size_t p = 0; p < ImgPixelSize; p++)
|
||||
if(GetPixel(m_pData, x, y, p) != GetPixel(Other.m_pData, x, y, p))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ public:
|
|||
|
||||
void OnInit(CEditor *pEditor) override;
|
||||
void AnalyseTileFlags();
|
||||
bool DataEquals(const CEditorImage &Other) const;
|
||||
|
||||
IGraphics::CTextureHandle m_Texture;
|
||||
int m_External = 0;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <engine/client.h>
|
||||
#include <engine/console.h>
|
||||
#include <engine/gfx/image_manipulation.h>
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/serverbrowser.h>
|
||||
#include <engine/shared/datafile.h>
|
||||
|
@ -509,14 +510,7 @@ bool CEditorMap::Load(const char *pFileName, int StorageType, const std::functio
|
|||
pImg->m_Height = ImgInfo.m_Height;
|
||||
pImg->m_Format = ImgInfo.m_Format;
|
||||
pImg->m_pData = ImgInfo.m_pData;
|
||||
if(pImg->m_Format != CImageInfo::FORMAT_RGBA)
|
||||
{
|
||||
uint8_t *pRgbaData = static_cast<uint8_t *>(malloc((size_t)pImg->m_Width * pImg->m_Height * CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA)));
|
||||
ConvertToRGBA(pRgbaData, *pImg);
|
||||
free(pImg->m_pData);
|
||||
pImg->m_pData = pRgbaData;
|
||||
pImg->m_Format = CImageInfo::FORMAT_RGBA;
|
||||
}
|
||||
ConvertToRgba(*pImg);
|
||||
|
||||
int TextureLoadFlag = m_pEditor->Graphics()->Uses2DTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE;
|
||||
if(ImgInfo.m_Width % 16 != 0 || ImgInfo.m_Height % 16 != 0)
|
||||
|
|
|
@ -17,51 +17,6 @@ bool operator<(const ColorRGBA &Left, const ColorRGBA &Right)
|
|||
return Left.a < Right.a;
|
||||
}
|
||||
|
||||
static ColorRGBA GetPixelColor(const CImageInfo &Image, size_t x, size_t y)
|
||||
{
|
||||
uint8_t *pData = Image.m_pData;
|
||||
const size_t PixelSize = Image.PixelSize();
|
||||
const size_t PixelStartIndex = x * PixelSize + (Image.m_Width * PixelSize * y);
|
||||
|
||||
ColorRGBA Color = {255, 255, 255, 255};
|
||||
if(PixelSize == 1)
|
||||
{
|
||||
Color.a = pData[PixelStartIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
Color.r = pData[PixelStartIndex + 0];
|
||||
Color.g = pData[PixelStartIndex + 1];
|
||||
Color.b = pData[PixelStartIndex + 2];
|
||||
|
||||
if(PixelSize == 4)
|
||||
Color.a = pData[PixelStartIndex + 3];
|
||||
}
|
||||
|
||||
return Color;
|
||||
}
|
||||
|
||||
static void SetPixelColor(CImageInfo *pImage, size_t x, size_t y, ColorRGBA Color)
|
||||
{
|
||||
uint8_t *pData = pImage->m_pData;
|
||||
const size_t PixelSize = pImage->PixelSize();
|
||||
const size_t PixelStartIndex = x * PixelSize + (pImage->m_Width * PixelSize * y);
|
||||
|
||||
if(PixelSize == 1)
|
||||
{
|
||||
pData[PixelStartIndex] = Color.a;
|
||||
}
|
||||
else
|
||||
{
|
||||
pData[PixelStartIndex + 0] = Color.r;
|
||||
pData[PixelStartIndex + 1] = Color.g;
|
||||
pData[PixelStartIndex + 2] = Color.b;
|
||||
|
||||
if(PixelSize == 4)
|
||||
pData[PixelStartIndex + 3] = Color.a;
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<ColorRGBA> GetUniqueColors(const CImageInfo &Image)
|
||||
{
|
||||
std::set<ColorRGBA> ColorSet;
|
||||
|
@ -70,7 +25,7 @@ static std::vector<ColorRGBA> GetUniqueColors(const CImageInfo &Image)
|
|||
{
|
||||
for(size_t y = 0; y < Image.m_Height; y++)
|
||||
{
|
||||
ColorRGBA Color = GetPixelColor(Image, x, y);
|
||||
ColorRGBA Color = Image.PixelColor(x, y);
|
||||
if(Color.a > 0 && ColorSet.insert(Color).second)
|
||||
vUniqueColors.push_back(Color);
|
||||
}
|
||||
|
@ -106,12 +61,12 @@ static std::vector<std::array<ColorRGBA, NumTiles>> GroupColors(const std::vecto
|
|||
return vaColorGroups;
|
||||
}
|
||||
|
||||
static void SetColorTile(CImageInfo *pImage, int x, int y, ColorRGBA Color)
|
||||
static void SetColorTile(CImageInfo &Image, int x, int y, ColorRGBA Color)
|
||||
{
|
||||
for(int i = 0; i < TileSize; i++)
|
||||
{
|
||||
for(int j = 0; j < TileSize; j++)
|
||||
SetPixelColor(pImage, x * TileSize + i, y * TileSize + j, Color);
|
||||
Image.SetPixelColor(x * TileSize + i, y * TileSize + j, Color);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,7 +83,7 @@ static CImageInfo ColorGroupToImage(const std::array<ColorRGBA, NumTiles> &aColo
|
|||
for(int x = 0; x < NumTilesRow; x++)
|
||||
{
|
||||
int ColorIndex = x + NumTilesRow * y;
|
||||
SetColorTile(&Image, x, y, aColorGroup[ColorIndex]);
|
||||
SetColorTile(Image, x, y, aColorGroup[ColorIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,7 +134,7 @@ static void SetTilelayerIndices(const std::shared_ptr<CLayerTiles> &pLayer, cons
|
|||
for(int x = 0; x < pLayer->m_Width; x++)
|
||||
{
|
||||
for(int y = 0; y < pLayer->m_Height; y++)
|
||||
pLayer->m_pTiles[x + y * pLayer->m_Width].m_Index = GetColorIndex(aColorGroup, GetPixelColor(Image, x, y));
|
||||
pLayer->m_pTiles[x + y * pLayer->m_Width].m_Index = GetColorIndex(aColorGroup, Image.PixelColor(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -887,6 +887,18 @@ void CGameContext::ConDrySave(IConsole::IResult *pResult, void *pUserData)
|
|||
io_close(File);
|
||||
}
|
||||
|
||||
void CGameContext::ConReloadCensorlist(IConsole::IResult *pResult, void *pUserData)
|
||||
{
|
||||
CGameContext *pSelf = (CGameContext *)pUserData;
|
||||
pSelf->ReadCensorList();
|
||||
}
|
||||
|
||||
void CGameContext::ConReloadAnnouncement(IConsole::IResult *pResult, void *pUserData)
|
||||
{
|
||||
CGameContext *pSelf = (CGameContext *)pUserData;
|
||||
pSelf->Server()->ReadAnnouncementsFile(g_Config.m_SvAnnouncementFileName);
|
||||
}
|
||||
|
||||
void CGameContext::ConDumpAntibot(IConsole::IResult *pResult, void *pUserData)
|
||||
{
|
||||
CGameContext *pSelf = (CGameContext *)pUserData;
|
||||
|
|
|
@ -1253,7 +1253,7 @@ void CGameContext::OnTick()
|
|||
|
||||
if(Server()->Tick() % (g_Config.m_SvAnnouncementInterval * Server()->TickSpeed() * 60) == 0)
|
||||
{
|
||||
const char *pLine = Server()->GetAnnouncementLine(g_Config.m_SvAnnouncementFileName);
|
||||
const char *pLine = Server()->GetAnnouncementLine();
|
||||
if(pLine)
|
||||
SendChat(-1, TEAM_ALL, pLine);
|
||||
}
|
||||
|
@ -3526,7 +3526,7 @@ void CGameContext::ConAddMapVotes(IConsole::IResult *pResult, void *pUserData)
|
|||
str_format(aCommand, sizeof(aCommand), "clear_votes; add_map_votes \"%s\"", aDirectory);
|
||||
}
|
||||
else
|
||||
str_format(aCommand, sizeof(aCommand), "change_map \"%s/%s\"", pDirectory, aOptionEscaped);
|
||||
str_format(aCommand, sizeof(aCommand), "change_map \"%s%s%s\"", pDirectory, pDirectory[0] == '\0' ? "" : "/", aOptionEscaped);
|
||||
|
||||
pSelf->AddVote(aDescription, aCommand);
|
||||
}
|
||||
|
@ -3642,6 +3642,8 @@ void CGameContext::OnConsoleInit()
|
|||
Console()->Register("set_team", "i[id] i[team-id] ?i[delay in minutes]", CFGFLAG_SERVER, ConSetTeam, this, "Set team of player to team");
|
||||
Console()->Register("set_team_all", "i[team-id]", CFGFLAG_SERVER, ConSetTeamAll, this, "Set team of all players to team");
|
||||
Console()->Register("hot_reload", "", CFGFLAG_SERVER | CMDFLAG_TEST, ConHotReload, this, "Reload the map while preserving the state of tees and teams");
|
||||
Console()->Register("reload_censorlist", "", CFGFLAG_SERVER, ConReloadCensorlist, this, "Reload the censorlist");
|
||||
Console()->Register("reload_announcement", "", CFGFLAG_SERVER, ConReloadAnnouncement, this, "Reload the announcements");
|
||||
|
||||
Console()->Register("add_vote", "s[name] r[command]", CFGFLAG_SERVER, ConAddVote, this, "Add a voting option");
|
||||
Console()->Register("remove_vote", "r[name]", CFGFLAG_SERVER, ConRemoveVote, this, "remove a voting option");
|
||||
|
@ -3933,19 +3935,7 @@ void CGameContext::OnInit(const void *pPersistentData)
|
|||
else
|
||||
m_pController = new CGameControllerDDRace(this);
|
||||
|
||||
const char *pCensorFilename = "censorlist.txt";
|
||||
CLineReader LineReader;
|
||||
if(LineReader.OpenFile(Storage()->OpenFile(pCensorFilename, IOFLAG_READ, IStorage::TYPE_ALL)))
|
||||
{
|
||||
while(const char *pLine = LineReader.Get())
|
||||
{
|
||||
m_vCensorlist.emplace_back(pLine);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_msg("censorlist", "failed to open '%s'", pCensorFilename);
|
||||
}
|
||||
ReadCensorList();
|
||||
|
||||
m_TeeHistorianActive = g_Config.m_SvTeeHistorian;
|
||||
if(m_TeeHistorianActive)
|
||||
|
@ -4989,3 +4979,21 @@ void CGameContext::OnUpdatePlayerServerInfo(CJsonStringWriter *pJSonWriter, int
|
|||
pJSonWriter->WriteAttribute("team");
|
||||
pJSonWriter->WriteIntValue(Team);
|
||||
}
|
||||
|
||||
void CGameContext::ReadCensorList()
|
||||
{
|
||||
const char *pCensorFilename = "censorlist.txt";
|
||||
CLineReader LineReader;
|
||||
m_vCensorlist.clear();
|
||||
if(LineReader.OpenFile(Storage()->OpenFile(pCensorFilename, IOFLAG_READ, IStorage::TYPE_ALL)))
|
||||
{
|
||||
while(const char *pLine = LineReader.Get())
|
||||
{
|
||||
m_vCensorlist.emplace_back(pLine);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_msg("censorlist", "failed to open '%s'", pCensorFilename);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -363,6 +363,7 @@ public:
|
|||
bool RateLimitPlayerMapVote(int ClientId) const;
|
||||
|
||||
void OnUpdatePlayerServerInfo(CJsonStringWriter *pJSonWriter, int Id) override;
|
||||
void ReadCensorList();
|
||||
|
||||
std::shared_ptr<CScoreRandomMapResult> m_SqlRandomMapResult;
|
||||
|
||||
|
@ -514,6 +515,9 @@ private:
|
|||
static void ConFreezeHammer(IConsole::IResult *pResult, void *pUserData);
|
||||
static void ConUnFreezeHammer(IConsole::IResult *pResult, void *pUserData);
|
||||
|
||||
static void ConReloadCensorlist(IConsole::IResult *pResult, void *pUserData);
|
||||
static void ConReloadAnnouncement(IConsole::IResult *pResult, void *pUserData);
|
||||
|
||||
CCharacter *GetPracticeCharacter(IConsole::IResult *pResult);
|
||||
|
||||
enum
|
||||
|
|
|
@ -529,12 +529,12 @@ ESaveResult CSaveTeam::Save(CGameContext *pGameServer, int Team, bool Dry, bool
|
|||
CCharacter *p = (CCharacter *)pGameServer->m_World.FindFirst(CGameWorld::ENTTYPE_CHARACTER);
|
||||
for(; p; p = (CCharacter *)p->TypeNext())
|
||||
{
|
||||
if(pTeams->m_Core.Team(p->GetPlayer()->GetCid()) != Team)
|
||||
if(pTeams->m_Core.Team(p->GetPlayer()->GetCid()) != Team && !Force)
|
||||
continue;
|
||||
if(m_MembersCount == j)
|
||||
if(m_MembersCount == j && !Force)
|
||||
return ESaveResult::CHAR_NOT_FOUND;
|
||||
ESaveResult Result = pGameServer->m_World.BlocksSave(p->GetPlayer()->GetCid());
|
||||
if(Result != ESaveResult::SUCCESS)
|
||||
if(Result != ESaveResult::SUCCESS && !Force)
|
||||
return Result;
|
||||
m_pSavedTees[j].Save(p);
|
||||
aPlayerCids[j] = p->GetPlayer()->GetCid();
|
||||
|
|
|
@ -1028,7 +1028,7 @@ bool CScoreWorker::ShowTop(IDbConnection *pSqlServer, const ISqlData *pGameData,
|
|||
|
||||
if(!g_Config.m_SvRegionalRankings)
|
||||
{
|
||||
str_copy(pResult->m_Data.m_aaMessages[Line], "----------------------------------------", sizeof(pResult->m_Data.m_aaMessages[Line]));
|
||||
str_copy(pResult->m_Data.m_aaMessages[Line], "-----------------------------------------", sizeof(pResult->m_Data.m_aaMessages[Line]));
|
||||
return !End;
|
||||
}
|
||||
|
||||
|
@ -1146,7 +1146,7 @@ bool CScoreWorker::ShowTeamTop5(IDbConnection *pSqlServer, const ISqlData *pGame
|
|||
}
|
||||
}
|
||||
|
||||
str_copy(paMessages[Line], "-------------------------------", sizeof(paMessages[Line]));
|
||||
str_copy(paMessages[Line], "---------------------------------", sizeof(paMessages[Line]));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1230,7 +1230,7 @@ bool CScoreWorker::ShowPlayerTeamTop5(IDbConnection *pSqlServer, const ISqlData
|
|||
break;
|
||||
}
|
||||
}
|
||||
str_copy(paMessages[Line], "-------------------------------", sizeof(paMessages[Line]));
|
||||
str_copy(paMessages[Line], "---------------------------------", sizeof(paMessages[Line]));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1352,7 +1352,7 @@ bool CScoreWorker::ShowTimes(IDbConnection *pSqlServer, const ISqlData *pGameDat
|
|||
{
|
||||
return true;
|
||||
}
|
||||
str_copy(paMessages[Line], "----------------------------------------------------", sizeof(paMessages[Line]));
|
||||
str_copy(paMessages[Line], "-------------------------------------------", sizeof(paMessages[Line]));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#ifndef GAME_VERSION_H
|
||||
#define GAME_VERSION_H
|
||||
#ifndef GAME_RELEASE_VERSION
|
||||
#define GAME_RELEASE_VERSION "18.5"
|
||||
#define GAME_RELEASE_VERSION "18.5.1"
|
||||
#endif
|
||||
|
||||
// teeworlds
|
||||
|
@ -13,7 +13,7 @@
|
|||
#define GAME_NETVERSION7 "0.7 802f1be60a05665f"
|
||||
|
||||
// ddnet
|
||||
#define DDNET_VERSION_NUMBER 18050
|
||||
#define DDNET_VERSION_NUMBER 18051
|
||||
extern const char *GIT_SHORTREV_HASH;
|
||||
#define GAME_NAME "DDNet"
|
||||
#endif
|
||||
|
|
|
@ -160,7 +160,7 @@ TEST_P(SingleScore, Top)
|
|||
ExpectLines(m_pPlayerResult,
|
||||
{"------------ Global Top ------------",
|
||||
"1. nameless tee Time: 01:40.00",
|
||||
"----------------------------------------"});
|
||||
"-----------------------------------------"});
|
||||
}
|
||||
|
||||
TEST_P(SingleScore, RankRegional)
|
||||
|
@ -197,7 +197,7 @@ TEST_P(SingleScore, TopServer)
|
|||
ExpectLines(m_pPlayerResult,
|
||||
{"------------ Global Top ------------",
|
||||
"1. nameless tee Time: 01:40.00",
|
||||
"----------------------------------------"});
|
||||
"-----------------------------------------"});
|
||||
}
|
||||
|
||||
TEST_P(SingleScore, RankServerRegional)
|
||||
|
@ -264,7 +264,7 @@ TEST_P(SingleScore, TimesExists)
|
|||
|
||||
str_copy(aBuf, m_pPlayerResult->m_Data.m_aaMessages[1] + str_length(m_pPlayerResult->m_Data.m_aaMessages[1]) - 10, 11);
|
||||
EXPECT_STREQ(aBuf, ", 01:40.00");
|
||||
EXPECT_STREQ(m_pPlayerResult->m_Data.m_aaMessages[2], "----------------------------------------------------");
|
||||
EXPECT_STREQ(m_pPlayerResult->m_Data.m_aaMessages[2], "-------------------------------------------");
|
||||
for(int i = 3; i < CScorePlayerResult::MAX_MESSAGES; i++)
|
||||
{
|
||||
EXPECT_STREQ(m_pPlayerResult->m_Data.m_aaMessages[i], "");
|
||||
|
@ -321,7 +321,7 @@ TEST_P(TeamScore, All)
|
|||
ExpectLines(m_pPlayerResult,
|
||||
{"------- Team Top 5 -------",
|
||||
"1. brainless tee & nameless tee Team Time: 01:40.00",
|
||||
"-------------------------------"});
|
||||
"---------------------------------"});
|
||||
}
|
||||
|
||||
TEST_P(TeamScore, PlayerExists)
|
||||
|
@ -331,7 +331,7 @@ TEST_P(TeamScore, PlayerExists)
|
|||
ExpectLines(m_pPlayerResult,
|
||||
{"------- Team Top 5 -------",
|
||||
"1. brainless tee & nameless tee Team Time: 01:40.00",
|
||||
"-------------------------------"});
|
||||
"---------------------------------"});
|
||||
}
|
||||
|
||||
TEST_P(TeamScore, PlayerDoesntExist)
|
||||
|
@ -349,7 +349,7 @@ TEST_P(TeamScore, RankUpdates)
|
|||
ExpectLines(m_pPlayerResult,
|
||||
{"------- Team Top 5 -------",
|
||||
"1. brainless tee & nameless tee Team Time: 01:38.00",
|
||||
"-------------------------------"});
|
||||
"---------------------------------"});
|
||||
}
|
||||
|
||||
struct MapInfo : public Score
|
||||
|
|
|
@ -1,88 +1,49 @@
|
|||
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
|
||||
/* If you are missing that file, acquire a complete release at teeworlds.com. */
|
||||
|
||||
#include <base/logger.h>
|
||||
#include <base/system.h>
|
||||
|
||||
#include <engine/gfx/image_loader.h>
|
||||
#include <engine/gfx/image_manipulation.h>
|
||||
#include <engine/graphics.h>
|
||||
|
||||
int DilateFile(const char *pFilename)
|
||||
static bool DilateFile(const char *pFilename)
|
||||
{
|
||||
IOHANDLE File = io_open(pFilename, IOFLAG_READ);
|
||||
if(File)
|
||||
CImageInfo Image;
|
||||
int PngliteIncompatible;
|
||||
if(!CImageLoader::LoadPng(io_open(pFilename, IOFLAG_READ), pFilename, Image, PngliteIncompatible))
|
||||
return false;
|
||||
|
||||
if(Image.m_Format != CImageInfo::FORMAT_RGBA)
|
||||
{
|
||||
io_seek(File, 0, IOSEEK_END);
|
||||
long int FileSize = io_tell(File);
|
||||
if(FileSize <= 0)
|
||||
{
|
||||
io_close(File);
|
||||
dbg_msg("dilate", "failed to get file size (%ld). filename='%s'", FileSize, pFilename);
|
||||
return false;
|
||||
}
|
||||
io_seek(File, 0, IOSEEK_START);
|
||||
TImageByteBuffer ByteBuffer;
|
||||
SImageByteBuffer ImageByteBuffer(&ByteBuffer);
|
||||
|
||||
ByteBuffer.resize(FileSize);
|
||||
io_read(File, &ByteBuffer.front(), FileSize);
|
||||
|
||||
io_close(File);
|
||||
|
||||
CImageInfo Img;
|
||||
EImageFormat ImageFormat;
|
||||
int PngliteIncompatible;
|
||||
if(LoadPng(ImageByteBuffer, pFilename, PngliteIncompatible, Img.m_Width, Img.m_Height, Img.m_pData, ImageFormat))
|
||||
{
|
||||
if(ImageFormat != IMAGE_FORMAT_RGBA)
|
||||
{
|
||||
free(Img.m_pData);
|
||||
dbg_msg("dilate", "%s: not an RGBA image", pFilename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
DilateImage(Img.m_pData, Img.m_Width, Img.m_Height);
|
||||
|
||||
// save here
|
||||
IOHANDLE SaveFile = io_open(pFilename, IOFLAG_WRITE);
|
||||
if(SaveFile)
|
||||
{
|
||||
TImageByteBuffer ByteBuffer2;
|
||||
SImageByteBuffer ImageByteBuffer2(&ByteBuffer2);
|
||||
|
||||
if(SavePng(IMAGE_FORMAT_RGBA, Img.m_pData, ImageByteBuffer2, Img.m_Width, Img.m_Height))
|
||||
io_write(SaveFile, &ByteBuffer2.front(), ByteBuffer2.size());
|
||||
io_close(SaveFile);
|
||||
|
||||
free(Img.m_pData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_msg("dilate", "failed unknown image format: %s", pFilename);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_msg("dilate", "failed to open image file. filename='%s'", pFilename);
|
||||
return -1;
|
||||
log_error("dilate", "ERROR: only RGBA PNG images are supported");
|
||||
Image.Free();
|
||||
return false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
DilateImage(Image);
|
||||
|
||||
const bool SaveResult = CImageLoader::SavePng(io_open(pFilename, IOFLAG_WRITE), pFilename, Image);
|
||||
Image.Free();
|
||||
|
||||
return SaveResult;
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
CCmdlineFix CmdlineFix(&argc, &argv);
|
||||
log_set_global_logger_default();
|
||||
|
||||
if(argc == 1)
|
||||
{
|
||||
dbg_msg("usage", "%s FILE1 [ FILE2... ]", argv[0]);
|
||||
log_error("dilate", "Usage: %s <image1.png> [<image2.png> ...]", argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool Success = true;
|
||||
for(int i = 1; i < argc; i++)
|
||||
DilateFile(argv[i]);
|
||||
|
||||
return 0;
|
||||
{
|
||||
Success &= DilateFile(argv[i]);
|
||||
}
|
||||
return Success ? 0 : -1;
|
||||
}
|
||||
|
|
|
@ -3,12 +3,14 @@
|
|||
|
||||
#include <base/logger.h>
|
||||
#include <base/system.h>
|
||||
|
||||
#include <engine/gfx/image_loader.h>
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/shared/datafile.h>
|
||||
#include <engine/storage.h>
|
||||
|
||||
#include <game/gamecore.h>
|
||||
#include <game/mapitems.h>
|
||||
|
||||
/*
|
||||
Usage: map_convert_07 <source map filepath> <dest map filepath>
|
||||
*/
|
||||
|
@ -25,53 +27,6 @@ int g_NextDataItemId = -1;
|
|||
|
||||
int g_aImageIds[MAX_MAPIMAGES];
|
||||
|
||||
int LoadPng(CImageInfo *pImg, const char *pFilename)
|
||||
{
|
||||
IOHANDLE File = io_open(pFilename, IOFLAG_READ);
|
||||
if(File)
|
||||
{
|
||||
io_seek(File, 0, IOSEEK_END);
|
||||
long int FileSize = io_tell(File);
|
||||
if(FileSize <= 0)
|
||||
{
|
||||
io_close(File);
|
||||
dbg_msg("map_convert_07", "failed to get file size (%ld). filename='%s'", FileSize, pFilename);
|
||||
return false;
|
||||
}
|
||||
io_seek(File, 0, IOSEEK_START);
|
||||
TImageByteBuffer ByteBuffer;
|
||||
SImageByteBuffer ImageByteBuffer(&ByteBuffer);
|
||||
|
||||
ByteBuffer.resize(FileSize);
|
||||
io_read(File, &ByteBuffer.front(), FileSize);
|
||||
|
||||
io_close(File);
|
||||
|
||||
uint8_t *pImgBuffer = NULL;
|
||||
EImageFormat ImageFormat;
|
||||
int PngliteIncompatible;
|
||||
if(LoadPng(ImageByteBuffer, pFilename, PngliteIncompatible, pImg->m_Width, pImg->m_Height, pImgBuffer, ImageFormat))
|
||||
{
|
||||
pImg->m_pData = pImgBuffer;
|
||||
|
||||
if(ImageFormat == IMAGE_FORMAT_RGBA && pImg->m_Width <= (2 << 13) && pImg->m_Height <= (2 << 13))
|
||||
{
|
||||
pImg->m_Format = CImageInfo::FORMAT_RGBA;
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_msg("map_convert_07", "invalid image format. filename='%s'", pFilename);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool CheckImageDimensions(void *pLayerItem, int LayerType, const char *pFilename)
|
||||
{
|
||||
if(LayerType != MAPITEMTYPE_LAYER)
|
||||
|
@ -117,15 +72,19 @@ void *ReplaceImageItem(int Index, CMapItemImage *pImgItem, CMapItemImage *pNewIm
|
|||
|
||||
dbg_msg("map_convert_07", "embedding image '%s'", pName);
|
||||
|
||||
CImageInfo ImgInfo;
|
||||
char aStr[IO_MAX_PATH_LENGTH];
|
||||
str_format(aStr, sizeof(aStr), "data/mapres/%s.png", pName);
|
||||
if(!LoadPng(&ImgInfo, aStr))
|
||||
|
||||
CImageInfo ImgInfo;
|
||||
int PngliteIncompatible;
|
||||
if(!CImageLoader::LoadPng(io_open(aStr, IOFLAG_READ), aStr, ImgInfo, PngliteIncompatible))
|
||||
return pImgItem; // keep as external if we don't have a mapres to replace
|
||||
|
||||
if(ImgInfo.m_Format != CImageInfo::FORMAT_RGBA)
|
||||
const size_t MaxImageDimension = 1 << 13;
|
||||
if(ImgInfo.m_Format != CImageInfo::FORMAT_RGBA || ImgInfo.m_Width > MaxImageDimension || ImgInfo.m_Height > MaxImageDimension)
|
||||
{
|
||||
dbg_msg("map_convert_07", "image '%s' is not in RGBA format", aStr);
|
||||
dbg_msg("map_convert_07", "ERROR: only RGBA PNG images with maximum width/height %" PRIzu " are supported", MaxImageDimension);
|
||||
ImgInfo.Free();
|
||||
return pImgItem;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
#include <base/logger.h>
|
||||
#include <base/system.h>
|
||||
|
||||
#include <engine/gfx/image_loader.h>
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/shared/datafile.h>
|
||||
#include <engine/storage.h>
|
||||
|
||||
#include <game/mapitems.h>
|
||||
|
||||
bool CreatePixelArt(const char[3][IO_MAX_PATH_LENGTH], const int[2], const int[2], int[2], const bool[2]);
|
||||
void InsertCurrentQuads(CDataFileReader &, CMapItemLayerQuads *, CQuad *);
|
||||
int InsertPixelArtQuads(CQuad *, int &, const CImageInfo &, const int[2], const int[2], const bool[2]);
|
||||
|
||||
bool LoadPng(CImageInfo *, const char *);
|
||||
bool OpenMaps(const char[2][IO_MAX_PATH_LENGTH], CDataFileReader &, CDataFileWriter &);
|
||||
void SaveOutputMap(CDataFileReader &, CDataFileWriter &, CMapItemLayerQuads *, int, CQuad *, int);
|
||||
|
||||
|
@ -73,7 +73,8 @@ int main(int argc, const char **argv)
|
|||
bool CreatePixelArt(const char aFilenames[3][IO_MAX_PATH_LENGTH], const int aLayerId[2], const int aStartingPos[2], int aPixelSizes[2], const bool aArtOptions[2])
|
||||
{
|
||||
CImageInfo Img;
|
||||
if(!LoadPng(&Img, aFilenames[2]))
|
||||
int PngliteIncompatible;
|
||||
if(!CImageLoader::LoadPng(io_open(aFilenames[2], IOFLAG_READ), aFilenames[2], Img, PngliteIncompatible))
|
||||
return false;
|
||||
|
||||
aPixelSizes[0] = aPixelSizes[0] ? aPixelSizes[0] : GetImagePixelSize(Img);
|
||||
|
@ -227,7 +228,7 @@ bool GetPixelClamped(const CImageInfo &Img, size_t x, size_t y, uint8_t aPixel[4
|
|||
aPixel[3] = 255;
|
||||
|
||||
const size_t PixelSize = Img.PixelSize();
|
||||
for(size_t i = 0; i < PixelSize; i++)
|
||||
for(size_t i = 0; i < minimum<size_t>(4, PixelSize); i++) // minimum is used here to avoid false positive stringop-overflow warning
|
||||
aPixel[i] = Img.m_pData[x * PixelSize + (Img.m_Width * PixelSize * y) + i];
|
||||
|
||||
return aPixel[3] > 0;
|
||||
|
@ -315,54 +316,6 @@ CQuad CreateNewQuad(const float PosX, const float PosY, const int Width, const i
|
|||
return Quad;
|
||||
}
|
||||
|
||||
bool LoadPng(CImageInfo *pImg, const char *pFilename)
|
||||
{
|
||||
IOHANDLE File = io_open(pFilename, IOFLAG_READ);
|
||||
if(!File)
|
||||
{
|
||||
dbg_msg("map_create_pixelart", "ERROR: Unable to open file %s", pFilename);
|
||||
return false;
|
||||
}
|
||||
|
||||
io_seek(File, 0, IOSEEK_END);
|
||||
long int FileSize = io_tell(File);
|
||||
if(FileSize <= 0)
|
||||
{
|
||||
io_close(File);
|
||||
dbg_msg("map_create_pixelart", "ERROR: Failed to get file size (%ld). filename='%s'", FileSize, pFilename);
|
||||
return false;
|
||||
}
|
||||
io_seek(File, 0, IOSEEK_START);
|
||||
TImageByteBuffer ByteBuffer;
|
||||
SImageByteBuffer ImageByteBuffer(&ByteBuffer);
|
||||
|
||||
ByteBuffer.resize(FileSize);
|
||||
io_read(File, &ByteBuffer.front(), FileSize);
|
||||
io_close(File);
|
||||
|
||||
uint8_t *pImgBuffer = NULL;
|
||||
EImageFormat ImageFormat;
|
||||
int PngliteIncompatible;
|
||||
|
||||
if(!LoadPng(ImageByteBuffer, pFilename, PngliteIncompatible, pImg->m_Width, pImg->m_Height, pImgBuffer, ImageFormat))
|
||||
{
|
||||
dbg_msg("map_create_pixelart", "ERROR: Unable to load a valid PNG from file %s", pFilename);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(ImageFormat != IMAGE_FORMAT_RGBA && ImageFormat != IMAGE_FORMAT_RGB)
|
||||
{
|
||||
dbg_msg("map_create_pixelart", "ERROR: only RGB and RGBA PNG images are supported");
|
||||
free(pImgBuffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
pImg->m_pData = pImgBuffer;
|
||||
pImg->m_Format = ImageFormat == IMAGE_FORMAT_RGB ? CImageInfo::FORMAT_RGB : CImageInfo::FORMAT_RGBA;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenMaps(const char pMapNames[2][IO_MAX_PATH_LENGTH], CDataFileReader &InputMap, CDataFileWriter &OutputMap)
|
||||
{
|
||||
IStorage *pStorage = CreateLocalStorage();
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
// Adapted from TWMapImagesRecovery by Tardo: https://github.com/Tardo/TWMapImagesRecovery
|
||||
|
||||
#include <base/logger.h>
|
||||
#include <base/system.h>
|
||||
|
||||
#include <engine/gfx/image_loader.h>
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/shared/datafile.h>
|
||||
#include <engine/storage.h>
|
||||
|
||||
#include <game/mapitems.h>
|
||||
|
||||
static void PrintMapInfo(CDataFileReader &Reader)
|
||||
|
@ -50,22 +52,18 @@ static void ExtractMapImages(CDataFileReader &Reader, const char *pPathSave)
|
|||
continue;
|
||||
}
|
||||
|
||||
IOHANDLE File = io_open(aBuf, IOFLAG_WRITE);
|
||||
if(File)
|
||||
{
|
||||
log_info("map_extract", "writing image: %s (%dx%d)", aBuf, pItem->m_Width, pItem->m_Height);
|
||||
TImageByteBuffer ByteBuffer;
|
||||
SImageByteBuffer ImageByteBuffer(&ByteBuffer);
|
||||
CImageInfo Image;
|
||||
Image.m_Width = pItem->m_Width;
|
||||
Image.m_Height = pItem->m_Height;
|
||||
Image.m_Format = CImageInfo::FORMAT_RGBA;
|
||||
Image.m_pData = static_cast<uint8_t *>(Reader.GetData(pItem->m_ImageData));
|
||||
|
||||
if(SavePng(IMAGE_FORMAT_RGBA, (const uint8_t *)Reader.GetData(pItem->m_ImageData), ImageByteBuffer, pItem->m_Width, pItem->m_Height))
|
||||
io_write(File, &ByteBuffer.front(), ByteBuffer.size());
|
||||
io_close(File);
|
||||
Reader.UnloadData(pItem->m_ImageData);
|
||||
}
|
||||
else
|
||||
log_info("map_extract", "writing image: %s (%dx%d)", aBuf, pItem->m_Width, pItem->m_Height);
|
||||
if(!CImageLoader::SavePng(io_open(aBuf, IOFLAG_WRITE), aBuf, Image))
|
||||
{
|
||||
log_error("map_extract", "failed to open image file for writing. filename='%s'", aBuf);
|
||||
log_error("map_extract", "failed to write image file. filename='%s'", aBuf);
|
||||
}
|
||||
Reader.UnloadData(pItem->m_ImageData);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,13 @@
|
|||
|
||||
#include <base/logger.h>
|
||||
#include <base/system.h>
|
||||
|
||||
#include <engine/gfx/image_loader.h>
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/shared/datafile.h>
|
||||
#include <engine/storage.h>
|
||||
|
||||
#include <game/mapitems.h>
|
||||
|
||||
/*
|
||||
Usage: map_replace_image <source map filepath> <dest map filepath> <current image name> <new image filepath>
|
||||
Notes: map filepath must be relative to user default teeworlds folder
|
||||
|
@ -23,55 +25,6 @@ int g_NewDataId = -1;
|
|||
int g_NewDataSize = 0;
|
||||
void *g_pNewData = nullptr;
|
||||
|
||||
bool LoadPng(CImageInfo *pImg, const char *pFilename)
|
||||
{
|
||||
IOHANDLE File = io_open(pFilename, IOFLAG_READ);
|
||||
if(File)
|
||||
{
|
||||
io_seek(File, 0, IOSEEK_END);
|
||||
long int FileSize = io_tell(File);
|
||||
if(FileSize <= 0)
|
||||
{
|
||||
io_close(File);
|
||||
return false;
|
||||
}
|
||||
io_seek(File, 0, IOSEEK_START);
|
||||
TImageByteBuffer ByteBuffer;
|
||||
SImageByteBuffer ImageByteBuffer(&ByteBuffer);
|
||||
|
||||
ByteBuffer.resize(FileSize);
|
||||
io_read(File, &ByteBuffer.front(), FileSize);
|
||||
|
||||
io_close(File);
|
||||
|
||||
uint8_t *pImgBuffer = NULL;
|
||||
EImageFormat ImageFormat;
|
||||
int PngliteIncompatible;
|
||||
if(LoadPng(ImageByteBuffer, pFilename, PngliteIncompatible, pImg->m_Width, pImg->m_Height, pImgBuffer, ImageFormat))
|
||||
{
|
||||
if((ImageFormat == IMAGE_FORMAT_RGBA || ImageFormat == IMAGE_FORMAT_RGB) && pImg->m_Width <= (2 << 13) && pImg->m_Height <= (2 << 13))
|
||||
{
|
||||
pImg->m_pData = pImgBuffer;
|
||||
|
||||
if(ImageFormat == IMAGE_FORMAT_RGB)
|
||||
pImg->m_Format = CImageInfo::FORMAT_RGB;
|
||||
else if(ImageFormat == IMAGE_FORMAT_RGBA)
|
||||
pImg->m_Format = CImageInfo::FORMAT_RGBA;
|
||||
else
|
||||
{
|
||||
free(pImgBuffer);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void *ReplaceImageItem(int Index, CMapItemImage *pImgItem, const char *pImgName, const char *pImgFile, CMapItemImage *pNewImgItem)
|
||||
{
|
||||
const char *pName = g_DataReader.GetDataString(pImgItem->m_ImageName);
|
||||
|
@ -87,13 +40,15 @@ void *ReplaceImageItem(int Index, CMapItemImage *pImgItem, const char *pImgName,
|
|||
dbg_msg("map_replace_image", "found image '%s'", pImgName);
|
||||
|
||||
CImageInfo ImgInfo;
|
||||
if(!LoadPng(&ImgInfo, pImgFile))
|
||||
return 0;
|
||||
int PngliteIncompatible;
|
||||
if(!CImageLoader::LoadPng(io_open(pImgName, IOFLAG_READ), pImgName, ImgInfo, PngliteIncompatible))
|
||||
return nullptr;
|
||||
|
||||
if(ImgInfo.m_Format != CImageInfo::FORMAT_RGBA)
|
||||
{
|
||||
dbg_msg("map_replace_image", "image '%s' is not in RGBA format", pImgName);
|
||||
return 0;
|
||||
dbg_msg("map_replace_image", "ERROR: only RGBA PNG images are supported");
|
||||
ImgInfo.Free();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
*pNewImgItem = *pImgItem;
|
||||
|
|
Loading…
Reference in a new issue