Compare commits

...

62 commits

Author SHA1 Message Date
Tater c832ce2e0a
Merge df269215d0 into a2e0ab2dbe 2024-09-18 14:10:09 +03:00
Robert Müller a2e0ab2dbe
Merge pull request #8973 from furo321/improve-annoucements
Various improvements to announcements
2024-09-18 10:34:53 +00:00
Robert Müller 255694c061
Merge pull request #8975 from furo321/android-save-minimized
Save the config file when the app is minimized on Android
2024-09-18 10:29:09 +00:00
furo b475c67039 Various improvements to announcements 2024-09-18 12:16:29 +02:00
furo a266cd2f70 Save the config file when the app is minimized on Android 2024-09-18 12:10:25 +02:00
Robert Müller 2c77e79061
Merge pull request #8980 from ChillerDragon/pr_fix_team_colors_sixup
Fix 0.7 client team colors (Closed #8977)
2024-09-18 09:51:35 +00:00
ChillerDragon 46c5344d71 Fix 0.7 client team colors (Closed #8977) 2024-09-18 16:50:23 +08:00
Dennis Felsing 96ad30eb21
Merge pull request #8978 from ChillerDragon/pr_spamprot
Describe what sv_spamprotection covers
2024-09-18 06:42:21 +00:00
Dennis Felsing b03245f7dd
Merge pull request #8979 from ChillerDragon/pr_flag_sounds7
Fix 0.7 client flag sounds being duplicated when dummy is connected
2024-09-18 06:39:16 +00:00
ChillerDragon 7192bbf397 Fix 0.7 client flag sounds being duplicated when dummy is connected
Closed https://github.com/ddnet-insta/ddnet-insta/issues/127
2024-09-18 10:40:36 +08:00
ChillerDragon 984845b2a9 Describe what sv_spamprotection covers 2024-09-18 09:40:09 +08:00
Dennis Felsing 60624d5599
Merge pull request #8976 from dobrykafe/pr-align-separators
Better alignment of upper and lower separators
2024-09-17 22:39:02 +00:00
dobrykafe 9d7b476c33 better alignment of upper and lower separators 2024-09-17 23:26:18 +02:00
Dennis Felsing 217103a0ee
Merge pull request #8968 from furo321/command-hint
Add autocompletion hint for chat commands
2024-09-17 07:09:36 +00:00
furo d86b510291 Add autocompletion hint for chat commands 2024-09-17 01:08:49 +02:00
Dennis Felsing 4f352d95d4
Merge pull request #8967 from dobrykafe/pr-fix-add-layers
Fix undo/redo for adding layers
2024-09-16 21:44:04 +00:00
dobrykafe 8f93b63b6a fix undo/redo for adding layers 2024-09-16 21:01:05 +02:00
Robert Müller e69e7d87c2
Merge pull request #8966 from furo321/reload-censorlist
Add `reload_censorlist` to reread `censorlist.txt`
2024-09-16 16:30:04 +00:00
Dennis Felsing 7dc6346b2d
Merge pull request #8965 from Robyt3/Image-Loading-Refactoring
Refactor image loading, saving and manipulation
2024-09-16 16:08:06 +00:00
furo e8e52a8b66 Add reload_censorlist to reread censorlist.txt 2024-09-16 18:07:28 +02:00
Robert Müller 3d746099fa Refactor image loading, saving and manipulation
Move `CImageInfo` from `engine/graphics.h` to own file `engine/image.h`. Also add 2-component image format to `CImageInfo::EImageFormat` for completeness, to replace the separate `EImageFormat` in `image_loader.h` with `CImageInfo::EImageFormat`.

Move `SetPixelColor`/`GetPixelColor` functions from editor to `CImageInfo` as member functions.

Replace `IGraphics::CopyTextureBufferSub` and `IGraphics::CopyTextureFromTextureBufferSub` functions with more versatile `CImageInfo::CopyRectFrom` function.

Make `IGraphics::LoadSpriteTexture` function more efficient by avoiding a copy of the image data by using the `LoadTextureRawMove` function. Remove unnecessary delegate function `CGraphics_Threaded::LoadSpriteTextureImpl` and temporary buffer `m_vSpriteHelper`.

Move `CEditorImage::DataEquals` function to `CImageInfo::DataEquals`. Use `mem_comp` to compare image data for more efficiency, instead of comparing each pixel individually.

Add another `IGraphics::LoadPng` function that loads image directly from memory and also handles the pnglite incompatibility warnings. This function will be used for more efficient loading of downloaded skin in the future.

Add convenience functions to load/save PNGs from/to `IOHANDLE` to reduce duplicate code when loading and saving images especially in the tools. These functions explicitly only allow loading images in RGBA and RGB format. Move general purpose image loading and saving functions to class `CImageLoader`.

Add more convenient `CByteBufferReader` and `CByteBufferWriter` classes for reading from and writing to a byte buffer while keeping track of the read/write position to replace existing `SImageByteBuffer`.

Extract `ConvertToGrayscale` utility function to reduce duplicate code when creating grayscale versions of skins, start menu images and community icons.

Move and rename `ConvertToRGBA` static function from graphics to `ConvertToRgba` in `image_manipulation.h/cpp`. Add `ConvertToRgbaAlloc` convenience function which allocates the target buffer. Add ``

Add `DilateImage`, `ResizeImage` and `ConvertToRgba` convenience functions that directly accept a `CImageInfo` argument that will be modified.

Remove unnecessary image size limitation in `map_replace_image` tool, which would only be relevant for 0.7 compatible maps. Adjust the maximum allowed image width/height in `map_convert_07` tool to be consistent with the actual limit that the 0.7 client has when loading images (`1 << 13 == 8192`).

Add doxygen comments for `CImageInfo`.

Pass `CImageInfo` by reference consistently, instead of sometimes passing a pointer.

Cleanup image loading and saving code. Improve error handling.
2024-09-16 17:49:18 +02:00
Robert Müller 5212d1d7ce Rename variables with PNG to Png 2024-09-16 15:45:30 +02:00
Dennis Felsing 67eb609452
Merge pull request #8964 from furo321/fix-dragger-crash
Fix crash while attached to dragger during `hot_reload`
2024-09-16 13:06:01 +00:00
furo ad0fc8898f Fix crash while attached to dragger during hot_reload 2024-09-16 13:52:49 +02:00
Robert Müller 0a3a095479
Merge pull request #8958 from dobrykafe/pr-filter-skin-parts
Make 0.7 skin filter apply to skin parts
2024-09-16 08:45:12 +00:00
Dennis Felsing ce245f479a
Merge pull request #8963 from ChillerDragon/pr_fix_hilite
Fix name in chat not being highlighted (Closed #2190)
2024-09-16 07:35:48 +00:00
ChillerDragon ac0621a062 Fix name in chat not being highlighted (Closed #2190)
The message "xfoo foo" did not highlight for player "foo" now it does.
2024-09-16 12:39:35 +08:00
Dennis Felsing 20cb02048d
Merge pull request #8957 from Robyt3/Client-Binds-Composite-Chat-Console-Menus-Fix
Fix composite binds that open chat, console or menus
2024-09-15 21:43:19 +00:00
Dennis Felsing a5138c078e
Merge pull request #8949 from MilkeeyCat/pr_fix_color_speed_values
Fix colored speed values after connecting to a server
2024-09-15 21:42:02 +00:00
dobrykafe bf442a9f95 make 0.7 skin filter apply to skin parts 2024-09-15 23:11:11 +02:00
Robert Müller 0d93b1add7 Fix composite binds that open chat, console or menus
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.

Closes #8901.
2024-09-15 14:54:03 +02:00
Dennis Felsing 0f12044fcd
Merge pull request #8956 from Robyt3/UI-Scrollbar-Rail-HotItem
Fix scrollbar rail clicking being active while popups open
2024-09-15 10:21:41 +00:00
Robert Müller 8f2c288698 Fix scrollbar rail clicking being active while popups open
Scrollbars are now also set as the hot item when the rail is hovered and the rail clicking function is now only enabled for the scrollbar that is the hot item.

Closes #8954.
2024-09-15 11:45:22 +02:00
MilkeeyCat 9963a3e3db Pass color to RenderMovementInformationTextContainer function 2024-09-15 12:40:17 +03:00
Dennis Felsing 4b6f2e22a8
Merge pull request #8955 from furo321/url-master-parsing
Don't allow URLs without port from the masterserver
2024-09-15 09:28:43 +00:00
furo 7bbd51cf73 Don't allow URLs without port from the masterserver 2024-09-15 11:04:48 +02:00
Dennis Felsing dcd02b50bb
Merge pull request #8953 from ChillerDragon/pr_bind_null
Add BindNull to SQL api
2024-09-15 06:31:43 +00:00
Dennis Felsing 4fe956dffc
Merge pull request #8951 from furo321/fix-capture-count
Fix captures not being counted for certain names
2024-09-15 06:13:40 +00:00
ChillerDragon 7c2f058c40 Add BindNull to SQL api
Comes in handy in my downstream project
2024-09-15 09:14:00 +08:00
furo 6bf4a016ba Fix captures not being counted for certain names 2024-09-15 00:51:39 +02:00
Dennis Felsing 0feee9aa3e
Merge pull request #8948 from Robyt3/Client-Chat-TeeRenderInfo-Cleanup
Store `CTeeRenderInfo` instead of members for chat lines, extract `CTeeRenderInfo::Apply(const CSkin *)` function
2024-09-14 21:39:46 +00:00
MilkeeyCat d5be8d1633 Fix colored speed values after connecting to a server 2024-09-14 23:18:28 +03:00
Robert Müller 65cf776846 Extract CTeeRenderInfo::Apply(const CSkin *) function
Add function to apply information of `CSkin` to a `CTeeRenderInfo` to reduce duplicate code.
2024-09-14 21:12:43 +02:00
Robert Müller 62075d22e3 Store CTeeRenderInfo instead of members for chat lines
Avoid code to convert from `CTeeRenderInfo` to chat-internal representation and back to `CTeeRenderInfo`.
2024-09-14 21:12:25 +02:00
Robert Müller 0369946156
Merge pull request #8946 from MilkeeyCat/pr_fix_callvote_map_change
Don't add `/` if the directory is empty
2024-09-14 17:11:22 +00:00
MilkeeyCat 232018de23 Don't add / if the directory is empty 2024-09-14 19:49:01 +03:00
Dennis Felsing 868c513c0c Version 18.5.1 2024-09-14 15:55:22 +02:00
Dennis Felsing 5a4d8e26e6
Merge pull request #8941 from Robyt3/Client-Skin07-Dummy-Skin-Name
Fix dummy 0.7 tee skin name not being used, fix some unused config variables not being detected
2024-09-13 21:30:19 +00:00
Robert Müller ffabdee953 Fix some unused config variables not being detected
Unused config variables were not detected if they were the prefix of a longer config variable which is used.
2024-09-13 21:37:25 +02:00
Robert Müller 7e1881263d Fix dummy 0.7 tee skin name not being used
The dummy 0.7 skin name was not being updated and used, causing the wrong skin to be selected when switching between player and dummy settings.
2024-09-13 21:36:31 +02:00
Dennis Felsing bf5add67ec
Merge pull request #8940 from Robyt3/Client-Skin07-Settings-Refactoring
Rework 0.7 tee settings layout and code
2024-09-13 15:37:01 +00:00
Dennis Felsing a09ce576ac
Merge pull request #8907 from KebsCS/pr-hotreload-dummy
Fix dummy disconnecting on hot reload
2024-09-13 15:36:05 +00:00
Robert Müller 6af07a78b3 Rework 0.7 tee settings layout and code
- Show Player/Dummy tabs at the top instead of using checkbox for dummy settings like in regular skin settings.
- Show Basic/Custom tabs instead of using button to toggle custom skin settings.
- Move Skin directory and Refresh icon buttons to right side like in regular skin settings. Refreshing 0.7 skins and parts is not implemented yet though.
- Render 0.7 skin entries like entries of the regular skin list, i.e. with more space and with only four skins per row.
- Move the Random skin button next to the Custom colors checkbox and make it an icon button like in the regular skin settings.
- Move skin preview to the right side of the Player/Dummy/Basic/Custom tabs to reduce unused empty space.
- Remove most overlapping and unnecessarily dark backgrounds.
- Improve layout of skin part tabs by only using colors for the first and last buttons.
- Localize skin part tab names again but properly capitalize the names being shown in the UI.
2024-09-13 17:01:41 +02:00
KebsCS 1477b8c69e
Fix dummy disconnecting on hot reload 2024-09-13 16:53:06 +02:00
Dennis Felsing 8d431f8feb
Merge pull request #8934 from sjrc6/pr_freeze_stars
Add client setting to restore old freeze stars
2024-09-12 12:31:54 +00:00
Tater 80e2de13da Add client side freeze stars 2024-09-12 04:48:46 -05:00
Dennis Felsing 66fb5d5d7f
Merge pull request #8933 from Robyt3/UI-EditText-Searchable
Extract `CUi::DoEditBox_Search` function for quick search
2024-09-11 21:55:25 +00:00
Robert Müller d3f0c2a156 Extract CUi::DoEditBox_Search function for quick search
Reduce duplicate and inconsistent code for rendering quick search for the demo browser, ingame vote list, player flag, skin, skin 0.7 and asset search.

The quick search and exclude in the server browser are not refactored, as they have additional labels and different alignment, which would make a general function complicated.
2024-09-11 21:33:08 +02:00
Jupeyy d5e81ca78d
Merge pull request #8932 from Robyt3/Client-Network-Before-Graphics
Initialize client networking before graphics
2024-09-11 18:07:20 +00:00
Robert Müller 128ffd2313 Initialize client networking before graphics
Avoid Vulkan crash if the backend is destroyed immediately after being created.

Slightly decreases time of initial black screen before loading menu is rendered.
2024-09-11 19:34:31 +02:00
Dennis Felsing af1b32d296
Merge pull request #8930 from timakro/pr-fix-econ-listening-publicly
Fixes a bug where econ was exposed publicly when ec_bindaddr was set to localhost
2024-09-11 16:50:42 +00:00
Tim Schumacher 0ad1c08c22 Fixes a bug where econ was exposed publicly when ec_bindaddr was set to localhost
Also implements the original intention of 85f5e9c that is to disable
econ if ec_binaddr is invalid.
2024-09-11 18:28:07 +02:00
71 changed files with 1590 additions and 1638 deletions

View file

@ -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
View file

View 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

View file

@ -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."""

View file

@ -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();

View file

@ -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;

View file

@ -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));
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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; }

View file

@ -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
View 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);
}
}

View file

@ -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;
}

View file

@ -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

View file

@ -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)

View file

@ -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);

View file

@ -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
View 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

View file

@ -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;

View file

@ -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;

View file

@ -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)

View file

@ -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__)

View file

@ -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();

View file

@ -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;

View file

@ -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")

View file

@ -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))
{

View file

@ -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")

View file

@ -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;
}

View file

@ -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);

View file

@ -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;

View file

@ -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
{

View file

@ -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);
}
}

View file

@ -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();

View file

@ -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);
}
}

View file

@ -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));

View file

@ -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);

View file

@ -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) {

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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];

View file

@ -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++)
{

View file

@ -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)

View file

@ -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

View file

@ -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;

View file

@ -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++;

View file

@ -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);
}

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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)

View file

@ -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));
}
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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

View file

@ -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();

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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();

View file

@ -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);
}
}

View file

@ -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;